add website tests

This commit is contained in:
2025-12-28 21:02:32 +01:00
parent 6edf12fda8
commit 2f6657f56d
20 changed files with 1868 additions and 97 deletions

View File

@@ -23,7 +23,9 @@ export class AuthApiClient extends BaseApiClient {
/** Get current session */
getSession(): Promise<AuthSessionDTO | null> {
return this.get<AuthSessionDTO | null>('/auth/session');
return this.request<AuthSessionDTO | null>('GET', '/auth/session', undefined, {
allowUnauthenticated: true,
});
}
/** Logout */

View File

@@ -19,7 +19,12 @@ export class BaseApiClient {
this.logger = logger;
}
protected async request<T>(method: string, path: string, data?: object | FormData): Promise<T> {
protected async request<T>(
method: string,
path: string,
data?: object | FormData,
options?: { allowUnauthenticated?: boolean },
): Promise<T> {
this.logger.info(`${method} ${path}`);
const isFormData = typeof FormData !== 'undefined' && data instanceof FormData;
@@ -43,6 +48,15 @@ export class BaseApiClient {
const response = await fetch(`${this.baseUrl}${path}`, config);
if (!response.ok) {
if (
options?.allowUnauthenticated &&
(response.status === 401 || response.status === 403)
) {
// For "auth probe" endpoints (e.g. session/policy checks), 401/403 is an expected state
// in public context and should not be logged as an application error.
return null as T;
}
let errorData: { message?: string } = { message: response.statusText };
try {
errorData = await response.json();

View File

@@ -24,6 +24,20 @@ import type { UpdateLeagueMemberRoleOutputDTO } from '../../types/generated/Upda
import type { RemoveLeagueMemberOutputDTO } from '../../types/generated/RemoveLeagueMemberOutputDTO';
import type { AllLeaguesWithCapacityAndScoringDTO } from '../../types/AllLeaguesWithCapacityAndScoringDTO';
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null;
}
function isRaceDTO(value: unknown): value is RaceDTO {
if (!isRecord(value)) return false;
return typeof value.id === 'string' && typeof value.name === 'string' && typeof value.date === 'string';
}
function parseRaceDTOArray(value: unknown): RaceDTO[] {
if (!Array.isArray(value)) return [];
return value.filter(isRaceDTO);
}
/**
* Leagues API Client
*
@@ -145,8 +159,9 @@ export class LeaguesApiClient extends BaseApiClient {
}
/** Get races for a league */
getRaces(leagueId: string): Promise<{ races: RaceDTO[] }> {
return this.get<{ races: RaceDTO[] }>(`/leagues/${leagueId}/races`);
async getRaces(leagueId: string): Promise<{ races: RaceDTO[] }> {
const response = await this.get<{ races?: unknown }>(`/leagues/${leagueId}/races`);
return { races: parseRaceDTOArray(response?.races) };
}
/** Admin roster: list current members (admin/owner only; actor derived from session) */