CI / build + unit + e2e (pull_request) Successful in 3m47s
- ADMIN button in the mission-control header opens a table picker + row grid with add/edit/delete (components/admin-panel.tsx), deliberately unauthenticated by explicit request
- /api/admin/tables and /api/admin/tables/{name}/rows driven off a name→table registry built from lib/db/schema.ts (lib/db/admin.ts) — no per-table code; 404 unknown table, 400 malformed payload, 409 constraint violation
- OpenAPI spec updated for all four admin operations + Admin* schemas
- unit: lib/db/admin.test.ts (registry, CRUD round-trips, composite PKs, error mapping); e2e: read-only admin.spec.ts
42 lines
2.1 KiB
TypeScript
42 lines
2.1 KiB
TypeScript
import { expect, test } from '@playwright/test';
|
|
|
|
// #38 — ADMIN DB CRUD panel. READ-ONLY on purpose: specs share the CI DB across retries, so no
|
|
// insert/update/delete here (the mutation endpoints are covered by unit tests in lib/db/admin.test.ts).
|
|
|
|
interface AdminColumnDto { name: string; type: string; pk: number }
|
|
interface AdminTableDto { name: string; columns: AdminColumnDto[]; count: number }
|
|
interface AdminRowsDto { rows: Record<string, unknown>[]; total: number }
|
|
|
|
test('ADMIN button opens the panel and lists every table (#38)', async ({ page }) => {
|
|
await page.goto('/');
|
|
// The ADMIN button sits in the header next to the SECTOR SCAN badge.
|
|
await page.getByRole('button', { name: 'ADMIN' }).click();
|
|
|
|
await expect(page.getByText('ADMIN // DB CRUD')).toBeVisible();
|
|
// Cross-check against the API: every table it lists must render in the panel (and vice versa).
|
|
const tables = (await (await page.request.get('/api/admin/tables')).json()) as AdminTableDto[];
|
|
expect(tables.length).toBeGreaterThanOrEqual(10);
|
|
for (const t of tables) {
|
|
await expect(page.getByRole('button', { name: new RegExp(`^${t.name}\\b`) })).toBeVisible();
|
|
}
|
|
});
|
|
|
|
test('opening a table renders its rows from the API (#38)', async ({ page }) => {
|
|
await page.goto('/');
|
|
await page.getByRole('button', { name: 'ADMIN' }).click();
|
|
|
|
// saves always has exactly one row (single-player by design) — deterministic across retries.
|
|
const api = (await (await page.request.get('/api/admin/tables/saves/rows')).json()) as AdminRowsDto;
|
|
expect(api.total).toBeGreaterThan(0);
|
|
|
|
await page.getByRole('button', { name: /^saves\b/ }).click();
|
|
// Column headers come from the API's column list.
|
|
for (const col of api.rows[0] ? Object.keys(api.rows[0]) : []) {
|
|
await expect(page.locator('table th', { hasText: col })).toBeVisible();
|
|
}
|
|
// Row count matches what the API reports, and a cell shows real data from it.
|
|
const body = page.locator('table tbody tr');
|
|
await expect(body).toHaveCount(api.rows.length);
|
|
await expect(page.locator('table tbody').getByText(String(api.rows[0].seed))).toBeVisible();
|
|
});
|