# 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