mirror of
https://github.com/Sea-Haven-Industries/proposal-system.git
synced 2026-09-30 21:43:14 +00:00
115 lines
3.8 KiB
TypeScript
115 lines
3.8 KiB
TypeScript
// Browser smoke: dev-login → dashboard shell → proposal list (Phase 5, web CI).
|
|
// The API is fully mocked via route interception so the smoke runs in pure CI
|
|
// with no backend — it exercises the login flow, auth context, protected
|
|
// routing, the app shell, and list rendering end-to-end in a real browser.
|
|
import { test, expect, type Page } from '@playwright/test';
|
|
|
|
function fakeJwt(): string {
|
|
const b64 = (o: object) =>
|
|
Buffer.from(JSON.stringify(o))
|
|
.toString('base64')
|
|
.replace(/\+/g, '-')
|
|
.replace(/\//g, '_')
|
|
.replace(/=+$/, '');
|
|
return `${b64({ alg: 'HS256' })}.${b64({
|
|
sub: 'e2e-user',
|
|
exp: Math.floor(Date.now() / 1000) + 3600,
|
|
})}.e2e-signature`;
|
|
}
|
|
|
|
const authUser = {
|
|
id: 'e2e-user',
|
|
email: 'adam@seahavenind.com',
|
|
displayName: 'Adam Moussa',
|
|
role: 'SysAdmin',
|
|
token: fakeJwt(),
|
|
};
|
|
|
|
const proposals = [
|
|
{
|
|
id: 'p-1',
|
|
proposalNumber: 'PROP-2026-001',
|
|
customerName: 'Harbor Point Marina',
|
|
workOrderNumber: 'WO-1001',
|
|
serviceCategory: 'HVAC',
|
|
priority: 'Standard',
|
|
status: 'InReview',
|
|
totalBidAmount: 12500,
|
|
submittedAt: '2026-07-01T12:00:00Z',
|
|
submittedByName: 'Adam Moussa',
|
|
assignedAdminName: null,
|
|
rowVersion: 'AAAAAAAAAAE=',
|
|
},
|
|
{
|
|
id: 'p-2',
|
|
proposalNumber: 'PROP-2026-002',
|
|
customerName: 'Seaside Property Group',
|
|
workOrderNumber: 'WO-1002',
|
|
serviceCategory: 'Electrical',
|
|
priority: 'Urgent',
|
|
status: 'Approved',
|
|
totalBidAmount: 4300,
|
|
submittedAt: '2026-07-05T09:30:00Z',
|
|
submittedByName: 'Adam Moussa',
|
|
assignedAdminName: 'Sarah Chen',
|
|
rowVersion: 'AAAAAAAAAAI=',
|
|
},
|
|
];
|
|
|
|
async function mockApi(page: Page) {
|
|
// Match on pathname, not a '**/api/**' glob: vite dev serves app modules from
|
|
// paths like /src/lib/api/client.ts, which such a glob would swallow.
|
|
// Registered first = matched last (Playwright checks routes newest-first):
|
|
// generic fallback so nothing the shell fetches hangs or hard-errors.
|
|
await page.route(
|
|
(url) => url.pathname.startsWith('/api/'),
|
|
(route) => route.fulfill({ json: {} }),
|
|
);
|
|
|
|
await page.route(
|
|
(url) => url.pathname === '/api/auth/dev-login',
|
|
(route) => route.fulfill({ json: authUser }),
|
|
);
|
|
await page.route(
|
|
(url) => url.pathname === '/api/proposals/stats',
|
|
(route) =>
|
|
route.fulfill({ json: { totalCount: 2, inReviewCount: 1, approvedCount: 1, sentCount: 0 } }),
|
|
);
|
|
await page.route(
|
|
(url) => url.pathname === '/api/proposals' && url.searchParams.has('page'),
|
|
(route) =>
|
|
route.fulfill({
|
|
json: { items: proposals, totalCount: proposals.length, page: 1, pageSize: 12 },
|
|
}),
|
|
);
|
|
}
|
|
|
|
test('dev-login lands on the dashboard and the proposal list renders', async ({ page }) => {
|
|
await mockApi(page);
|
|
|
|
await page.goto('/');
|
|
await expect(page).toHaveURL(/\/login$/);
|
|
await expect(page.getByText('Welcome back')).toBeVisible();
|
|
|
|
await page.getByRole('button', { name: /SysAdmin/ }).click();
|
|
|
|
// Authenticated shell: topbar user chip + dashboard content.
|
|
await expect(page).toHaveURL('/');
|
|
await expect(page.getByText('Adam Moussa').first()).toBeVisible();
|
|
await expect(page.getByText('Recent Proposals')).toBeVisible();
|
|
|
|
// Proposal list via sidebar nav.
|
|
await page.getByText('My Proposals', { exact: true }).click();
|
|
await expect(page).toHaveURL(/\/proposals$/);
|
|
await expect(page.getByRole('heading', { name: 'My Proposals' })).toBeVisible();
|
|
await expect(page.getByText('Harbor Point Marina')).toBeVisible();
|
|
await expect(page.getByText('Seaside Property Group')).toBeVisible();
|
|
});
|
|
|
|
test('unauthenticated visits are bounced to the login card', async ({ page }) => {
|
|
await mockApi(page);
|
|
|
|
await page.goto('/proposals');
|
|
await expect(page).toHaveURL(/\/login$/);
|
|
await expect(page.getByText('Welcome back')).toBeVisible();
|
|
});
|