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>
19 KiB
TravelHub test catalogue
Generated by
npm run docs:testsfrom the test sources. Do not edit by hand — regenerate instead.
277 test cases across 25 files.
| Suite | Files | Cases | Command |
|---|---|---|---|
| Go | 6 | 98 | npm run test:go |
| Angular | 12 | 114 | npm run test:web |
| Playwright | 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 -vprints morePASSlines 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
authandresponsive(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:
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