feat(m10): file sharing with upload, lightbox, permission, tests, e2e
- FileRepository (CRUD, soft-delete, pagination, project isolation) + FileStorageService (local FS save data/uploads/<projectId>/<uuid>.<ext>, MIME allowlist, filename sanitize, unique uuid name, member permission, file_shared notification to other members, file_uploaded activity log, SSE file.uploaded, uploader/admin delete + FS unlink) - APIs: files list/upload(multipart), download (membership), delete - Screens: files page + Uploader(multipart) + FileList(image Lightbox, PDF link, delete), ProjectNav ファイル link - Unit tests (FileRepository, FileStorageService) + e2e file-sharing
This commit is contained in:
68
tests/e2e/file-sharing.spec.ts
Normal file
68
tests/e2e/file-sharing.spec.ts
Normal file
@ -0,0 +1,68 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
function unique(prefix: string): string {
|
||||
return `${prefix}-${Date.now()}-${Math.floor(Math.random() * 1_000_000)}`;
|
||||
}
|
||||
|
||||
// 1x1 透明PNG
|
||||
const PNG_BUFFER = Buffer.from(
|
||||
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=',
|
||||
'base64'
|
||||
);
|
||||
|
||||
async function setupOwner(page: import('@playwright/test').Page) {
|
||||
const email = unique('owner') + '@example.com';
|
||||
await page.goto('/login');
|
||||
await page.getByRole('button', { name: '新規登録はこちら' }).click();
|
||||
await page.getByLabel('表示名').fill('Owner');
|
||||
await page.getByLabel('メールアドレス').fill(email);
|
||||
await page.getByLabel('パスワード').fill('password123');
|
||||
await page.getByRole('button', { name: '登録する' }).click();
|
||||
await expect(page).toHaveURL(/\/dashboard/);
|
||||
await page.getByLabel('プロジェクト名').fill(unique('Proj'));
|
||||
await page.getByRole('button', { name: '新規プロジェクト' }).click();
|
||||
await expect(page).toHaveURL(/\/projects\/\d+$/);
|
||||
return Number(page.url().match(/\/projects\/(\d+)/)![1]);
|
||||
}
|
||||
|
||||
test.describe('file sharing', () => {
|
||||
test('upload, view in lightbox, and delete a file', async ({ page }) => {
|
||||
const projectId = await setupOwner(page);
|
||||
|
||||
// アップロード(multipart)
|
||||
const uploadRes = await page.request.post(
|
||||
`/api/projects/${projectId}/files`,
|
||||
{
|
||||
multipart: {
|
||||
file: {
|
||||
name: 'tiny.png',
|
||||
mimeType: 'image/png',
|
||||
buffer: PNG_BUFFER,
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
expect(uploadRes.ok()).toBeTruthy();
|
||||
const { file } = (await uploadRes.json()) as { file: { id: number } };
|
||||
|
||||
// ファイル一覧に表示
|
||||
await page.goto(`/projects/${projectId}/files`);
|
||||
await expect(page.getByTestId(`file-item-${file.id}`)).toBeVisible();
|
||||
|
||||
// 画像をクリック → Lightbox
|
||||
await page
|
||||
.getByTestId(`file-item-${file.id}`)
|
||||
.getByRole('button')
|
||||
.first()
|
||||
.click();
|
||||
await expect(page.getByTestId('lightbox')).toBeVisible();
|
||||
// Lightboxを閉じる(リロードで確実に解除)
|
||||
await page.reload();
|
||||
|
||||
// 削除(API)
|
||||
const delRes = await page.request.delete(`/api/files/${file.id}`);
|
||||
expect(delRes.ok()).toBeTruthy();
|
||||
await page.reload();
|
||||
await expect(page.getByTestId(`file-item-${file.id}`)).toHaveCount(0);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user