Sign in verified accounts automatically and require eight-character passwords

This commit is contained in:
2026-10-13 16:33:52 +09:00
parent 9e0f493228
commit d6e60ecb3b
18 changed files with 538 additions and 63 deletions

View File

@@ -68,18 +68,35 @@ try {
const auth = page.locator('.account-auth');
await auth.getByLabel('이메일', { exact: true }).fill(email);
await auth.getByLabel('표시 이름', { exact: true }).fill(name);
await auth.getByLabel('비밀번호', { exact: true }).fill('1234');
await auth.getByLabel('비밀번호 확인', { exact: true }).fill('1234');
assert.equal(await auth.getByLabel('비밀번호', { exact: true }).getAttribute('minlength'), '8');
assert.equal(
await auth.getByLabel('비밀번호', { exact: true }).getAttribute('placeholder'),
'8자 이상',
);
await auth.getByLabel('비밀번호', { exact: true }).fill('1234567');
await auth.getByLabel('비밀번호 확인', { exact: true }).fill('1234567');
const beforeShortPassword = mails.length;
await auth.getByRole('button', { name: '가입하고 인증 메일 받기' }).click();
assert.equal(
await auth.getByLabel('비밀번호', { exact: true }).evaluate((el) => el.validity.tooShort),
true,
);
assert.equal(mails.length, beforeShortPassword);
await auth.getByLabel('비밀번호', { exact: true }).fill('12345678');
await auth.getByLabel('비밀번호 확인', { exact: true }).fill('12345678');
await auth.getByRole('button', { name: '가입하고 인증 메일 받기' }).click();
await auth.locator('.auth-message').waitFor();
const token = mails.find((m) => m.to === email).text.match(/#verify=([A-Za-z0-9_-]+)/)[1];
await page.goto(`${url}/#verify=${token}`);
await page.getByRole('button', { name: '이메일 인증 완료하기' }).click();
await auth.getByRole('button', { name: '로그인', exact: true }).waitFor();
await auth.getByLabel('이메일', { exact: true }).fill(email);
await auth.getByLabel('비밀번호', { exact: true }).fill('1234');
await auth.getByRole('button', { name: '로그인', exact: true }).click();
await page.locator('.account-workspace').waitFor();
assert.equal(
await page.evaluate(async () => (await (await fetch('/api/auth/me')).json()).account.email),
email,
'인증한 계정으로 자동 로그인',
);
assert.equal(await auth.count(), 0);
assert(!(await page.locator('body').innerText()).includes('로그인해 주세요'));
assert.deepEqual(
(await page.getByRole('tab').allTextContents()).map((text) => text.trim()),
['받은 항목', '임시 저장'],
@@ -377,9 +394,82 @@ try {
await receiver.getByRole('button', { name: '로그아웃', exact: true }).click();
await receiver.locator('.account-auth').waitFor();
assert(!(await receiver.locator('body').innerText()).includes('https://example.com/saved'));
const signup = receiver.locator('.account-auth');
await signup.getByRole('button', { name: '이메일로 가입', exact: true }).click();
await signup.getByLabel('이메일', { exact: true }).fill('carol@example.com');
await signup.getByLabel('표시 이름', { exact: true }).fill('Carol');
await signup.getByLabel('비밀번호', { exact: true }).fill('12345678');
await signup.getByLabel('비밀번호 확인', { exact: true }).fill('12345678');
await signup.getByRole('button', { name: '가입하고 인증 메일 받기' }).click();
await signup.locator('.auth-message').waitFor();
const carolToken = mails
.find((mail) => mail.to === 'carol@example.com')
.text.match(/#verify=([A-Za-z0-9_-]+)/)[1];
const oldAccountCookie = (await a.cookies(url)).find(
(cookie) => cookie.name === 'hop_account',
).value;
const verificationPage = await a.newPage();
verificationPage.on('pageerror', (error) => errors.push(error.message));
await verificationPage.goto(`${url}/#verify=${carolToken}`);
await verificationPage.getByRole('button', { name: '이메일 인증 완료하기' }).waitFor();
await verificationPage.locator('.app-shell[data-online="true"]').waitFor();
await verificationPage.waitForFunction(
() => document.querySelector('.account-auth button[type="submit"]')?.disabled === false,
);
assert.equal(
await verificationPage.evaluate(
async () => (await (await fetch('/api/auth/me')).json()).account.email,
),
'alice@example.com',
);
let delayedAuthLookup = false;
await verificationPage.route('**/api/auth/me', async (route) => {
const response = await route.fetch();
if (!delayedAuthLookup) {
delayedAuthLookup = true;
const stale = { ...(await response.json()), account: null };
await new Promise((resolve) => setTimeout(resolve, 260));
await route.fulfill({ response, json: stale });
} else await route.fulfill({ response });
});
await verificationPage.getByRole('button', { name: '이메일 인증 완료하기' }).click();
await verificationPage.locator('.account-workspace').waitFor();
await verificationPage.unroute('**/api/auth/me');
assert.equal(
await verificationPage.evaluate(
async () => (await (await fetch('/api/auth/me')).json()).account.email,
),
'carol@example.com',
);
assert.equal(
await verificationPage
.getByRole('tab', { name: '받은 항목', exact: true })
.getAttribute('aria-selected'),
'true',
);
assert.equal(await verificationPage.locator('.item-card,.account-auth').count(), 0);
assert(
!(await verificationPage.locator('body').innerText()).includes('https://example.com/saved'),
);
const revokedAccount = await a.request.get(`${url}/api/auth/me`, {
headers: { Cookie: `hop_account=${oldAccountCookie}` },
});
assert.equal((await revokedAccount.json()).account, null);
const inaccessibleVault = await a.request.get(`${url}/api/vault`, {
headers: { Cookie: `hop_account=${oldAccountCookie}` },
});
assert.equal(inaccessibleVault.status(), 401);
assert.equal(verificationPage.url(), `${url}/`, '인증 토큰을 주소창에 남기지 않음');
await verificationPage.getByRole('button', { name: '계정 설정', exact: true }).click();
await verificationPage
.getByRole('dialog', { name: '계정 설정', exact: true })
.getByText('carol@example.com', { exact: true })
.waitFor();
await verificationPage.screenshot({ path: 'tests/e2e/verification-switch.png' });
await verificationPage.close();
assert.deepEqual(errors, []);
console.log(
'PASS: 받은 항목 기본 화면·보관 안내, PC/모바일 계정 설정·입력·키보드 탐색, 가입·인증·로그인, 날짜별 기록, 직접 링크/파일 저장·메모·수락 후 자동 열기/다운로드, 파일 재업로드 없음, 7일 만료·삭제/복원·로그아웃 권한 격리, 원본 수동 삭제 시 다운로드 전 안내·양쪽 목록/용량 정리·열린 전송 선택 해제. 실제 Resend 발송은 포함하지 않음.',
'PASS: PC/모바일 가입 8자 규칙과 인증 자동 로그인, 다른 계정의 인증 링크에서 세션 교체·이전 쿠키 무효화·보관함 격리·지연된 인증 조회 경합, 받은 항목 기본 화면·계정 설정·날짜별 기록·저장/메모/수락 후 열기/다운로드·7일 보관·삭제/복원·원본 수동 삭제 대응·토스트. 실제 Resend 발송은 포함하지 않음.',
);
} catch (error) {
console.error(error);