Initial commit: TravelHub multi-tenant flight booking platform
Angular + Go + PostgreSQL sample with strict per-tenant isolation. - Backend: Go API (basic auth, tenant-scoped repository layer, pgx pool) - Frontend: Angular 22 SPA (search, checkout, bookings, dashboard) - Database: schema, seed data and demo bookings migrations - Tests: 311 across Go (integration), Angular (unit) and Playwright (e2e), plus a generated evidence report with PDF export - Security: whitebox VAPT report under security/ Secrets (.env, tokens, DB credentials) are git-ignored. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
import { expect, Locator, Page } from '@playwright/test';
|
||||
|
||||
/** The bookings list. */
|
||||
export class BookingsPage {
|
||||
readonly rows: Locator;
|
||||
readonly emptyState: Locator;
|
||||
readonly filterAll: Locator;
|
||||
readonly filterConfirmed: Locator;
|
||||
readonly filterCancelled: Locator;
|
||||
|
||||
constructor(private readonly page: Page) {
|
||||
this.rows = page.locator('table.data tbody tr');
|
||||
this.emptyState = page.locator('.empty');
|
||||
this.filterAll = page.locator('.filter', { hasText: 'All' });
|
||||
this.filterConfirmed = page.locator('.filter', { hasText: 'Confirmed' });
|
||||
this.filterCancelled = page.locator('.filter', { hasText: 'Cancelled' });
|
||||
}
|
||||
|
||||
async goto(): Promise<void> {
|
||||
await this.page.goto('/bookings');
|
||||
await this.settled();
|
||||
}
|
||||
|
||||
/** Waits for either a populated table or the empty state. */
|
||||
async settled(): Promise<void> {
|
||||
await expect(this.page.locator('.filters')).toBeVisible();
|
||||
await expect(this.page.locator('.skeleton')).toHaveCount(0);
|
||||
}
|
||||
|
||||
rowFor(reference: string): Locator {
|
||||
return this.rows.filter({ hasText: reference });
|
||||
}
|
||||
|
||||
async open(reference: string): Promise<void> {
|
||||
await this.rowFor(reference).getByRole('link', { name: 'View' }).click();
|
||||
await this.page.waitForURL(/\/bookings\/[0-9a-f-]+/);
|
||||
}
|
||||
|
||||
async filterBy(status: 'all' | 'confirmed' | 'cancelled'): Promise<void> {
|
||||
const button = {
|
||||
all: this.filterAll,
|
||||
confirmed: this.filterConfirmed,
|
||||
cancelled: this.filterCancelled,
|
||||
}[status];
|
||||
await button.click();
|
||||
await this.settled();
|
||||
}
|
||||
}
|
||||
|
||||
/** A single booking, with its cancel flow. */
|
||||
export class BookingDetailPage {
|
||||
readonly confirmationBanner: Locator;
|
||||
readonly reference: Locator;
|
||||
readonly statusBadge: Locator;
|
||||
readonly travellerRows: Locator;
|
||||
readonly totalPaid: Locator;
|
||||
readonly cancelButton: Locator;
|
||||
readonly confirmCancelButton: Locator;
|
||||
readonly keepButton: Locator;
|
||||
readonly cancelledNotice: Locator;
|
||||
|
||||
constructor(page: Page) {
|
||||
this.confirmationBanner = page.locator('.confirmed-banner');
|
||||
this.reference = page.locator('.sum-list div', { hasText: 'Reference' }).locator('dd');
|
||||
this.statusBadge = page.locator('.head-actions .badge');
|
||||
this.travellerRows = page.locator('table.data tbody tr');
|
||||
this.totalPaid = page.locator('.total-row strong');
|
||||
this.cancelButton = page.getByRole('button', { name: 'Cancel booking' });
|
||||
this.confirmCancelButton = page.getByRole('button', { name: /Yes, cancel booking|Cancelling/ });
|
||||
this.keepButton = page.getByRole('button', { name: 'Keep it' });
|
||||
this.cancelledNotice = page.locator('.alert-info');
|
||||
}
|
||||
|
||||
async bookingReference(): Promise<string> {
|
||||
return (await this.reference.innerText()).trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks through the two-step cancel confirmation.
|
||||
*
|
||||
* The caller must name the booking it means to void. Cancelling is
|
||||
* destructive and these tests share a database with seeded demo data, so the
|
||||
* page object refuses to act on whatever booking happens to be on screen
|
||||
* after a mis-step earlier in the test.
|
||||
*/
|
||||
async cancel(expectedReference: string): Promise<void> {
|
||||
await expect(
|
||||
this.reference,
|
||||
`refusing to cancel: expected booking ${expectedReference} on screen`,
|
||||
).toHaveText(expectedReference);
|
||||
|
||||
await this.cancelButton.click();
|
||||
await this.confirmCancelButton.click();
|
||||
await expect(this.cancelledNotice).toBeVisible();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user