Move file transfer restriction into network settings

This commit is contained in:
2026-10-15 13:51:36 +09:00
parent 328fad2e36
commit 9cda9cbe84
20 changed files with 456 additions and 88 deletions

View File

@@ -26,6 +26,9 @@ const runtime = await createApp(
},
{ mailer: async () => {} },
);
// A NAS-sized disk verifies comma grouping in the rendered admin card.
const realSummary = runtime.service.admin.summary.bind(runtime.service.admin);
runtime.service.admin.summary = () => ({ ...realSummary(), diskFreeBytes: 7687e9 });
let browser;
try {
await runtime.app.listen({ host: '127.0.0.1', port: 0 });
@@ -90,7 +93,7 @@ try {
const response = await fetch('/api' + path, {
method,
headers: {
'Content-Type': 'application/json',
...(body !== undefined ? { 'Content-Type': 'application/json' } : {}),
'X-CSRF-Token': csrf,
'Idempotency-Key': crypto.randomUUID(),
},
@@ -150,6 +153,10 @@ try {
await admin.getByRole('button', { name: '더보기', exact: true }).click();
await admin.getByRole('menuitem', { name: '관리자', exact: true }).click();
await admin.locator('.admin-stats').waitFor();
assert.equal(
await admin.locator('.admin-stats article').nth(1).locator('strong').innerText(),
'7,687.0GB',
);
await admin.locator('.admin-file').filter({ hasText: 'sample.mp4' }).waitFor();
assert.match(await admin.locator('.admin-file').innerText(), /중국\(CN\) · Beijing/);
assert.match(await admin.locator('.admin-file').innerText(), /1\.1\.1\.1/);
@@ -161,16 +168,119 @@ try {
await admin.locator('.admin-file footer small').innerText(),
/^\[보관 만료\] \d{4}년 \d{1,2}월 \d{1,2}일 \d{2}:\d{2} 까지$/,
);
await admin.getByRole('button', { name: '신규 업로드 중지', exact: true }).click();
await admin.getByText('신규 업로드 중지됨', { exact: true }).waitFor();
assert.equal(await admin.locator('.admin-upload-control').count(), 0);
// Prepare a stored account file and an unsent file draft before pausing.
const normalSessionBefore = await session(normal);
const policyFile = await call(normal, normalSessionBefore.csrfToken, '/vault', {
kind: 'file',
files: [{ name: 'policy-file.txt', size: 4, type: 'text/plain' }],
});
await uploadFile(
normal,
normalSessionBefore.csrfToken,
policyFile.requestId,
'policy-file.txt',
'text/plain',
);
await normal.getByRole('button', { name: '더보기', exact: true }).click();
await normal.getByRole('tab', { name: '임시 저장', exact: true }).click();
const policyCard = normal.locator('.compact-item').filter({ hasText: 'policy-file.txt' });
await policyCard.waitFor();
await normal.getByRole('button', { name: '보관함에 담기', exact: true }).click();
await normal.getByRole('button', { name: '파일 저장', exact: true }).click();
await sender
.locator('.peer-card')
.filter({ hasText: '#' + b.device.code })
.click();
const composer = sender.locator('.send-dialog');
await composer
.locator('input[type=file]')
.setInputFiles({ name: 'draft.txt', mimeType: 'text/plain', buffer: Buffer.from('draft') });
await composer.locator('.selected-files').waitFor();
await admin.getByRole('button', { name: '네트워크', exact: true }).click();
async function banFiles(value) {
await admin.getByRole('button', { name: '수정', exact: true }).click();
const toggle = admin.getByRole('switch', { name: '파일 전송 금지', exact: true });
if ((await toggle.getAttribute('aria-checked')) !== String(value)) await toggle.click();
await admin.getByRole('button', { name: '네트워크 설정 저장', exact: true }).click();
await admin.getByRole('button', { name: '수정', exact: true }).waitFor();
}
await banFiles(true);
assert.equal(runtime.service.admin.state.uploadsPaused, true);
await admin.getByRole('button', { name: '업로드 재개', exact: true }).click();
await admin.getByText('신규 업로드 허용 중', { exact: true }).waitFor();
await composer
.getByText('파일 전송이 일시 제한되어 링크와 텍스트만 보낼 수 있어요.', { exact: true })
.waitFor();
assert.equal(await composer.locator('input[type=file]').count(), 0);
assert.equal(
await composer.getByRole('button', { name: '대신 파일 선택하기', exact: true }).count(),
0,
);
assert.equal(await composer.locator('.selected-files').count(), 0);
await normal.getByLabel('보낼 텍스트', { exact: true }).waitFor();
assert.equal(await normal.locator('input[type=file]').count(), 0);
assert.equal(
await policyCard.getByRole('button', { name: '기기로 보내기', exact: true }).count(),
0,
);
assert.equal(await policyCard.getByRole('button', { name: '다운로드', exact: true }).count(), 1);
await normal.getByRole('button', { name: '다른 종류 선택', exact: true }).click();
assert.equal(await normal.getByRole('button', { name: '파일 저장', exact: true }).count(), 0);
await normal.getByRole('button', { name: '보관함 저장 닫기', exact: true }).click();
const pausedGuest = await page(390);
await pausedGuest.locator('.file-policy-note').waitFor();
await pausedGuest.context().close();
const message = '파일 제한 중에도 한글 日本語 😀\n 텍스트를 전송합니다.';
await composer.getByLabel('보낼 텍스트', { exact: true }).fill(message);
await composer.locator('.unified-composer').evaluate((element) => {
const data = new DataTransfer();
data.items.add(new File(['ignored'], 'blocked.txt', { type: 'text/plain' }));
element.dispatchEvent(
new DragEvent('drop', { bubbles: true, cancelable: true, dataTransfer: data }),
);
element.dispatchEvent(
new ClipboardEvent('paste', { bubbles: true, cancelable: true, clipboardData: data }),
);
});
assert.equal(await composer.getByLabel('보낼 텍스트', { exact: true }).inputValue(), message);
await sender.screenshot({ path: 'tests/e2e/file-policy-sender.png', fullPage: true });
await composer.getByRole('button', { name: '전송하기', exact: true }).click();
await receiver.getByRole('button', { name: '수락', exact: true }).click();
await receiver.getByRole('dialog', { name: '받은 텍스트', exact: true }).waitFor();
assert.equal(await receiver.getByLabel('받은 텍스트 내용', { exact: true }).innerText(), message);
await receiver.getByRole('button', { name: '받은 텍스트 닫기', exact: true }).click();
await composer.waitFor({ state: 'hidden' });
// Unpausing updates existing clients without a reload and preserves typed text.
await sender
.locator('.peer-card')
.filter({ hasText: '#' + b.device.code })
.click();
await composer.getByLabel('보낼 텍스트', { exact: true }).fill('작성 중인 내용은 유지');
await banFiles(false);
await composer.getByRole('button', { name: '대신 파일 선택하기', exact: true }).waitFor();
assert.equal(
await composer.getByLabel('보낼 텍스트', { exact: true }).inputValue(),
'작성 중인 내용은 유지',
);
await composer.getByRole('button', { name: '전송 화면 닫기', exact: true }).click();
const removed = (
await normal.evaluate(async () => (await (await fetch('/api/vault')).json()).items)
).find((item) => item.file?.name === 'policy-file.txt');
const adminSession = await session(admin);
await call(admin, adminSession.csrfToken, '/admin/files/' + removed.file.id, undefined, 'DELETE');
await admin.getByRole('button', { name: '용량 설정', exact: true }).click();
const capacityForm = admin.locator('.admin-settings');
assert.equal(await capacityForm.locator('input').count(), 0);
assert.match(await capacityForm.innerText(), /로그인 보관함 계정당 한도[\s\S]*2\.1GB/);
await admin.getByRole('button', { name: '수정', exact: true }).click();
const rowInputs = await admin.locator('.admin-settings-grid input').evaluateAll((inputs) =>
inputs.slice(0, 2).map((input) => ({
top: input.getBoundingClientRect().top,
height: input.getBoundingClientRect().height,
})),
);
assert.equal(rowInputs[0].top, rowInputs[1].top);
assert.equal(rowInputs[0].height, rowInputs[1].height);
await admin.screenshot({ path: 'tests/e2e/admin-capacity-edit-desktop.png', fullPage: true });
await admin.getByRole('button', { name: '설정 저장', exact: true }).click();
await admin.getByText('관리 설정을 저장했어요.', { exact: true }).waitFor();
assert.equal(
@@ -293,7 +403,7 @@ try {
assert.doesNotMatch(ownerLabel, /user-|guest-|\[/);
await admin.screenshot({ path: 'tests/e2e/admin-file-account.png', fullPage: true });
await admin.getByRole('button', { name: '관리 기록', exact: true }).click();
await admin.getByText('원본 파일 삭제', { exact: true }).waitFor();
await admin.getByText('원본 파일 삭제', { exact: true }).first().waitFor();
const changedRow = admin
.locator('.admin-audit article')
.filter({ hasText: '동시에 처리할 업로드 수 4 > 3' });
@@ -384,7 +494,7 @@ try {
assert.equal(await mobile.getByRole('menuitem', { name: '관리자', exact: true }).count(), 0);
assert.deepEqual(errors, []);
console.log(
'PASS: verified hello-only menu and server authorization, admin storage/location/IP overview, pause/resume, saved quotas and country/IP rules, delete confirmation/audit, mobile layout and font size, logout privilege removal.',
'PASS: verified hello-only menu and server authorization, admin storage/location/IP overview, file-only ban/live menu updates and clipboard-only transfer, comma-separated GB, aligned capacity inputs, saved quotas and country/IP rules, delete confirmation/audit, mobile layout and font size, logout privilege removal.',
);
} finally {
await browser?.close();