Use visible NAS upload storage and reconcile missing files
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { chromium } from '@playwright/test';
|
||||
import { mkdtemp, rm } from 'node:fs/promises';
|
||||
import { mkdtemp, rm, readdir, unlink } from 'node:fs/promises';
|
||||
import { resolve } from 'node:path';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { loadConfig } from '../../apps/server/src/config.ts';
|
||||
@@ -80,6 +80,10 @@ try {
|
||||
await auth.getByLabel('비밀번호', { exact: true }).fill('1234');
|
||||
await auth.getByRole('button', { name: '로그인', exact: true }).click();
|
||||
await page.locator('.account-workspace').waitFor();
|
||||
assert.deepEqual(
|
||||
(await page.getByRole('tab').allTextContents()).map((text) => text.trim()),
|
||||
['받은 항목', '임시 저장'],
|
||||
);
|
||||
assert.equal(
|
||||
await page.getByRole('tab', { name: '받은 항목', exact: true }).getAttribute('aria-selected'),
|
||||
'true',
|
||||
@@ -109,6 +113,11 @@ try {
|
||||
await page.keyboard.press('Tab');
|
||||
assert.equal(await preferences.locator('select,input').count(), 0, '항목 목록을 먼저 표시');
|
||||
assert.equal(await preferences.locator('.preference-item').count(), 2);
|
||||
assert.equal(
|
||||
await preferences.getByRole('button', { name: '이메일 변경', exact: true }).count(),
|
||||
0,
|
||||
'이메일 변경 버튼 숨김',
|
||||
);
|
||||
assert.equal(
|
||||
await preferences.locator('.preference-item').first().locator('p').innerText(),
|
||||
email,
|
||||
@@ -146,20 +155,19 @@ try {
|
||||
}
|
||||
await page.screenshot({ path: `tests/e2e/account-password-${device}.png` });
|
||||
await preferences.getByRole('button', { name: '계정 설정 목록으로', exact: true }).click();
|
||||
await preferences.getByRole('button', { name: '이메일 변경', exact: true }).click();
|
||||
await preferences.getByRole('button', { name: '비밀번호 변경', exact: true }).click();
|
||||
assert.equal(
|
||||
await preferences.getByLabel('현재 비밀번호', { exact: true }).inputValue(),
|
||||
'',
|
||||
'항목 전환 시 비밀번호 초안 제거',
|
||||
);
|
||||
await preferences.getByLabel('새 이메일', { exact: true }).fill('new@example.com');
|
||||
await page.screenshot({ path: `tests/e2e/account-email-${device}.png` });
|
||||
await preferences.getByLabel('새 비밀번호', { exact: true }).fill('draft1234');
|
||||
await page.keyboard.press('Escape');
|
||||
await preferences.waitFor({ state: 'hidden' });
|
||||
await page.getByRole('button', { name: '계정 설정', exact: true }).click();
|
||||
await preferences.getByRole('button', { name: '이메일 변경', exact: true }).click();
|
||||
await preferences.getByRole('button', { name: '비밀번호 변경', exact: true }).click();
|
||||
assert.equal(
|
||||
await preferences.getByLabel('새 이메일', { exact: true }).inputValue(),
|
||||
await preferences.getByLabel('새 비밀번호', { exact: true }).inputValue(),
|
||||
'',
|
||||
'모달 닫기 시 개인정보 초안 제거',
|
||||
);
|
||||
@@ -220,6 +228,10 @@ try {
|
||||
await sender.locator('.current-url, .current-text').fill('https://example.com/saved');
|
||||
await sender.getByRole('button', { name: '저장하기', exact: true }).click();
|
||||
await sender.locator('.item-card').filter({ hasText: '나중에 읽을 글' }).waitFor();
|
||||
assert.match(
|
||||
await sender.locator('.item-footer > span').first().innerText(),
|
||||
/^보관 만료 · .+까지$/,
|
||||
);
|
||||
await sender.getByRole('button', { name: '메모 수정', exact: true }).click();
|
||||
await sender.getByLabel('한 줄 메모 수정').fill('업무 링크');
|
||||
await sender.locator('.note-editor').getByRole('button', { name: '저장', exact: true }).click();
|
||||
@@ -300,12 +312,74 @@ try {
|
||||
);
|
||||
const saved = await sender.evaluate(async () => (await (await fetch('/api/vault')).json()).items);
|
||||
assert(saved.every((item) => item.expiresAt - item.createdAt <= 7 * 86400000));
|
||||
await navigate(receiver, '기기');
|
||||
await receiver.locator('.peer-card').first().click();
|
||||
await receiver.getByRole('button', { name: '보관함에서 가져오기', exact: true }).click();
|
||||
await receiver.locator('.vault-choice').filter({ hasText: 'saved.txt' }).click();
|
||||
await receiver.locator('.current-vault').waitFor();
|
||||
assert.equal(await receiver.locator('.content-submit').isEnabled(), true);
|
||||
let removed = false;
|
||||
for (const key of await readdir(runtime.service.storage.originals)) {
|
||||
const path = resolve(runtime.service.storage.originals, key);
|
||||
if ((await readFile(path)).equals(bytes)) {
|
||||
await unlink(path);
|
||||
removed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert(removed, '보관함 원본을 서버 저장소에서 직접 삭제');
|
||||
let unexpectedDownloads = 0;
|
||||
sender.on('download', () => unexpectedDownloads++);
|
||||
await fileRow.getByRole('button', { name: '다운로드', exact: true }).click();
|
||||
await sender.locator('.error-toast').filter({ hasText: '다시 전송' }).waitFor();
|
||||
const toastAlignment = await sender.locator('.error-toast').evaluate((element) => {
|
||||
const toast = element.getBoundingClientRect(),
|
||||
text = element.querySelector('span').getBoundingClientRect(),
|
||||
icon = element.querySelector('button svg').getBoundingClientRect();
|
||||
return {
|
||||
left: text.left - toast.left,
|
||||
right: toast.right - icon.right,
|
||||
textCenter: text.top + text.height / 2,
|
||||
iconCenter: icon.top + icon.height / 2,
|
||||
};
|
||||
});
|
||||
assert(
|
||||
Math.abs(toastAlignment.left - toastAlignment.right) < 1,
|
||||
'오류 텍스트와 X 아이콘의 양쪽 여백 균형',
|
||||
);
|
||||
assert(
|
||||
Math.abs(toastAlignment.textCenter - toastAlignment.iconCenter) < 1,
|
||||
'오류 텍스트와 X 아이콘의 세로 정렬',
|
||||
);
|
||||
await sender.screenshot({ path: 'tests/e2e/account-missing-file.png', fullPage: true });
|
||||
assert.equal(unexpectedDownloads, 0, '없는 파일을 다운로드하기 전에 안내');
|
||||
await fileRow.waitFor({ state: 'hidden' });
|
||||
await receiver.locator('.current-vault').waitFor({ state: 'hidden' });
|
||||
await receiver
|
||||
.locator('.vault-choice')
|
||||
.filter({ hasText: 'saved.txt' })
|
||||
.waitFor({ state: 'hidden' });
|
||||
assert.equal(
|
||||
await receiver.locator('.content-submit').isDisabled(),
|
||||
true,
|
||||
'다른 기기의 열린 전송 선택도 삭제 알림으로 해제',
|
||||
);
|
||||
await receiver.getByRole('button', { name: '전송 화면 닫기', exact: true }).click();
|
||||
await navigate(receiver, '받은 항목');
|
||||
for (const page of [sender, receiver])
|
||||
assert.equal(
|
||||
await page.evaluate(
|
||||
async () => (await (await fetch('/api/account/storage')).json()).usedBytes,
|
||||
),
|
||||
0,
|
||||
);
|
||||
await sender.locator('.error-toast').waitFor({ state: 'hidden', timeout: 8000 });
|
||||
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'));
|
||||
assert.deepEqual(errors, []);
|
||||
console.log(
|
||||
'PASS: 받은 항목 기본 화면·보관 안내, PC/모바일 계정 설정 목록·항목별 폼·간격·16px 입력·초안 제거·키보드 탐색, 가입·인증·로그인, 날짜별 기록, 직접 링크/파일 저장과 메모 수정, 보관함 자료의 수락 후 자동 열기/다운로드, 파일 재업로드 없음, 7일 만료, 삭제/복원, 로그아웃 권한 격리. 실제 Resend 발송은 포함하지 않음.',
|
||||
'PASS: 받은 항목 기본 화면·보관 안내, PC/모바일 계정 설정·입력·키보드 탐색, 가입·인증·로그인, 날짜별 기록, 직접 링크/파일 저장·메모·수락 후 자동 열기/다운로드, 파일 재업로드 없음, 7일 만료·삭제/복원·로그아웃 권한 격리, 원본 수동 삭제 시 다운로드 전 안내·양쪽 목록/용량 정리·열린 전송 선택 해제. 실제 Resend 발송은 포함하지 않음.',
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
Reference in New Issue
Block a user