Files
TS_Proto/TEST-CATALOG.md
nilesh.pandClaude Opus 4.8 4d3eb18a6c 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>
2026-09-13 11:38:01 +05:30

499 lines
19 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# TravelHub test catalogue
> Generated by `npm run docs:tests` from the test sources.
> Do not edit by hand — regenerate instead.
**277 test cases** across 25 files.
| Suite | Files | Cases | Command |
| ----- | ----: | ----: | ------- |
| [Go](#go-backend) | 6 | 98 | `npm run test:go` |
| [Angular](#angular-frontend) | 12 | 114 | `npm run test:web` |
| [Playwright](#playwright-end-to-end) | 7 | 65 | `npm run test:e2e` |
| **Total** | **25** | **277** | `npm test` |
### Why the runners report different numbers
A *case* below is one thing being tested. The runners count differently, so
both figures are given rather than one being quietly preferred:
- **Go** reports 74 test functions plus each table-driven subtest
separately, so `go test -v` prints more `PASS` lines than there are cases
here. A few tables build their subtest names at runtime and cannot be
listed statically; those appear as a single case.
- **Playwright** re-runs `auth` and `responsive` (17 tests) under a second,
mobile viewport, so a full run executes 82 tests from 65 declared.
- **Angular** counts exactly as listed.
Running a single file:
```bash
cd backend && go test ./internal/repository/ -run TestCreateBookingSellsSeats -v
cd frontend && npx ng test --no-watch # all specs
cd e2e && npx playwright test tests/booking.spec.ts
```
---
## Go (backend)
74 test functions covering 98 cases. These run against a real
PostgreSQL, with a fresh tenant created and dropped per test.
### `backend/internal/config/config_test.go`
7 tests, 7 cases.
- **Load defaults** — `TestLoadDefaults`
- **Load requires a password** — `TestLoadRequiresAPassword`
- **Load escapes awkward passwords** — `TestLoadEscapesAwkwardPasswords`
- **Database url wins over parts** — `TestDatabaseURLWinsOverParts`
- **Load reads dot env** — `TestLoadReadsDotEnv`
- **Environment beats dot env** — `TestEnvironmentBeatsDotEnv`
- **Load finds dot env in a parent directory** — `TestLoadFindsDotEnvInAParentDirectory`
### `backend/internal/handlers/handlers_test.go`
22 tests, 33 cases.
- **Health needs no credentials** — `TestHealthNeedsNoCredentials`
- **Tenant list is public but thin** — `TestTenantListIsPublicButThin`
- **Protected routes reject anonymous** — `TestProtectedRoutesRejectAnonymous`
- **Session describes the caller** — `TestSessionDescribesTheCaller`
- **Session never returns the password hash** — `TestSessionNeverReturnsThePasswordHash`
- **Search flights returns a page** — `TestSearchFlightsReturnsAPage`
- **Search flights validates input** — `TestSearchFlightsValidatesInput`
- bad date
- bad cabin
- good cabin
- absurd limit is clamped
- negative offset falls back
- non-numeric passengers falls back
- **Get flight rejects a malformed id** — `TestGetFlightRejectsAMalformedID`
- **Get flight across tenants is four oh four** — `TestGetFlightAcrossTenantsIsFourOhFour`
- **Create booking returns created** — `TestCreateBookingReturnsCreated`
- **Create booking validation** — `TestCreateBookingValidation`
- missing flight id
- missing contact email
- malformed contact email
- no passengers
- blank passenger name
- more than nine passengers
- **Create booking rejects unknown fields** — `TestCreateBookingRejectsUnknownFields`
- **Create booking rejects malformed json** — `TestCreateBookingRejectsMalformedJSON`
- **Create booking over capacity is conflict** — `TestCreateBookingOverCapacityIsConflict`
- **Create booking on another tenants flight is four oh four** — `TestCreateBookingOnAnotherTenantsFlightIsFourOhFour`
- **Booking lifecycle over http** — `TestBookingLifecycleOverHTTP`
- **List bookings rejects an unknown status** — `TestListBookingsRejectsAnUnknownStatus`
- **Bookings are invisible to other tenants** — `TestBookingsAreInvisibleToOtherTenants`
- **Dashboard summarises the tenant** — `TestDashboardSummarisesTheTenant`
- **Reference lists are scoped to the tenant network** — `TestReferenceListsAreScopedToTheTenantNetwork`
- **Unknown route and wrong method** — `TestUnknownRouteAndWrongMethod`
- unknown path
- wrong method
- **Every json response carries its content type** — `TestEveryJSONResponseCarriesItsContentType`
### `backend/internal/middleware/middleware_test.go`
10 tests, 20 cases.
- **Basic auth accepts valid credentials** — `TestBasicAuthAcceptsValidCredentials`
- **Basic auth is case insensitive on email** — `TestBasicAuthIsCaseInsensitiveOnEmail`
- **Basic auth rejections** — `TestBasicAuthRejections`
- no credentials at all
- wrong password
- unknown user
- unknown tenant
- valid credentials against another tenant
- missing tenant header
- malformed authorization header
- **Basic auth refuses suspended tenant** — `TestBasicAuthRefusesSuspendedTenant`
- **Unauthorized does not trigger the browser dialog** — `TestUnauthorizedDoesNotTriggerTheBrowserDialog`
- **Tenant falls back to subdomain** — `TestTenantFallsBackToSubdomain`
- **Tenant header beats subdomain** — `TestTenantHeaderBeatsSubdomain`
- **Require admin** — `TestRequireAdmin`
- admin passes
- agent is refused
- **Cors** — `TestCORS`
- echoes an allowed origin
- tolerates a trailing slash in configuration
- ignores an origin that is not on the list
- answers preflight without reaching the handler
- **Recoverer turns panic into five hundred** — `TestRecovererTurnsPanicIntoFiveHundred`
### `backend/internal/repository/booking_repo_test.go`
15 tests, 15 cases.
- **Create booking sells seats** — `TestCreateBookingSellsSeats`
- **Create booking stores passenger detail** — `TestCreateBookingStoresPassengerDetail`
- **Create booking rejects blank date of birth** — `TestCreateBookingRejectsBlankDateOfBirth`
- **Create booking refuses more seats than remain** — `TestCreateBookingRefusesMoreSeatsThanRemain`
- **Create booking requires a passenger** — `TestCreateBookingRequiresAPassenger`
- **Create booking cannot reach another tenants flight** — `TestCreateBookingCannotReachAnotherTenantsFlight`
- **Create booking refuses cancelled flight** — `TestCreateBookingRefusesCancelledFlight`
- **Create booking does not oversell under concurrency** — `TestCreateBookingDoesNotOversellUnderConcurrency`
- **Cancel booking returns seats** — `TestCancelBookingReturnsSeats`
- **Cancel booking is not repeatable** — `TestCancelBookingIsNotRepeatable`
- **Cancel booking across tenants is not found** — `TestCancelBookingAcrossTenantsIsNotFound`
- **List bookings isolates tenants and filters status** — `TestListBookingsIsolatesTenantsAndFiltersStatus`
- **Booking by id across tenants is not found** — `TestBookingByIDAcrossTenantsIsNotFound`
- **Booking references are unique within a tenant** — `TestBookingReferencesAreUniqueWithinATenant`
- **Dashboard stats count only this tenant** — `TestDashboardStatsCountOnlyThisTenant`
### `backend/internal/repository/flight_repo_test.go`
13 tests, 16 cases.
- **Search flights filters by route** — `TestSearchFlightsFiltersByRoute`
- **Search flights lowercase iata still matches** — `TestSearchFlightsLowercaseIATAStillMatches`
- **Search flights isolates tenants** — `TestSearchFlightsIsolatesTenants`
- **Flight by id across tenants is not found** — `TestFlightByIDAcrossTenantsIsNotFound`
- **Flight by id unknown is not found** — `TestFlightByIDUnknownIsNotFound`
- **Search flights hides departed and cancelled** — `TestSearchFlightsHidesDepartedAndCancelled`
- **Search flights respects party size** — `TestSearchFlightsRespectsPartySize`
- **Search flights filters by cabin** — `TestSearchFlightsFiltersByCabin`
- **Search flights filters by departure day** — `TestSearchFlightsFiltersByDepartureDay`
- **Search flights sorting** — `TestSearchFlightsSorting`
- price
- duration
- departure
- nonsense-falls-back-to-price
- **Search flights paginates** — `TestSearchFlightsPaginates`
- **Search flights computes duration** — `TestSearchFlightsComputesDuration`
- **List airports covers only this tenants network** — `TestListAirportsCoversOnlyThisTenantsNetwork`
### `backend/internal/repository/identity_repo_test.go`
7 tests, 7 cases.
- **Tenant by slug** — `TestTenantBySlug`
- **Tenant by slug unknown is not found** — `TestTenantBySlugUnknownIsNotFound`
- **Tenant by slug suspended is not found** — `TestTenantBySlugSuspendedIsNotFound`
- **User by email is case insensitive** — `TestUserByEmailIsCaseInsensitive`
- **User by email is scoped to tenant** — `TestUserByEmailIsScopedToTenant`
- **Same email can exist in two tenants** — `TestSameEmailCanExistInTwoTenants`
- **List tenants excludes suspended** — `TestListTenantsExcludesSuspended`
---
## Angular (frontend)
114 tests. Vitest with jsdom, via `ng test`.
### `frontend/src/app/core/api.service.spec.ts`
10 tests.
- **ApiService › searchFlights**
- sends only the filters that were actually set
- drops empty strings so a cleared filter does not narrow the search
- keeps a zero offset, which is a real value rather than an absent one
- **ApiService**
- requests one flight by id
- **ApiService › listBookings**
- defaults to the first page of every status
- passes a status filter through when given
- **ApiService**
- posts a booking
- cancels a booking with an empty body
- reads the dashboard
- reads the tenant reference lists
### `frontend/src/app/core/auth.guard.spec.ts`
5 tests.
- **authGuard**
- lets a signed-in user through
- redirects to sign-in and remembers where the user was heading
- omits returnTo when the destination was the root
- **guestGuard**
- lets a signed-out visitor reach the sign-in screen
- sends an already signed-in user to the dashboard
### `frontend/src/app/core/auth.interceptor.spec.ts`
8 tests.
- **authInterceptor**
- stamps API calls with the credentials and tenant
- leaves non-API requests alone
- does not overwrite credentials a caller set explicitly
- adds nothing when signed out
- signs out and redirects when stored credentials go stale
- does not sign out on a 401 from an unstamped request
- leaves the session intact for other error statuses
- passes the error through to the caller
### `frontend/src/app/core/auth.service.spec.ts`
12 tests.
- **AuthService**
- starts signed out with nothing in storage
- fetches the public tenant list without credentials
- verifies credentials against the chosen tenant before storing them
- leaves no trace when the server rejects the credentials
- reports the admin role
- revalidates stored credentials on restore
- discards stored credentials the server no longer accepts
- restores without a second round trip once the session is known
- short-circuits restore when there are no credentials
- clears everything on sign out
- treats unreadable storage as signed out rather than crashing
- ignores a stored value of the wrong shape
### `frontend/src/app/features/bookings/booking-detail.spec.ts`
8 tests.
- **BookingDetailPage**
- loads the booking with its travellers
- shows the confirmation banner only when arriving from checkout
- asks for confirmation before cancelling
- lets the user back out
- cancels on confirmation and reflects the new status
- reports a cancel that failed and leaves the booking alone
- offers no cancel control on an already cancelled booking
- shows a not-found state for a booking in another agency
### `frontend/src/app/features/bookings/bookings.spec.ts`
6 tests.
- **BookingsPage**
- loads every booking on open
- filters by status and starts again from page one
- pages through a long list
- shows an empty state with a way forward
- surfaces a load failure
- renders a row with the reference, route and status
### `frontend/src/app/features/checkout/checkout.spec.ts`
16 tests.
- **CheckoutPage**
- loads the flight and prefills the agent as the contact
- opens with one traveller form by default
- honours the traveller count carried over from search
- clamps a silly traveller count to what the cabin allows
- never asks for more travellers than there are seats
- ignores a non-numeric traveller count
- adds and removes travellers, repricing as it goes
- will not go below one traveller or above nine
- warns when the party outgrows the remaining seats
- blocks submission until every required field is filled
- submits the booking and moves to the confirmation
- explains a seat sold out from under the form
- explains a flight that disappeared
- relays the server`s own validation message
- shows a not-found state when the flight cannot be loaded
- renders cabin codes as words
### `frontend/src/app/features/dashboard/dashboard.spec.ts`
7 tests.
- **DashboardPage**
- greets the user by first name and names the agency
- renders the headline figures
- scales the route bars against the biggest earner
- keeps a tiny route visible rather than drawing nothing
- copes with no sales at all
- treats a null topRoutes as an empty list
- surfaces a failure to load
### `frontend/src/app/features/login/login.spec.ts`
14 tests.
- **LoginPage**
- loads the agency list on open
- preselects the only agency in a single-tenant deployment
- does not preselect when there is a real choice to make
- explains a backend that is not running
- fills the form from a demo chip
- refuses to submit an incomplete form
- rejects a malformed email before calling the server
- signs in and moves to the dashboard
- returns the user to the page they were trying to reach
- reports wrong credentials in the agency`s terms
- reports an unreachable API distinctly from a bad password
- falls back to a generic message for unexpected failures
- renders one option per agency plus the placeholder
- shows four demo account shortcuts
### `frontend/src/app/features/search/search.spec.ts`
16 tests.
- **SearchPage**
- searches once on open and fills the airport pickers
- asks for the first page with the default page size
- passes the filter panel through to the API
- returns to page one when the filters change
- clears the filters on reset
- **SearchPage › pagination**
- describes the visible slice
- moves forward and back
- never steps past the ends
- **SearchPage**
- reports an empty result set rather than an error
- surfaces a failed search
- carries the traveller count into checkout
- **SearchPage › presentation helpers**
- marks an arrival on a later calendar day
- shows no offset for a same-day arrival
- flags scarce inventory as urgent
- renders cabin codes as words
- **SearchPage**
- renders a result card with the fare and route
### `frontend/src/app/layout/shell.spec.ts`
8 tests.
- **Shell**
- keeps the agency permanently on screen
- shows who is signed in
- **Shell › initials**
- takes the first letter of the first two names
- copes with a single name
- copes with extra spacing
- falls back when there is no name yet
- **Shell**
- offers the three main destinations
- signs out and returns to the sign-in screen
### `frontend/src/app/shared/duration.pipe.spec.ts`
4 tests.
- **DurationPipe**
- renders hours and zero-padded minutes
- drops the hour part under an hour
- handles long-haul durations past a day
- renders a placeholder for missing or nonsensical values
---
## Playwright (end to end)
65 tests, each run against a real browser and the real stack.
### `e2e/tests/auth.spec.ts`
13 tests.
- **Sign in**
- offers every active agency in the picker
- a demo shortcut fills the whole form
- signs an agent into their own agency
- signs an admin in and shows the admin role
- rejects a wrong password
- rejects valid credentials aimed at the wrong agency
- will not submit without an agency
- rejects a malformed email before contacting the server
- **Session**
- survives a page reload
- sends an anonymous visitor to sign-in and back again
- signing out clears the session for good
- an already signed-in user is moved off the sign-in screen
- stale stored credentials are discarded
### `e2e/tests/booking.spec.ts`
7 tests.
- **Booking a flight**
- carries the fare and traveller count into checkout
- reprices as travellers are added and removed
- refuses to submit with missing traveller names
- books, confirms, lists and cancels, returning the seats
- a cancel can be backed out of
- the bookings list filters by status
- a booking opened directly shows no stale confirmation banner
### `e2e/tests/dashboard.spec.ts`
6 tests.
- **Dashboard**
- greets the agent and names the agency
- shows the four headline figures
- lists recent bookings and best routes from the seed data
- a new sale moves the figures, and cancelling moves them back
- a recent booking row opens the booking
- the new booking shortcut goes to search
### `e2e/tests/responsive.spec.ts`
4 tests.
- **Layout**
- the sign-in screen fits without sideways scrolling
- the console screens fit at phone width
- wide tables scroll inside their own container
- the sidebar collapses into a top bar on narrow screens
### `e2e/tests/search.spec.ts`
11 tests.
- **Flight search**
- opens with the agency inventory already listed
- offers only airports this agency flies
- narrows results to a route
- a route with no inventory shows an empty state
- resets back to the full list
- sorts by lowest fare
- sorts by shortest trip
- filters by cabin
- hides flights without room for the whole party
- pages through results without repeating them
- a result card carries everything needed to choose
### `e2e/tests/simulation.spec.ts`
19 tests.
- **When the API fails**
- search shows an error instead of an empty page
- the dashboard explains itself rather than showing zeroes
- the bookings list reports a failure
- a missing backend is named on the sign-in screen
- going offline mid-session surfaces a failure, not a hang
- **When the API is slow**
- search shows skeletons while it waits
- the sign-in button reports that it is working
- the dashboard shows skeleton tiles before its figures
- **When the session expires**
- a 401 mid-session returns the agent to sign-in
- **Edge cases in the data**
- an agency with no inventory sees an empty state, not an error
- a brand new agency sees zeroes without a broken dashboard
- the last seat is flagged as urgent
- a seat sold during checkout is explained clearly
- a party larger than the cabin is blocked before submitting
- a huge result set still paginates sensibly
- **Awkward content**
- very long names do not break the layout or escape the page
- markup in data is rendered as text, never as HTML
- **Keyboard operation**
- the sign-in form can be completed without a mouse
- every interactive control on search is reachable by tab
### `e2e/tests/tenant-isolation.spec.ts`
5 tests.
- **Tenant isolation**
- the same route is priced per agency
- the sidebar always names the agency you are working in
- one agency never sees another agency booking
- revenue figures are per agency
- a deep link to another agency flight is refused