3388895912
* Add dev/demo login selector for quick user switching When AUTH_DISABLED=true, the app now shows a login selector page that lists staff members and clients from the database. Selecting a user sets a localStorage-based session and sends X-Dev-User-Id header on all API requests. A persistent bottom bar shows the active persona with a "Switch user" link. - API: /api/dev/config (public) and /api/dev/users (auth-disabled only) - API: auth middleware reads X-Dev-User-Id header when auth is disabled - Frontend: DevLoginSelector page, DevSessionIndicator bar - Frontend: fetch interceptor injects X-Dev-User-Id on /api/* calls - Tests: 7 passing (5 nav + 2 dev login) Closes #60 Co-Authored-By: Paperclip <noreply@paperclip.ing> * fix(e2e): seed dev user in localStorage to prevent login redirect E2E tests were failing because the dev login selector redirects to /login when AUTH_DISABLED=true and no dev user is in localStorage. Added a shared Playwright fixture that pre-seeds localStorage with a default dev user before each test. Also rebased onto latest main to resolve merge conflict in App.test.tsx. Co-Authored-By: Paperclip <noreply@paperclip.ing> * fix(e2e): mock /api/dev/config to bypass auth redirect in tests The fixture now also mocks /api/dev/config to return authDisabled: false, preventing the app from entering the redirect flow during E2E tests. Previously only seeded localStorage, but the async config fetch from the real Docker API was still triggering the redirect check. Co-Authored-By: Paperclip <noreply@paperclip.ing> --------- Co-authored-by: Groom Book CTO <cto@groombook.app> Co-authored-by: Paperclip <noreply@paperclip.ing>
60 lines
1.6 KiB
TypeScript
60 lines
1.6 KiB
TypeScript
import { test, expect } from "./fixtures.js";
|
|
|
|
/**
|
|
* Client management E2E tests.
|
|
*
|
|
* API calls are mocked so tests run without a live backend.
|
|
*/
|
|
|
|
const MOCK_CLIENTS = [
|
|
{
|
|
id: "client-1",
|
|
name: "Alice Johnson",
|
|
email: "alice@example.com",
|
|
phone: "555-0101",
|
|
address: null,
|
|
notes: null,
|
|
createdAt: "2026-01-01T00:00:00.000Z",
|
|
updatedAt: "2026-01-01T00:00:00.000Z",
|
|
},
|
|
{
|
|
id: "client-2",
|
|
name: "Bob Williams",
|
|
email: "bob@example.com",
|
|
phone: null,
|
|
address: null,
|
|
notes: null,
|
|
createdAt: "2026-01-02T00:00:00.000Z",
|
|
updatedAt: "2026-01-02T00:00:00.000Z",
|
|
},
|
|
];
|
|
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.route("/api/clients**", (route) =>
|
|
route.fulfill({ json: MOCK_CLIENTS })
|
|
);
|
|
// Pets loaded when a client is selected
|
|
await page.route("/api/pets**", (route) =>
|
|
route.fulfill({ json: [] })
|
|
);
|
|
});
|
|
|
|
test("clients page shows client list", async ({ page }) => {
|
|
await page.goto("/admin/clients");
|
|
await expect(page.getByText("Alice Johnson")).toBeVisible();
|
|
await expect(page.getByText("Bob Williams")).toBeVisible();
|
|
});
|
|
|
|
test("clients page shows search input", async ({ page }) => {
|
|
await page.goto("/admin/clients");
|
|
await expect(page.getByPlaceholder(/search/i)).toBeVisible();
|
|
});
|
|
|
|
test("clicking a client shows their details", async ({ page }) => {
|
|
await page.goto("/admin/clients");
|
|
await expect(page.getByText("Alice Johnson")).toBeVisible();
|
|
await page.getByText("Alice Johnson").click();
|
|
// Email appears in both the list row and the detail panel once selected
|
|
await expect(page.getByText("alice@example.com")).toHaveCount(2);
|
|
});
|