- 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
69 lines
2.4 KiB
TypeScript
69 lines
2.4 KiB
TypeScript
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);
|
|
});
|
|
});
|