Files
gridpilot.gg/testing/mock-api-server.cjs
2026-01-03 02:42:47 +01:00

1192 lines
35 KiB
JavaScript

const http = require('http');
const PORT = Number(process.env.PORT || 3000);
const baseCors = {
'Access-Control-Allow-Credentials': 'true',
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Allow-Methods': 'GET,POST,PUT,PATCH,DELETE,OPTIONS',
};
function nowIso() {
return new Date().toISOString();
}
function parseCookies(cookieHeader) {
if (!cookieHeader) return {};
const out = {};
const parts = String(cookieHeader).split(';');
for (const part of parts) {
const [rawKey, ...rest] = part.trim().split('=');
if (!rawKey) continue;
const rawValue = rest.join('=');
out[rawKey] = decodeURIComponent(rawValue || '');
}
return out;
}
function getDemoMode(req) {
const cookies = parseCookies(req.headers.cookie);
const raw = cookies.gridpilot_demo_mode || 'none';
if (raw === 'admin' || raw === 'driver' || raw === 'sponsor' || raw === 'none') return raw;
return 'none';
}
function getFaultMode(req) {
const cookies = parseCookies(req.headers.cookie);
const raw = cookies.gridpilot_fault_mode || '';
if (raw === 'null-array' || raw === 'missing-field' || raw === 'invalid-date') return raw;
return null;
}
function getSessionDriftMode(req) {
const cookies = parseCookies(req.headers.cookie);
const raw = cookies.gridpilot_session_drift || '';
if (raw === 'invalid-cookie' || raw === 'expired' || raw === 'missing-sponsor-id') return raw;
return null;
}
function sendJson(res, code, obj) {
res.statusCode = code;
res.setHeader('content-type', 'application/json');
res.end(JSON.stringify(obj));
}
function sendNull(res) {
res.statusCode = 200;
res.setHeader('content-type', 'application/json');
res.end('null');
}
function readRequestBody(req) {
return new Promise((resolve, reject) => {
let body = '';
req.on('data', (chunk) => {
body += chunk;
});
req.on('end', () => resolve(body));
req.on('error', reject);
});
}
async function readJsonBody(req) {
const text = await readRequestBody(req);
if (!text) return null;
try {
return JSON.parse(text);
} catch {
return null;
}
}
function normalizeArrayFields(obj, fields) {
if (!obj || typeof obj !== 'object') return obj;
const out = { ...obj };
for (const field of fields) {
if (out[field] == null) {
out[field] = [];
continue;
}
if (!Array.isArray(out[field])) {
out[field] = [];
}
}
return out;
}
const ONE_BY_ONE_PNG_BASE64 =
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO0pS0kAAAAASUVORK5CYII=';
function sendPng(res, code = 200) {
res.statusCode = code;
res.setHeader('content-type', 'image/png');
res.end(Buffer.from(ONE_BY_ONE_PNG_BASE64, 'base64'));
}
function escapeRegExp(value) {
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function matchPathname(pathname, template) {
const re = new RegExp(`^${template.split('/').map(escapeRegExp).join('/')}$`);
return re.test(pathname);
}
function getPathParam(pathname, matcher) {
const match = pathname.match(matcher);
return match ? match[1] : null;
}
function getSessionForMode(mode, req) {
if (mode === 'none') return null;
const cookies = parseCookies(req.headers.cookie);
const sponsorId = cookies.gridpilot_sponsor_id || 'demo-sponsor-1';
if (mode === 'admin') {
return {
token: 'test-token-admin',
user: {
userId: 'user-admin',
email: 'admin@gridpilot.test',
displayName: 'Demo Admin',
primaryDriverId: 'driver-admin',
role: 'league-admin', // MATCH WEBSITE EXPECTATIONS
},
};
}
if (mode === 'sponsor') {
return {
token: 'test-token-sponsor',
user: {
userId: 'user-sponsor',
email: 'sponsor@gridpilot.test',
displayName: 'Demo Sponsor User',
primaryDriverId: 'driver-sponsor',
sponsorId,
role: 'sponsor', // MATCH WEBSITE EXPECTATIONS
},
};
}
return {
token: 'test-token-driver',
user: {
userId: 'user-driver',
email: 'driver@gridpilot.test',
displayName: 'Demo Driver',
primaryDriverId: 'driver-1',
role: 'driver', // MATCH WEBSITE EXPECTATIONS
},
};
}
const DEMO = {
leagueId: 'league-1',
teamId: 'team-1',
raceId: 'race-1',
protestId: 'protest-1',
seasonId: 'season-1',
sponsorId: 'demo-sponsor-1',
};
function buildLeagueList() {
return {
leagues: [
{
id: DEMO.leagueId,
name: 'Demo League',
description: 'Demo league for docker smoke tests',
ownerId: 'driver-admin',
createdAt: nowIso(),
usedSlots: 2,
timingSummary: 'Weekly',
settings: { maxDrivers: 50 },
scoring: {
scoringPresetName: 'Demo rules',
scoringPatternSummary: 'Standard',
},
},
],
totalCount: 1,
};
}
function buildTeamsList() {
return {
teams: [
{
id: DEMO.teamId,
name: 'Demo Team',
tag: 'DEMO',
description: 'Demo team for docker smoke tests',
ownerId: 'driver-admin',
createdAt: nowIso(),
memberCount: 2,
leagues: [DEMO.leagueId],
isRecruiting: true,
totalWins: 5,
totalRaces: 20,
rating: 2500,
logoUrl: `/media/teams/${DEMO.teamId}/logo`,
},
],
totalCount: 1,
};
}
function buildRaceSchedule(seasonId) {
const date = nowIso();
return {
seasonId,
published: true,
races: [
{
id: DEMO.raceId,
name: 'Demo Race',
date,
scheduledAt: date,
track: 'Demo Track',
car: 'Demo Car',
sessionType: 'race',
status: 'scheduled',
isRegistered: false,
},
],
};
}
function buildStandings() {
return {
standings: [
{ driverId: 'driver-1', points: 100, position: 1 },
{ driverId: 'driver-admin', points: 90, position: 2 },
],
};
}
function buildMemberships() {
return {
members: [
{ driverId: 'driver-admin', role: 'owner', joinedAt: nowIso() },
{ driverId: 'driver-1', role: 'member', joinedAt: nowIso() },
{ driverId: 'driver-sponsor', role: 'member', joinedAt: nowIso() },
],
};
}
function buildDriver(driverId) {
return {
currentDriver: {
id: driverId,
name: driverId === 'driver-admin' ? 'Demo Admin Driver' : 'Demo Driver',
country: 'DE',
createdAt: nowIso(),
},
};
}
function buildDriverProfile(driverId) {
return {
currentDriver: {
id: driverId,
name: driverId === 'driver-admin' ? 'Demo Admin Driver' : 'Demo Driver',
country: 'DE',
avatarUrl: '/images/avatars/neutral-default-avatar.jpeg',
iracingId: driverId === 'driver-admin' ? '1002' : '1001',
joinedAt: nowIso(),
rating: 2500,
globalRank: 42,
consistency: 78,
bio: '',
totalDrivers: 1000,
},
stats: {
totalRaces: 12,
wins: 2,
podiums: 5,
dnfs: 1,
avgFinish: 6.3,
bestFinish: 1,
worstFinish: 18,
finishRate: 91.7,
winRate: 16.7,
podiumRate: 41.7,
percentile: 42,
rating: 2500,
consistency: 78,
overallRank: 42,
},
finishDistribution: {
totalRaces: 12,
wins: 2,
podiums: 5,
topTen: 8,
dnfs: 1,
other: 3,
},
teamMemberships: [],
socialSummary: {
friendsCount: 1,
friends: [
{
id: 'driver-admin',
name: 'Demo Admin Driver',
country: 'DE',
avatarUrl: '/images/avatars/male-default-avatar.jpg',
},
],
},
extendedProfile: {
socialHandles: [],
achievements: [],
racingStyle: 'Balanced',
favoriteTrack: 'Spa',
favoriteCar: 'Porsche 992 Cup',
timezone: 'Europe/Berlin',
availableHours: 'Evenings',
lookingForTeam: false,
openToRequests: true,
},
};
}
function buildTeamDetails(teamId) {
return {
team: {
id: teamId,
name: 'Demo Team',
ownerId: 'driver-admin',
createdAt: nowIso(),
description: '',
},
};
}
function buildTeamMembers(teamId) {
return {
teamId,
members: [
{ driverId: 'driver-admin', role: 'owner', joinedAt: nowIso(), driver: { id: 'driver-admin', name: 'Demo Admin Driver' } },
{ driverId: 'driver-1', role: 'member', joinedAt: nowIso(), driver: { id: 'driver-1', name: 'Demo Driver' } },
],
};
}
function buildRacePageData() {
const date = nowIso();
return {
races: [
{
id: DEMO.raceId,
name: 'Demo Race',
date,
scheduledAt: date,
leagueId: DEMO.leagueId,
leagueName: 'Demo League',
track: 'Demo Track',
car: 'Demo Car',
status: 'scheduled',
strengthOfField: null,
},
],
};
}
function buildRaceDetail(raceId) {
const date = nowIso();
return {
race: {
id: raceId,
name: 'Demo Race',
date,
track: 'Demo Track',
car: 'Demo Car',
status: 'scheduled',
leagueId: DEMO.leagueId,
},
league: { id: DEMO.leagueId, name: 'Demo League' },
entryList: [],
registration: { isRegistered: false },
userResult: null,
};
}
function buildRaceResults(raceId) {
return {
raceId,
results: [],
};
}
function buildSponsorDashboard(sponsorId) {
return {
sponsorId,
sponsor: { id: sponsorId, name: 'Demo Sponsor', logoUrl: '', websiteUrl: '' },
stats: { impressions: 0, clicks: 0 },
activeSponsorships: [],
recentCampaigns: [],
};
}
function buildSponsorSponsorships(sponsorId) {
return {
sponsorId,
sponsorships: [],
};
}
function buildSponsorSettings(sponsorId) {
return {
profile: { sponsorId, name: 'Demo Sponsor', websiteUrl: '', logoUrl: '' },
notifications: {},
privacy: {},
};
}
function buildPendingSponsorshipRequests() {
return {
requests: [],
};
}
function buildDashboardOverview() {
const scheduledAt = nowIso();
return {
currentDriver: {
id: 'driver-1',
name: 'Demo Driver',
country: 'DE',
avatarUrl: '/images/avatars/neutral-default-avatar.jpeg',
rating: 2500,
globalRank: 42,
totalRaces: 12,
wins: 2,
podiums: 5,
consistency: 78,
},
myUpcomingRaces: [
{
id: DEMO.raceId,
leagueId: DEMO.leagueId,
leagueName: 'Demo League',
track: 'Spa',
car: 'Porsche 992 Cup',
scheduledAt,
status: 'scheduled',
isMyLeague: true,
},
],
otherUpcomingRaces: [],
upcomingRaces: [
{
id: DEMO.raceId,
leagueId: DEMO.leagueId,
leagueName: 'Demo League',
track: 'Spa',
car: 'Porsche 992 Cup',
scheduledAt,
status: 'scheduled',
isMyLeague: true,
},
],
activeLeaguesCount: 1,
nextRace: {
id: DEMO.raceId,
leagueId: DEMO.leagueId,
leagueName: 'Demo League',
track: 'Spa',
car: 'Porsche 992 Cup',
scheduledAt,
status: 'scheduled',
isMyLeague: true,
},
recentResults: [],
leagueStandingsSummaries: [
{ leagueId: DEMO.leagueId, leagueName: 'Demo League', position: 1, totalDrivers: 10, points: 100 },
],
feedSummary: {
items: [
{
id: 'feed-1',
type: 'info',
headline: 'Welcome to GridPilot',
body: 'Demo data from the docker test API.',
timestamp: nowIso(),
ctaLabel: 'Browse leagues',
ctaHref: '/leagues',
},
],
},
friends: [
{
id: 'driver-admin',
name: 'Demo Admin Driver',
avatarUrl: '/images/avatars/male-default-avatar.jpg',
country: 'DE',
},
],
};
}
const server = http.createServer((req, res) => {
const origin = req.headers.origin || 'http://localhost:3100';
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Vary', 'Origin');
for (const [k, v] of Object.entries(baseCors)) res.setHeader(k, v);
if (req.method === 'OPTIONS') {
res.statusCode = 204;
return res.end();
}
const url = new URL(req.url, 'http://localhost');
const { pathname: rawPathname, searchParams } = url;
// Normalize trailing slashes so `/foo` and `/foo/` behave the same in mocks.
// This prevents false-negative 404s caused by minor URL formatting differences.
const pathname = rawPathname !== '/' ? rawPathname.replace(/\/+$/, '') || '/' : rawPathname;
const demoMode = getDemoMode(req);
const faultMode = getFaultMode(req);
const sessionDriftMode = getSessionDriftMode(req);
const send = (code, obj) => sendJson(res, code, obj);
if (pathname === '/health') return send(200, { status: 'ok' });
if (pathname === '/auth/demo-login' && req.method === 'POST') {
return readJsonBody(req)
.then((body) => {
const role = body && typeof body.role === 'string' ? body.role : 'driver';
// Map role to mode for session lookup
// The role parameter from tests should match what website expects
let mode;
if (role === 'sponsor') {
mode = 'sponsor';
} else if (role === 'league-admin' || role === 'league-owner' || role === 'league-steward' || role === 'super-admin' || role === 'system-owner') {
mode = 'admin'; // All admin-like roles use admin mode
} else {
mode = 'driver'; // Default to driver
}
const session = getSessionForMode(mode, req);
// For the docker smoke environment, the website middleware checks gp_session to
// allow protected routes, while the mock session endpoint uses gridpilot_demo_mode.
const gpSessionValue = `demo-${mode}-session`;
// Set cookies with proper domain for Docker environment
// In Docker tests, both website (3100) and API (3101) are on localhost
// so we need to set cookies for localhost domain
const domain = 'localhost';
const cookies = [
`gp_session=${encodeURIComponent(gpSessionValue)}; Path=/; HttpOnly; Domain=${domain}`,
`gridpilot_demo_mode=${encodeURIComponent(mode)}; Path=/; Domain=${domain}`,
];
if (mode === 'sponsor') {
cookies.push(`gridpilot_sponsor_id=${encodeURIComponent(DEMO.sponsorId)}; Path=/; Domain=${domain}`);
cookies.push(`gridpilot_sponsor_name=${encodeURIComponent('Demo Sponsor')}; Path=/; Domain=${domain}`);
}
res.setHeader('Set-Cookie', cookies);
return send(200, session);
})
.catch((err) => {
return send(500, { message: String(err?.message || err || 'demo-login failed') });
});
}
if (pathname === '/policy/snapshot') {
return send(200, {
policyVersion: 1,
operationalMode: 'test',
maintenanceAllowlist: { view: [], mutate: [] },
capabilities: {},
loadedFrom: 'defaults',
loadedAtIso: nowIso(),
});
}
if (pathname === '/auth/session') {
const session = getSessionForMode(demoMode, req);
// Test-mock behavior: "public" mode returns 200 with a null session so the browser
// does not emit noisy "Failed to load resource 401/403" console errors.
if (!session) return sendNull(res);
// Drift injection is only enabled when explicitly requested via cookie.
if (sessionDriftMode === 'expired') {
return sendNull(res);
}
if (sessionDriftMode === 'invalid-cookie') {
return send(200, { token: 'invalid', user: session.user });
}
if (sessionDriftMode === 'missing-sponsor-id') {
if (session.user && typeof session.user === 'object' && 'sponsorId' in session.user) {
const { sponsorId: _omit, ...restUser } = session.user;
return send(200, { token: session.token, user: restUser });
}
return send(200, session);
}
return send(200, session);
}
const avatarPath = getPathParam(pathname, /^\/media\/avatar\/([^/]+)$/);
if (avatarPath) return sendPng(res, 200);
const leagueMedia = pathname.match(/^\/media\/leagues\/([^/]+)\/(cover|logo)$/);
if (leagueMedia) return sendPng(res, 200);
const teamMedia = pathname.match(/^\/media\/teams\/([^/]+)\/logo$/);
if (teamMedia) return sendPng(res, 200);
const sponsorMedia = pathname.match(/^\/media\/sponsors\/([^/]+)\/logo$/);
if (sponsorMedia) return sendPng(res, 200);
if (pathname === '/leagues/all-with-capacity') {
const payload = normalizeArrayFields(buildLeagueList(), ['leagues']);
if (faultMode === 'null-array') payload.leagues = null;
return send(200, payload);
}
if (pathname === '/leagues/all-with-capacity-and-scoring') {
const payload = normalizeArrayFields(buildLeagueList(), ['leagues']);
if (faultMode === 'null-array') payload.leagues = null;
return send(200, payload);
}
if (pathname === '/teams/all') {
const payload = normalizeArrayFields(buildTeamsList(), ['teams']);
if (faultMode === 'null-array') payload.teams = null;
return send(200, payload);
}
if (pathname === '/leagues/scoring-presets') {
return send(200, {
presets: [
{
id: 'preset-1',
name: 'Demo Scoring',
description: 'Demo scoring preset for docker smoke tests',
primaryChampionshipType: 'driver',
sessionSummary: 'Main race',
bonusSummary: '',
dropPolicySummary: 'All results count',
defaultTimings: {
practiceMinutes: 15,
qualifyingMinutes: 10,
sprintRaceMinutes: 0,
mainRaceMinutes: 30,
sessionCount: 1,
},
},
],
});
}
if (pathname === '/dashboard/overview') {
const payload = buildDashboardOverview();
if (faultMode === 'null-array') {
if (payload.feedSummary && payload.feedSummary.items) payload.feedSummary.items = null;
if (payload.friends) payload.friends = null;
if (payload.leagueStandingsSummaries) payload.leagueStandingsSummaries = null;
if (payload.myUpcomingRaces) payload.myUpcomingRaces = null;
if (payload.otherUpcomingRaces) payload.otherUpcomingRaces = null;
if (payload.upcomingRaces) payload.upcomingRaces = null;
}
if (faultMode === 'invalid-date') {
if (payload.nextRace && payload.nextRace.scheduledAt) payload.nextRace.scheduledAt = 'not-a-date';
if (Array.isArray(payload.upcomingRaces) && payload.upcomingRaces[0]?.scheduledAt) payload.upcomingRaces[0].scheduledAt = 'not-a-date';
if (Array.isArray(payload.myUpcomingRaces) && payload.myUpcomingRaces[0]?.scheduledAt) payload.myUpcomingRaces[0].scheduledAt = 'not-a-date';
}
return send(200, payload);
}
// Admin dashboard stats endpoint
if (pathname === '/admin/dashboard/stats') {
// Check authorization - only admin roles can access
if (demoMode !== 'admin') {
return send(403, { message: 'Forbidden' });
}
return send(200, {
totalLeagues: 1,
totalMembers: 10,
totalRevenue: 5000,
activeSponsorships: 2,
});
}
if (pathname === '/drivers/leaderboard') return send(200, { drivers: [] });
if (pathname === '/drivers/current')
return send(200, buildDriver(getSessionForMode(demoMode, req)?.user?.primaryDriverId || 'driver-1'));
if (pathname === '/races/page-data') {
const payload = normalizeArrayFields(buildRacePageData(), ['races']);
if (faultMode === 'null-array') payload.races = null;
if (faultMode === 'invalid-date' && Array.isArray(payload.races) && payload.races[0]) {
payload.races[0].date = 'not-a-date';
payload.races[0].scheduledAt = 'not-a-date';
}
return send(200, payload);
}
if (pathname === '/races/reference/penalty-types') {
return send(200, {
penaltyTypes: [
{ type: 'time_penalty', requiresValue: true, valueKind: 'seconds' },
{ type: 'grid_penalty', requiresValue: true, valueKind: 'grid_positions' },
{ type: 'points_deduction', requiresValue: true, valueKind: 'points' },
{ type: 'disqualification', requiresValue: false, valueKind: 'none' },
{ type: 'warning', requiresValue: false, valueKind: 'none' },
{ type: 'license_points', requiresValue: true, valueKind: 'points' },
],
defaultReasons: {
upheld: 'Protest upheld based on steward review.',
dismissed: 'Protest dismissed due to insufficient evidence.',
},
});
}
const leagueProtestsMatch = pathname.match(/^\/leagues\/([^/]+)\/protests(?:\/([^/]+))?$/);
if (leagueProtestsMatch) {
const leagueId = leagueProtestsMatch[1];
const protestId = leagueProtestsMatch[2] || DEMO.protestId;
return send(200, {
protests: [
{
id: protestId,
leagueId,
raceId: DEMO.raceId,
protestingDriverId: 'driver-1',
accusedDriverId: 'driver-admin',
submittedAt: nowIso(),
description: 'Demo protest for docker smoke tests',
status: 'pending',
},
],
racesById: {
[DEMO.raceId]: {
id: DEMO.raceId,
name: 'Demo Race',
date: nowIso(),
leagueName: 'Demo League',
},
},
driversById: {
'driver-1': {
id: 'driver-1',
iracingId: '1001',
name: 'Demo Driver',
country: 'DE',
joinedAt: nowIso(),
},
'driver-admin': {
id: 'driver-admin',
iracingId: '1002',
name: 'Demo Admin Driver',
country: 'DE',
joinedAt: nowIso(),
},
},
});
}
const raceIdProtests = getPathParam(pathname, /^\/races\/([^/]+)\/protests$/);
if (raceIdProtests) {
return send(200, {
protests: [
{
id: DEMO.protestId,
protestingDriverId: 'driver-1',
accusedDriverId: 'driver-admin',
incident: { type: 'contact' },
lap: 1,
description: 'Demo incident',
status: 'pending',
filedAt: nowIso(),
},
],
driverMap: {
'driver-1': 'Demo Driver',
'driver-admin': 'Demo Admin Driver',
},
});
}
const raceIdPenalties = getPathParam(pathname, /^\/races\/([^/]+)\/penalties$/);
if (raceIdPenalties) {
return send(200, {
penalties: [],
driverMap: {},
});
}
const leagueIdFromMemberships = getPathParam(pathname, /^\/leagues\/([^/]+)\/memberships$/);
if (leagueIdFromMemberships) {
if (leagueIdFromMemberships !== DEMO.leagueId) return send(404, { message: 'Not Found', path: pathname, rawPath: rawPathname });
const payload = normalizeArrayFields(buildMemberships(), ['members']);
if (faultMode === 'null-array') payload.members = null;
return send(200, payload);
}
const leagueIdFromStandings = getPathParam(pathname, /^\/leagues\/([^/]+)\/standings$/);
if (leagueIdFromStandings) {
if (leagueIdFromStandings !== DEMO.leagueId) return send(404, { message: 'Not Found', path: pathname, rawPath: rawPathname });
const payload = normalizeArrayFields(buildStandings(), ['standings']);
if (faultMode === 'null-array') payload.standings = null;
return send(200, payload);
}
const leagueIdFromSchedule = getPathParam(pathname, /^\/leagues\/([^/]+)\/schedule$/);
if (leagueIdFromSchedule) {
if (leagueIdFromSchedule !== DEMO.leagueId) return send(404, { message: 'Not Found', path: pathname, rawPath: rawPathname });
const seasonId = searchParams.get('seasonId') || DEMO.seasonId;
const payload = normalizeArrayFields(buildRaceSchedule(seasonId), ['races']);
if (faultMode === 'null-array') payload.races = null;
if (faultMode === 'invalid-date' && Array.isArray(payload.races) && payload.races[0]) {
payload.races[0].date = 'not-a-date';
payload.races[0].scheduledAt = 'not-a-date';
}
if (faultMode === 'missing-field' && Array.isArray(payload.races) && payload.races[0]) {
delete payload.races[0].track;
}
return send(200, payload);
}
const leagueIdFromWallet = getPathParam(pathname, /^\/leagues\/([^/]+)\/wallet$/);
if (leagueIdFromWallet) {
if (leagueIdFromWallet !== DEMO.leagueId) return send(404, { message: 'Not Found', path: pathname, rawPath: rawPathname });
const date = nowIso();
const payload = {
balance: 2880,
currency: 'USD',
totalRevenue: 3200,
totalFees: 320,
totalWithdrawals: 0,
pendingPayouts: 0,
canWithdraw: true,
transactions: [
{
id: 'wallet-tx-1',
type: 'sponsorship',
description: 'Demo sponsorship revenue',
amount: 1600,
fee: 160,
netAmount: 1440,
date,
status: 'completed',
reference: 'sponsorship-1',
},
],
};
if (faultMode === 'null-array') payload.transactions = null;
if (faultMode === 'invalid-date' && Array.isArray(payload.transactions) && payload.transactions[0]) {
payload.transactions[0].date = 'not-a-date';
}
return send(200, payload);
}
const leagueIdFromWalletWithdraw = getPathParam(pathname, /^\/leagues\/([^/]+)\/wallet\/withdraw$/);
if (leagueIdFromWalletWithdraw && req.method === 'POST') {
if (leagueIdFromWalletWithdraw !== DEMO.leagueId) return send(404, { message: 'Not Found', path: pathname, rawPath: rawPathname });
return send(200, { success: true });
}
const leagueIdFromRaces = getPathParam(pathname, /^\/leagues\/([^/]+)\/races$/);
if (leagueIdFromRaces) {
if (leagueIdFromRaces !== DEMO.leagueId) return send(404, { message: 'Not Found', path: pathname, rawPath: rawPathname });
return send(200, { races: [buildRaceDetail(DEMO.raceId).race] });
}
const leagueIdFromSeasons = getPathParam(pathname, /^\/leagues\/([^/]+)\/seasons$/);
if (leagueIdFromSeasons) {
if (leagueIdFromSeasons !== DEMO.leagueId) return send(404, { message: 'Not Found', path: pathname, rawPath: rawPathname });
return send(200, [
{ seasonId: DEMO.seasonId, name: 'Season 1', status: 'active', startDate: nowIso(), endDate: nowIso() },
]);
}
const leagueIdFromRosterMembers = getPathParam(pathname, /^\/leagues\/([^/]+)\/admin\/roster\/members$/);
if (leagueIdFromRosterMembers) {
// Check authorization - only admin roles can access
if (demoMode !== 'admin') {
return send(403, { message: 'Forbidden' });
}
return send(200, [
{
driverId: 'driver-admin',
role: 'owner',
joinedAt: nowIso(),
driver: { id: 'driver-admin', name: 'Demo Admin Driver' },
},
{
driverId: 'driver-1',
role: 'member',
joinedAt: nowIso(),
driver: { id: 'driver-1', name: 'Demo Driver' },
},
]);
}
const leagueIdFromJoinRequests = getPathParam(pathname, /^\/leagues\/([^/]+)\/admin\/roster\/join-requests$/);
if (leagueIdFromJoinRequests) {
// Check authorization - only admin roles can access
if (demoMode !== 'admin') {
return send(403, { message: 'Forbidden' });
}
return send(200, [
{
id: 'join-request-1',
leagueId: leagueIdFromJoinRequests,
driverId: 'driver-sponsor',
requestedAt: nowIso(),
message: 'Please approve my join request',
driver: { id: 'driver-sponsor', name: 'Demo Sponsor Driver' },
},
]);
}
const seasonIdFromSponsorships = getPathParam(pathname, /^\/leagues\/seasons\/([^/]+)\/sponsorships$/);
if (seasonIdFromSponsorships) {
return send(200, {
sponsorships: [
{ id: 'sponsorship-1', seasonId: seasonIdFromSponsorships, sponsorId: DEMO.sponsorId, tier: 'main', status: 'active' },
],
});
}
const driverId = getPathParam(pathname, /^\/drivers\/([^/]+)$/);
if (driverId) return send(200, buildDriver(driverId));
const driverIdProfile = getPathParam(pathname, /^\/drivers\/([^/]+)\/profile$/);
if (driverIdProfile) {
// This endpoint is public, no auth required
return send(200, buildDriverProfile(driverIdProfile));
}
const teamIdDetails = getPathParam(pathname, /^\/teams\/([^/]+)$/);
if (teamIdDetails) return send(200, buildTeamDetails(teamIdDetails));
const teamIdMembers = getPathParam(pathname, /^\/teams\/([^/]+)\/members$/);
if (teamIdMembers) return send(200, buildTeamMembers(teamIdMembers));
const teamIdMembership = getPathParam(pathname, /^\/teams\/([^/]+)\/members\/([^/]+)$/);
if (teamIdMembership) {
const parts = pathname.split('/');
const teamId = parts[2];
const memberDriverId = parts[4];
return send(200, { teamId, driverId: memberDriverId, role: memberDriverId === 'driver-admin' ? 'owner' : 'member' });
}
const raceIdDetail = getPathParam(pathname, /^\/races\/([^/]+)$/);
if (raceIdDetail) {
if (raceIdDetail !== DEMO.raceId) return send(404, { message: 'Not Found', path: pathname, rawPath: rawPathname });
const driverIdForRace =
searchParams.get('driverId') || (getSessionForMode(demoMode, req)?.user?.primaryDriverId || 'driver-1');
void driverIdForRace;
const payload = buildRaceDetail(raceIdDetail);
if (faultMode === 'invalid-date' && payload.race) {
payload.race.date = 'not-a-date';
}
if (faultMode === 'null-array') {
payload.entryList = null;
}
return send(200, payload);
}
const raceIdSof = getPathParam(pathname, /^\/races\/([^/]+)\/sof$/);
if (raceIdSof) return send(200, { raceId: raceIdSof, strengthOfField: 2500 });
const raceIdResults = getPathParam(pathname, /^\/races\/([^/]+)\/results$/);
if (raceIdResults) return send(200, buildRaceResults(raceIdResults));
const sponsorDashboard = getPathParam(pathname, /^\/sponsors\/dashboard\/([^/]+)$/);
if (sponsorDashboard) {
const payload = buildSponsorDashboard(sponsorDashboard);
if (faultMode === 'null-array') {
payload.activeSponsorships = null;
payload.recentCampaigns = null;
}
if (faultMode === 'missing-field' && payload.sponsor) {
delete payload.sponsor.name;
}
return send(200, payload);
}
const sponsorSponsorships = getPathParam(pathname, /^\/sponsors\/([^/]+)\/sponsorships$/);
if (sponsorSponsorships) {
const payload = buildSponsorSponsorships(sponsorSponsorships);
if (faultMode === 'null-array') payload.sponsorships = null;
return send(200, payload);
}
const sponsorGet = getPathParam(pathname, /^\/sponsors\/([^/]+)$/);
if (sponsorGet) return send(200, { sponsor: { id: sponsorGet, name: 'Demo Sponsor', logoUrl: '', websiteUrl: '' } });
if (matchPathname(pathname, '/sponsors/pricing')) return send(200, { pricing: [] });
if (matchPathname(pathname, '/sponsors')) return send(200, { sponsors: [{ id: DEMO.sponsorId, name: 'Demo Sponsor', logoUrl: '', websiteUrl: '' }] });
if (pathname === '/sponsors/requests') return send(200, buildPendingSponsorshipRequests());
const sponsorBilling = getPathParam(pathname, /^\/sponsors\/billing\/([^/]+)$/);
if (sponsorBilling) {
// Check authorization - only sponsor role can access
if (demoMode !== 'sponsor') {
return send(403, { message: 'Forbidden' });
}
const today = new Date();
const invoiceDate = new Date(today.getFullYear(), today.getMonth(), 1).toISOString();
const dueDate = new Date(today.getFullYear(), today.getMonth(), 15).toISOString();
const nextPaymentDate = new Date(today.getFullYear(), today.getMonth() + 1, 1).toISOString();
return send(200, {
paymentMethods: [
{
id: 'pm-1',
type: 'card',
last4: '4242',
brand: 'Visa',
isDefault: true,
expiryMonth: 12,
expiryYear: 2030,
},
],
invoices: [
{
id: 'inv-1',
invoiceNumber: 'GP-0001',
date: invoiceDate,
dueDate,
amount: 100,
vatAmount: 20,
totalAmount: 120,
status: 'paid',
description: 'Demo sponsorship invoice',
sponsorshipType: 'league',
pdfUrl: '/billing/invoices/inv-1.pdf',
},
],
stats: {
totalSpent: 120,
pendingAmount: 0,
nextPaymentDate,
nextPaymentAmount: 0,
activeSponsorships: 0,
averageMonthlySpend: 20,
},
});
}
const sponsorSettings = getPathParam(pathname, /^\/sponsors\/settings\/([^/]+)$/);
if (sponsorSettings) {
// Check authorization - only sponsor role can access
if (demoMode !== 'sponsor') {
return send(403, { message: 'Forbidden' });
}
return send(200, buildSponsorSettings(sponsorSettings));
}
const sponsorLeagueAvailable = pathname === '/sponsors/leagues/available';
if (sponsorLeagueAvailable) {
// Check authorization - only sponsor role can access
if (demoMode !== 'sponsor') {
return send(403, { message: 'Forbidden' });
}
return send(200, [
{
id: DEMO.leagueId,
name: 'Demo League',
game: 'iRacing',
drivers: 24,
avgViewsPerRace: 3200,
mainSponsorSlot: { available: true, price: 1500 },
secondarySlots: { available: 2, total: 4, price: 500 },
rating: 4.6,
tier: 'standard',
nextRace: 'Sunday 19:00',
seasonStatus: 'active',
description: 'Demo league available for sponsorship (docker smoke tests).',
},
]);
}
const sponsorLeagueDetail = getPathParam(pathname, /^\/sponsors\/leagues\/([^/]+)\/detail$/);
if (sponsorLeagueDetail) {
// Check authorization - only sponsor role can access
if (demoMode !== 'sponsor') {
return send(403, { message: 'Forbidden' });
}
return send(200, {
league: {
id: sponsorLeagueDetail,
name: 'Demo League',
game: 'iRacing',
tier: 'standard',
season: '2025 S1',
description: 'Demo league detail for sponsor pages (docker smoke tests).',
drivers: 24,
races: 10,
completedRaces: 2,
totalImpressions: 42000,
avgViewsPerRace: 3200,
engagement: 78,
rating: 4.6,
seasonStatus: 'active',
seasonDates: { start: nowIso(), end: nowIso() },
nextRace: { name: 'Demo Race 3', date: nowIso() },
sponsorSlots: {
main: {
available: true,
price: 1500,
benefits: ['Logo on broadcast overlay', 'Mentioned in race intro'],
},
secondary: {
available: 2,
total: 4,
price: 500,
benefits: ['Logo on results page', 'Listed on sponsor board'],
},
},
},
drivers: [
{
id: 'driver-1',
name: 'Demo Driver',
country: 'DE',
position: 1,
races: 2,
impressions: 6400,
team: 'Demo Team',
},
],
races: [
{
id: DEMO.raceId,
name: 'Demo Race',
date: nowIso(),
views: 3200,
status: 'completed',
},
{
id: 'race-2',
name: 'Demo Race 2',
date: nowIso(),
views: 0,
status: 'upcoming',
},
],
});
}
return send(404, { message: 'Not Found', path: pathname, rawPath: rawPathname });
});
server.listen(PORT, () => {
// eslint-disable-next-line no-console
console.log(`[api-mock] listening on ${PORT}`);
});