1
0
forked from baron/baron-sso

test verify-only approval close routing

This commit is contained in:
2026-05-21 14:48:32 +09:00
parent eb46918397
commit 710f1a865c

View File

@@ -3,6 +3,8 @@ import { expect, test, type Page, type Route } from '@playwright/test';
type MockOptions = {
sessionStatus?: number;
captureApprove?: (pendingRef: string | null) => void;
captureUserMe?: () => void;
captureVerify?: (path: string, body: Record<string, unknown>) => void;
};
async function seedTokenLogin(page: Page): Promise<void> {
@@ -33,11 +35,12 @@ async function mockUserfrontApis(
): Promise<void> {
const sessionStatus = options.sessionStatus ?? 200;
await page.route('**/api/v1/**', async (route: Route) => {
await page.context().route('**/api/v1/**', async (route: Route) => {
const requestUrl = new URL(route.request().url());
const path = requestUrl.pathname;
if (path.endsWith('/api/v1/user/me')) {
options.captureUserMe?.();
if (sessionStatus === 200) {
await route.fulfill({
status: 200,
@@ -117,6 +120,13 @@ async function mockUserfrontApis(
path.endsWith('/api/v1/auth/login/code/verify') ||
path.endsWith('/api/v1/auth/login/code/verify-short')
) {
let body: Record<string, unknown> = {};
try {
body = (route.request().postDataJSON() ?? {}) as Record<string, unknown>;
} catch {
body = {};
}
options.captureVerify?.(path, body);
await route.fulfill({
status: 200,
contentType: 'application/json',
@@ -196,13 +206,35 @@ test.describe('UserFront WASM auth routing', () => {
test('verifyOnly 승인 완료 화면의 상단 액션은 signin으로 이동시키지 않는다', async ({
page,
}) => {
await mockUserfrontApis(page, { sessionStatus: 401 });
let userMeCalls = 0;
const verifyRequests: Array<{
path: string;
body: Record<string, unknown>;
}> = [];
await mockUserfrontApis(page, {
sessionStatus: 401,
captureUserMe: () => {
userMeCalls += 1;
},
captureVerify: (path, body) => {
verifyRequests.push({ path, body });
},
});
await page.goto('/ko/l/AB123456');
await expect(page).toHaveURL(/\/ko\/l\/AB123456$/);
await page.waitForTimeout(500);
await expect.poll(() => verifyRequests.length, { timeout: 10_000 }).toBe(1);
await expect(page).toHaveURL(/\/ko\/l\/AB123456$/);
expect(userMeCalls).toBe(0);
expect(verifyRequests[0].path).toContain(
'/api/v1/auth/login/code/verify-short',
);
expect(verifyRequests[0].body).toMatchObject({
shortCode: 'AB123456',
verifyOnly: true,
});
await page.locator('flt-glass-pane').click({
position: { x: 30, y: 28 },
@@ -212,4 +244,188 @@ test.describe('UserFront WASM auth routing', () => {
await expect(page).toHaveURL(/\/ko\/l\/AB123456$/);
});
test('verifyOnly 승인 완료 버튼은 SMS 링크에서 user/me 조회나 루트 이동을 만들지 않는다', async ({
page,
}) => {
let userMeCalls = 0;
let verifyCalls = 0;
await mockUserfrontApis(page, {
sessionStatus: 401,
captureUserMe: () => {
userMeCalls += 1;
},
captureVerify: () => {
verifyCalls += 1;
},
});
await page.goto('/ko/l/AB123456');
await expect(page).toHaveURL(/\/ko\/l\/AB123456$/);
await expect.poll(() => verifyCalls, { timeout: 10_000 }).toBe(1);
expect(userMeCalls).toBe(0);
const viewport = page.viewportSize();
if (!viewport) throw new Error('viewport is required');
await page.locator('flt-glass-pane').click({
position: {
x: Math.floor(viewport.width / 2),
y: Math.floor(viewport.height * 0.66),
},
force: true,
});
await page.waitForTimeout(300);
expect(userMeCalls).toBe(0);
await expect(page).toHaveURL(/\/ko\/l\/AB123456$/);
});
test('verifyOnly 승인 링크를 팝업에서 닫으면 창만 닫히고 부모는 이동하지 않는다', async ({
page,
}, testInfo) => {
let userMeCalls = 0;
let verifyCalls = 0;
await mockUserfrontApis(page, {
sessionStatus: 401,
captureUserMe: () => {
userMeCalls += 1;
},
captureVerify: () => {
verifyCalls += 1;
},
});
const baseURL = testInfo.project.use.baseURL;
if (typeof baseURL !== 'string') throw new Error('baseURL is required');
const popupURL = new URL('/ko/l/AB123456', baseURL).toString();
await page.goto('about:blank');
await expect(page).toHaveURL('about:blank');
const popupPromise = page.waitForEvent('popup');
await page.evaluate((url) => {
window.open(url, '_blank');
}, popupURL);
const popup = await popupPromise;
await expect(popup).toHaveURL(/\/ko\/l\/AB123456$/);
await expect.poll(() => verifyCalls, { timeout: 10_000 }).toBe(1);
expect(userMeCalls).toBe(0);
const viewport = popup.viewportSize();
if (!viewport) throw new Error('viewport is required');
const closePromise = popup.waitForEvent('close');
await popup.locator('flt-glass-pane').click({
position: {
x: Math.floor(viewport.width / 2),
y: Math.floor(viewport.height * 0.66),
},
force: true,
});
await closePromise;
expect(userMeCalls).toBe(0);
await expect(page).toHaveURL('about:blank');
});
test('verifyOnly 승인 완료 버튼은 이메일 magic link에서도 user/me 조회나 루트 이동을 만들지 않는다', async ({
page,
}) => {
let userMeCalls = 0;
const verifyRequests: Array<{
path: string;
body: Record<string, unknown>;
}> = [];
await mockUserfrontApis(page, {
sessionStatus: 401,
captureUserMe: () => {
userMeCalls += 1;
},
captureVerify: (path, body) => {
verifyRequests.push({ path, body });
},
});
await page.goto('/ko/verify/e2e-email-token');
await expect(page).toHaveURL(/\/ko\/verify\/e2e-email-token$/);
await expect.poll(() => verifyRequests.length, { timeout: 10_000 }).toBe(1);
expect(userMeCalls).toBe(0);
expect(verifyRequests[0].path).toContain('/api/v1/auth/magic-link/verify');
expect(verifyRequests[0].body).toMatchObject({
token: 'e2e-email-token',
verifyOnly: true,
});
const viewport = page.viewportSize();
if (!viewport) throw new Error('viewport is required');
await page.locator('flt-glass-pane').click({
position: {
x: Math.floor(viewport.width / 2),
y: Math.floor(viewport.height * 0.66),
},
force: true,
});
await page.waitForTimeout(300);
expect(userMeCalls).toBe(0);
await expect(page).toHaveURL(/\/ko\/verify\/e2e-email-token$/);
});
test('verifyOnly 승인 완료 버튼은 이메일 code link에서도 user/me 조회나 루트 이동을 만들지 않는다', async ({
page,
}) => {
let userMeCalls = 0;
const verifyRequests: Array<{
path: string;
body: Record<string, unknown>;
}> = [];
await mockUserfrontApis(page, {
sessionStatus: 401,
captureUserMe: () => {
userMeCalls += 1;
},
captureVerify: (path, body) => {
verifyRequests.push({ path, body });
},
});
await page.goto(
'/ko/verify?loginId=e2e%40example.com&code=654321&pendingRef=pending-email',
);
await expect(page).toHaveURL(
/\/ko\/verify\?loginId=e2e(?:%40|@)example\.com&code=654321&pendingRef=pending-email$/,
);
await expect.poll(() => verifyRequests.length, { timeout: 10_000 }).toBe(1);
expect(userMeCalls).toBe(0);
expect(verifyRequests[0].path).toContain('/api/v1/auth/login/code/verify');
expect(verifyRequests[0].body).toMatchObject({
loginId: 'e2e@example.com',
code: '654321',
pendingRef: 'pending-email',
verifyOnly: true,
});
const viewport = page.viewportSize();
if (!viewport) throw new Error('viewport is required');
await page.locator('flt-glass-pane').click({
position: {
x: Math.floor(viewport.width / 2),
y: Math.floor(viewport.height * 0.66),
},
force: true,
});
await page.waitForTimeout(300);
expect(userMeCalls).toBe(0);
await expect(page).toHaveURL(
/\/ko\/verify\?loginId=e2e(?:%40|@)example\.com&code=654321&pendingRef=pending-email$/,
);
});
});