118 lines
5.5 KiB
TypeScript
118 lines
5.5 KiB
TypeScript
import { expect, test, type Page, type Route } from '@playwright/test';
|
|
import { writeFile } from 'node:fs/promises';
|
|
|
|
const response = (data: unknown) => ({ result: { data } });
|
|
const operationNames = (route: Route): string[] => {
|
|
const url = new URL(route.request().url());
|
|
return decodeURIComponent(url.pathname.slice(url.pathname.lastIndexOf('/trpc/') + 6)).split(',');
|
|
};
|
|
|
|
const installFixture = async (page: Page, approved = true) => {
|
|
const requests: Array<{ operation: string; body: string }> = [];
|
|
await page.addInitScript(() => {
|
|
window.localStorage.setItem('sammo-session-token', 'kakao-replacement-session');
|
|
});
|
|
await page.route('**/gateway/api/trpc/**', async (route) => {
|
|
const body = route.request().postData() ?? '';
|
|
const results = operationNames(route).map((operation) => {
|
|
requests.push({ operation, body });
|
|
if (operation === 'account.get') {
|
|
return response({
|
|
id: 'replacement-user',
|
|
username: 'replacement-user',
|
|
displayName: '교체 사용자',
|
|
roles: ['user'],
|
|
oauthType: 'KAKAO',
|
|
email: 'replacement@example.test',
|
|
createdAt: '2026-08-01T00:00:00.000Z',
|
|
iconUrl: null,
|
|
icons: [],
|
|
preferredPicture: 'default.jpg',
|
|
maxActiveIcons: 5,
|
|
nextUploadAt: null,
|
|
nextRetireAt: null,
|
|
thirdPartyUse: false,
|
|
deleteAfter: null,
|
|
kakaoReplacementApprovedUntil: approved ? new Date(Date.now() + 86_400_000).toISOString() : null,
|
|
});
|
|
}
|
|
if (operation === 'account.notifications.get') {
|
|
return response({
|
|
capability: { enabled: false, publicKey: null },
|
|
eventTypes: [],
|
|
profiles: [],
|
|
preferences: [],
|
|
subscriptionCount: 0,
|
|
currentDeviceSubscribed: false,
|
|
});
|
|
}
|
|
if (operation === 'auth.kakaoStart') {
|
|
return response({
|
|
authUrl: `${new URL(route.request().url()).origin}/gateway/account?replacement=started`,
|
|
});
|
|
}
|
|
throw new Error(`Unhandled account replacement fixture operation: ${operation}`);
|
|
});
|
|
const isBatch = new URL(route.request().url()).searchParams.get('batch') === '1';
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify(isBatch ? results : results[0]),
|
|
});
|
|
});
|
|
return requests;
|
|
};
|
|
|
|
test('starts a Kakao replacement with confirmation before authenticating the new account', async ({
|
|
page,
|
|
}, testInfo) => {
|
|
const requests = await installFixture(page);
|
|
let confirmation = '';
|
|
page.on('dialog', async (dialog) => {
|
|
confirmation = dialog.message();
|
|
await dialog.accept();
|
|
});
|
|
|
|
await page.goto('account');
|
|
const button = page.getByRole('button', { name: '새 카카오 계정으로 교체' });
|
|
await expect(button).toBeVisible();
|
|
await expect(page.getByText(/교체 승인 .*까지/)).toBeVisible();
|
|
const initialBackground = await button.evaluate((element) => getComputedStyle(element).backgroundColor);
|
|
await button.hover();
|
|
await expect
|
|
.poll(() => button.evaluate((element) => getComputedStyle(element).backgroundColor))
|
|
.not.toBe(initialBackground);
|
|
await button.focus();
|
|
await expect(button).toBeFocused();
|
|
await page.screenshot({ path: testInfo.outputPath('account-kakao-replacement-desktop.png'), fullPage: true });
|
|
|
|
await button.click();
|
|
await expect(page).toHaveURL(/replacement=started/);
|
|
expect(confirmation).toContain('새 카카오톡 인증을 완료하면');
|
|
const startRequest = requests.find(({ operation }) => operation === 'auth.kakaoStart');
|
|
expect(startRequest?.body).toContain('verify');
|
|
expect(startRequest?.body).toContain('kakao-replacement-session');
|
|
|
|
await page.setViewportSize({ width: 500, height: 844 });
|
|
const mobileButton = page.getByRole('button', { name: '새 카카오 계정으로 교체' });
|
|
await expect(mobileButton).toBeVisible();
|
|
const geometry = await mobileButton.evaluate((element) => {
|
|
const rect = element.getBoundingClientRect();
|
|
return { left: rect.left, right: rect.right, width: rect.width, viewportWidth: window.innerWidth };
|
|
});
|
|
expect(geometry.left).toBeGreaterThanOrEqual(0);
|
|
expect(geometry.right).toBeLessThanOrEqual(geometry.viewportWidth);
|
|
await writeFile(testInfo.outputPath('account-kakao-replacement-mobile-geometry.json'), JSON.stringify(geometry));
|
|
await page.screenshot({ path: testInfo.outputPath('account-kakao-replacement-mobile.png'), fullPage: true });
|
|
});
|
|
|
|
test('offers ordinary users account replacement without an administrator approval', async ({ page }) => {
|
|
const requests = await installFixture(page, false);
|
|
page.on('dialog', (dialog) => dialog.accept());
|
|
await page.goto('account');
|
|
await expect(page.getByText(/교체 승인 .*까지/)).toHaveCount(0);
|
|
await page.getByRole('button', { name: '새 카카오 계정으로 교체', exact: true }).click();
|
|
await expect(page).toHaveURL(/replacement=started/);
|
|
expect(requests.some(({ operation }) => operation === 'auth.kakaoStart')).toBe(true);
|
|
});
|