The Queues tab at /admin/apps/default/queues was returning "Cannot parse `app_id` with value `default`: UUID parsing failed". 27 admin endpoints across 6 files used strict `Path<AppId>` instead of the canonical `Path<String>` + `resolve_app()` pattern from app_repo.rs:34. Working endpoints (apps, app_members, users_admin) all use the lenient pattern; this commit brings the remaining 6 files into line: - queues_api.rs — 2 handlers - files_api.rs — 2 handlers - secrets_api.rs — 3 handlers - topics_api.rs — 4 handlers - dead_letters_api.rs — 5 handlers - triggers_api.rs — 10 handlers The handler bodies (authz, repo calls) are unchanged; only the path extractor and the per-file `ensure_app_exists` helper (now renamed `resolve_app`) move. Lib tests updated to pass `.to_string()` at the call site (Path now takes String, not AppId). email_inbound_api.rs deliberately stays strict-UUID — it's a public webhook receiver consumed by external providers, not by the slug-based dashboard. Adds Playwright spec `dashboard/tests/e2e/navigation/tabs.spec.ts` covering every per-app tab (queues, files, dead-letters, users, invitations, plus the main page hosting triggers/secrets/topics) with a negative assertion against the "Cannot parse" error text plus a focused regression test for the original queues report. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
81 lines
2.9 KiB
TypeScript
81 lines
2.9 KiB
TypeScript
import { expect } from '@playwright/test';
|
|
import { test } from '../fixtures/ids';
|
|
import { CleanupRegistry } from '../fixtures/cleanup';
|
|
import { adminApi } from '../fixtures/api';
|
|
|
|
// Regression coverage for the slug-vs-UUID bug: every per-app tab
|
|
// (`/admin/apps/[slug]/...`) must accept the URL slug and not surface
|
|
// the "Cannot parse `app_id` with value `<slug>`" error. The original
|
|
// report was the Queues tab, but the bug surface spans six backend
|
|
// files — see AUDIT.md's Phase 1 plan in chore/audit branch.
|
|
//
|
|
// Each tab is hit in isolation against a freshly-created app so the
|
|
// test doesn't depend on the dev-seeded `default` app being present.
|
|
|
|
const PARSE_ERROR = /Cannot parse|UUID parsing failed/i;
|
|
|
|
const cleanup = new CleanupRegistry();
|
|
test.afterEach(async () => {
|
|
await cleanup.run();
|
|
});
|
|
|
|
test.describe('per-app tab navigation accepts slug URLs', () => {
|
|
test('every tab loads without an app_id parse error', async ({ page, uniqueSlug }) => {
|
|
const slug = uniqueSlug('nav');
|
|
const api = await adminApi();
|
|
try {
|
|
const res = await api.post('/api/v1/admin/apps', {
|
|
data: { slug, name: slug }
|
|
});
|
|
expect(res.ok()).toBe(true);
|
|
} finally {
|
|
await api.dispose();
|
|
}
|
|
cleanup.app(slug);
|
|
|
|
const tabs = [
|
|
{ path: `/admin/apps/${slug}`, label: 'main (triggers/secrets/topics)' },
|
|
{ path: `/admin/apps/${slug}/queues`, label: 'queues' },
|
|
{ path: `/admin/apps/${slug}/files`, label: 'files' },
|
|
{ path: `/admin/apps/${slug}/dead-letters`, label: 'dead-letters' },
|
|
{ path: `/admin/apps/${slug}/users`, label: 'app users' },
|
|
{ path: `/admin/apps/${slug}/users/invitations`, label: 'app invitations' }
|
|
];
|
|
|
|
for (const tab of tabs) {
|
|
await page.goto(tab.path);
|
|
// Network must settle so any error banner from the failed
|
|
// API call has rendered before we assert its absence.
|
|
await page.waitForLoadState('networkidle');
|
|
await expect(
|
|
page.getByText(PARSE_ERROR),
|
|
`expected no app_id parse error on ${tab.label} (${tab.path})`
|
|
).toHaveCount(0);
|
|
}
|
|
});
|
|
|
|
test('queues tab specifically — the original bug report', async ({ page, uniqueSlug }) => {
|
|
// The bug surfaced on the queues tab. Pin a focused test so a
|
|
// regression on just that page is easy to spot in CI output.
|
|
const slug = uniqueSlug('queues');
|
|
const api = await adminApi();
|
|
try {
|
|
const res = await api.post('/api/v1/admin/apps', {
|
|
data: { slug, name: slug }
|
|
});
|
|
expect(res.ok()).toBe(true);
|
|
} finally {
|
|
await api.dispose();
|
|
}
|
|
cleanup.app(slug);
|
|
|
|
await page.goto(`/admin/apps/${slug}/queues`);
|
|
await page.waitForLoadState('networkidle');
|
|
await expect(page.getByText(PARSE_ERROR)).toHaveCount(0);
|
|
// Positive assertion: the empty-state copy renders. If the API
|
|
// call fails, this string never appears because the page bails
|
|
// into the error branch.
|
|
await expect(page.getByText('No queues in this app yet', { exact: false })).toBeVisible();
|
|
});
|
|
});
|