95 lines
3.3 KiB
TypeScript
95 lines
3.3 KiB
TypeScript
import { WebsiteRouteManager, RouteAccess } from './WebsiteRouteManager';
|
|
import { routes } from '../../../apps/website/lib/routing/RouteConfig';
|
|
|
|
/**
|
|
* Expected HTTP status or behavior for a route.
|
|
* - 'ok': 200 OK
|
|
* - 'redirect': 3xx redirect (usually to login)
|
|
* - 'notFoundAllowed': 404 is an acceptable/expected outcome (e.g. for edge cases)
|
|
* - 'errorRoute': The dedicated error pages themselves
|
|
*/
|
|
export type ExpectedStatus = 'ok' | 'redirect' | 'notFoundAllowed' | 'errorRoute';
|
|
|
|
/**
|
|
* RouteContract defines the "Single Source of Truth" for how a website route
|
|
* should behave during SSR and E2E testing.
|
|
*/
|
|
export interface RouteContract {
|
|
/** The fully resolved path (e.g. /leagues/123 instead of /leagues/[id]) */
|
|
path: string;
|
|
/** The required access level for this route */
|
|
accessLevel: RouteAccess;
|
|
/** What we expect when hitting this route unauthenticated */
|
|
expectedStatus: ExpectedStatus;
|
|
/** If expectedStatus is 'redirect', where should it go? (pathname only) */
|
|
expectedRedirectTo?: string;
|
|
/** Strings or Regex that MUST be present in the SSR HTML */
|
|
ssrMustContain?: Array<string | RegExp>;
|
|
/** Strings or Regex that MUST NOT be present in the SSR HTML (e.g. error markers) */
|
|
ssrMustNotContain?: Array<string | RegExp>;
|
|
/** Minimum expected length of the HTML response body */
|
|
minTextLength?: number;
|
|
}
|
|
|
|
const DEFAULT_SSR_MUST_CONTAIN = ['<!DOCTYPE html>', '<body'];
|
|
const DEFAULT_SSR_MUST_NOT_CONTAIN = [
|
|
'__NEXT_ERROR__',
|
|
'Application error: a client-side exception has occurred',
|
|
];
|
|
|
|
/**
|
|
* Generates the full list of route contracts by augmenting the base inventory
|
|
* with expected behaviors and sanity checks.
|
|
*/
|
|
export function getWebsiteRouteContracts(): RouteContract[] {
|
|
const manager = new WebsiteRouteManager();
|
|
const inventory = manager.getWebsiteRouteInventory();
|
|
|
|
// Per-route overrides for special cases where the group-based logic isn't enough
|
|
const overrides: Record<string, Partial<RouteContract>> = {
|
|
[routes.error.notFound]: {
|
|
expectedStatus: 'notFoundAllowed',
|
|
},
|
|
[routes.error.serverError]: {
|
|
expectedStatus: 'errorRoute',
|
|
},
|
|
};
|
|
|
|
return inventory.map((def) => {
|
|
const path = manager.resolvePathTemplate(def.pathTemplate, def.params);
|
|
|
|
// Default augmentation based on access level
|
|
let expectedStatus: ExpectedStatus = 'ok';
|
|
let expectedRedirectTo: string | undefined = undefined;
|
|
|
|
if (def.access !== 'public') {
|
|
expectedStatus = 'redirect';
|
|
// Most protected routes redirect to login when unauthenticated
|
|
expectedRedirectTo = routes.auth.login;
|
|
}
|
|
|
|
// If the inventory explicitly allows 404 (e.g. for non-existent IDs in edge cases)
|
|
if (def.allowNotFound) {
|
|
expectedStatus = 'notFoundAllowed';
|
|
}
|
|
|
|
const contract: RouteContract = {
|
|
path,
|
|
accessLevel: def.access,
|
|
expectedStatus,
|
|
expectedRedirectTo,
|
|
ssrMustContain: [...DEFAULT_SSR_MUST_CONTAIN],
|
|
ssrMustNotContain: [...DEFAULT_SSR_MUST_NOT_CONTAIN],
|
|
minTextLength: 1000, // Reasonable minimum for a Next.js page
|
|
};
|
|
|
|
// Apply per-route overrides (matching by template or resolved path)
|
|
const override = overrides[def.pathTemplate] || overrides[path];
|
|
if (override) {
|
|
Object.assign(contract, override);
|
|
}
|
|
|
|
return contract;
|
|
});
|
|
}
|