This commit is contained in:
2025-11-29 17:52:10 +01:00
parent 93798201b4
commit 187619cbb2
2 changed files with 24 additions and 252 deletions

View File

@@ -11,7 +11,7 @@ export interface IFixtureServer {
/**
* Step number to fixture file mapping.
* Steps 2-17 map to the corresponding HTML fixture files.
* Steps 1-18 map to the corresponding HTML fixture files.
*/
const STEP_TO_FIXTURE: Record<number, string> = {
1: '01-hosted-racing.html',
@@ -40,7 +40,8 @@ export class FixtureServer implements IFixtureServer {
private fixturesPath: string;
constructor(fixturesPath?: string) {
this.fixturesPath = fixturesPath ?? path.resolve(process.cwd(), 'html-dumps/iracing-hosted-sessions');
this.fixturesPath =
fixturesPath ?? path.resolve(process.cwd(), 'html-dumps/iracing-hosted-sessions');
}
async start(port: number = 3456): Promise<{ url: string; port: number }> {
@@ -57,7 +58,6 @@ export class FixtureServer implements IFixtureServer {
this.server.on('error', (err: NodeJS.ErrnoException) => {
if (err.code === 'EADDRINUSE') {
// Try next port
this.server = null;
this.start(port + 1).then(resolve).catch(reject);
} else {
@@ -102,7 +102,23 @@ export class FixtureServer implements IFixtureServer {
private handleRequest(req: http.IncomingMessage, res: http.ServerResponse): void {
const urlPath = req.url || '/';
const fileName = urlPath === '/' ? 'step-02-hosted-racing.html' : urlPath.replace(/^\//, '');
let fileName: string;
if (urlPath === '/') {
fileName = STEP_TO_FIXTURE[1];
} else {
fileName = urlPath.replace(/^\//, '');
const legacyMatch = fileName.match(/^step-(\d+)-/);
if (legacyMatch) {
const stepNum = Number(legacyMatch[1]);
const mapped = STEP_TO_FIXTURE[stepNum];
if (mapped) {
fileName = mapped;
}
}
}
const filePath = path.join(this.fixturesPath, fileName);
// Security check - prevent directory traversal
@@ -114,246 +130,10 @@ export class FixtureServer implements IFixtureServer {
fs.readFile(filePath, (err, data) => {
if (err) {
if (err.code === 'ENOENT') {
// Only generate fallback HTML for known step fixture filenames
// (e.g., 'step-03-create-race.html'). For other missing files,
// return 404 so tests that expect non-existent files to be 404 still pass.
if (!/^step-\d+-/.test(fileName)) {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Not Found');
return;
}
const stepMatch = fileName.match(/(\d+)-/);
const stepNum = stepMatch ? Number(stepMatch[1]) : 1;
const fallbackHtml = `
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title>Mock Fixture - Step ${stepNum}</title>
<style>
.hidden { display: none; }
.modal { display: block; }
.wizard-footer a.btn { display: inline-block; padding: 6px 10px; }
.btn-primary { background: #0b74de; color: #fff; }
.btn-success { background: #28a745; color: #fff; }
</style>
<script>
// Run after DOMContentLoaded so elements exist when we attempt to un-hide them.
document.addEventListener('DOMContentLoaded', function () {
try {
const step = Number(${stepNum});
let id = null;
let indicator = null;
if (step === 1) {
id = null; // hosted sessions - not part of modal
} else if (step === 2) {
id = 'set-session-information';
indicator = 'race-information';
} else if (step === 3) {
id = 'set-session-information';
indicator = 'race-information';
} else if (step === 4) {
id = 'set-server-details';
indicator = 'server-details';
} else if (step === 5) {
id = 'set-admins';
indicator = 'set-admins';
} else if (step === 6) {
id = 'set-admins';
indicator = 'add-admin';
} else if (step === 7) {
id = 'set-time-limit';
indicator = 'time-limits';
} else if (step === 8) {
id = 'set-cars';
indicator = 'set-cars';
} else if (step === 9) {
id = 'set-cars';
indicator = 'add-car';
} else if (step === 10) {
id = 'set-car-classes';
indicator = 'set-car-classes';
} else if (step === 11) {
id = 'set-track';
indicator = 'set-track';
} else if (step === 12) {
id = 'set-track';
indicator = 'add-track';
} else if (step === 13) {
id = 'set-track-options';
indicator = 'track-options';
} else if (step === 14) {
id = 'set-time-of-day';
indicator = 'time-of-day';
} else if (step === 15) {
id = 'set-weather';
indicator = 'weather';
} else if (step === 16) {
id = 'set-race-options';
indicator = 'race-options';
} else if (step === 17) {
id = 'team-driving';
indicator = 'team-driving';
} else if (step === 18) {
id = 'set-track-conditions';
indicator = 'track-conditions';
}
if (id) {
var el = document.getElementById(id);
if (el) el.classList.remove('hidden');
// Ensure parent modal is visible for modal-contained steps
var modal = document.getElementById('create-race-modal');
if (modal) modal.classList.remove('hidden');
} else {
// If no modal step is relevant, ensure modal is hidden
var modal = document.getElementById('create-race-modal');
if (modal) modal.classList.add('hidden');
}
// Set data-indicator for step identification
if (indicator) {
document.body.setAttribute('data-indicator', indicator);
}
} catch (e) {
// noop
}
});
</script>
</head>
<body data-step="${stepNum}" data-indicator="">
<nav>
<button aria-label="Create a Race" id="create-race-btn">Create a Race</button>
</nav>
<!-- Generic wizard modal and sidebar -->
<div id="create-race-modal" role="dialog" class="modal show">
<div id="create-race-wizard">
<aside class="wizard-sidebar">
<a id="wizard-sidebar-link-set-session-information" data-indicator="race-information">Race Information</a>
<a id="wizard-sidebar-link-set-server-details" data-indicator="server-details">Server Details</a>
<a id="wizard-sidebar-link-set-admins" data-indicator="set-admins">Set Admins</a>
<a id="wizard-sidebar-link-add-admin" data-indicator="add-admin">Add Admin</a>
<a id="wizard-sidebar-link-time-limits" data-indicator="time-limits">Time Limits</a>
<a id="wizard-sidebar-link-set-cars" data-indicator="set-cars">Set Cars</a>
<a id="wizard-sidebar-link-add-car" data-indicator="add-car">Add Car</a>
<a id="wizard-sidebar-link-set-car-classes" data-indicator="set-car-classes">Set Car Classes</a>
<a id="wizard-sidebar-link-set-track" data-indicator="set-track">Set Track</a>
<a id="wizard-sidebar-link-add-track" data-indicator="add-track">Add Track</a>
<a id="wizard-sidebar-link-track-options" data-indicator="track-options">Track Options</a>
<a id="wizard-sidebar-link-time-of-day" data-indicator="time-of-day">Time of Day</a>
<a id="wizard-sidebar-link-weather" data-indicator="weather">Weather</a>
<a id="wizard-sidebar-link-race-options" data-indicator="race-options">Race Options</a>
<a id="wizard-sidebar-link-team-driving" data-indicator="team-driving">Team Driving</a>
<a id="wizard-sidebar-link-track-conditions" data-indicator="track-conditions">Track Conditions</a>
</aside>
<div class="wizard-content">
<section id="set-session-information" class="wizard-step hidden">
<div class="card-block">
<div class="form-group">
<input class="form-control" data-field="sessionName" placeholder="Session name" />
</div>
<div class="form-group">
<input class="form-control" type="password" data-field="password" />
</div>
<div class="form-group">
<textarea class="form-control" data-field="description"></textarea>
</div>
</div>
</section>
<section id="set-server-details" class="wizard-step hidden">
<select class="form-control" data-dropdown="region">
<option value="eu-central">EU Central</option>
<option value="us-west">US West</option>
</select>
<input type="checkbox" data-toggle="startNow" />
</section>
<section id="set-admins" class="wizard-step hidden">
<input placeholder="Search" data-field="adminSearch" />
<div data-list="admins">
<div data-item="admin-001">admin-001</div>
</div>
</section>
<section id="set-time-limit" class="wizard-step hidden">
<input placeholder="Time limit" data-field="timeLimit" />
</section>
<section id="set-cars" class="wizard-step hidden">
<input placeholder="Search" data-field="carSearch" />
<div data-list="cars"></div>
<a class="btn" data-modal-trigger="car">Add Car</a>
</section>
<section id="set-car-classes" class="wizard-step hidden">
<input placeholder="Search" data-field="carClassSearch" />
<div data-list="car-classes"></div>
</section>
<section id="set-track" class="wizard-step hidden">
<input placeholder="Search" data-field="trackSearch" />
<div data-list="tracks"></div>
</section>
<section id="set-track-options" class="wizard-step hidden">
<input placeholder="Track options" data-field="trackOptions" />
</section>
<section id="set-time-of-day" class="wizard-step hidden">
<input placeholder="Time of day" data-field="timeOfDay" />
</section>
<section id="set-weather" class="wizard-step hidden">
<input placeholder="Weather" data-field="weather" />
</section>
<section id="set-race-options" class="wizard-step hidden">
<input placeholder="Race options" data-field="raceOptions" />
</section>
<section id="team-driving" class="wizard-step hidden">
<input placeholder="Team driving" data-field="teamDriving" />
</section>
<section id="set-track-conditions" class="wizard-step hidden">
<select data-dropdown="trackState"></select>
<input data-slider="rubberLevel" value="50" />
</section>
</div>
<footer class="wizard-footer">
<a class="btn btn-secondary">Back</a>
<a class="btn btn-primary"><span class="icon-caret-right"></span> Next</a>
<a class="btn btn-success"><span class="label-pill">$0.00</span> Check Out</a>
</footer>
</div>
</div>
<div class="modal" data-modal="true">
<div class="modal-content">
<div class="modal-body">
<input placeholder="Search" />
<table><tr><td><a class="btn btn-primary btn-xs">Select</a></td></tr></table>
</div>
<div class="modal-footer">
<a class="btn-success">Confirm</a>
<a class="btn-secondary">Back</a>
</div>
</div>
</div>
</body>
</html>
`;
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(fallbackHtml);
const errno = (err as NodeJS.ErrnoException).code;
if (errno === 'ENOENT') {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Not Found');
} else {
res.writeHead(500, { 'Content-Type': 'text/plain' });
res.end('Internal Server Error');