Files
Flight-Ops/app/api/test/expire-contract/route.ts
T

75 lines
3.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { randomInt, randomUUID } from 'node:crypto';
import { eq } from 'drizzle-orm';
import { getGameDb, persist } from '@/lib/db/client';
import * as schema from '@/lib/db/schema';
import { loadAirportsGeoJson } from '@/lib/airports';
import { generateContract, mulberry32 } from '@/lib/game/seed';
import { tickNow } from '@/lib/sim/engine';
// TEST-ONLY hook (v0.7.0) — lets the e2e suite observe contract expiry without
// waiting hours of game time. Disabled unless FLIGHT_TEST_HOOKS=1, which is set in
// docker-compose.yml for the e2e instance only; every other deployment 404s here.
//
// POST /api/test/expire-contract
// body { contractId?: string }
// - with contractId: moves that OPEN contract's deadline to now + HOOK_DEADLINE_MS
// (game time) so the next fast tick expires it; 404 if unknown/not open.
// - without: creates a fresh open contract with the same short deadline.
const HOOK_DEADLINE_MS = 10_000; // game ms past the current clock (~0.2 s real at 60×)
export async function POST(req: Request) {
if (process.env.FLIGHT_TEST_HOOKS !== '1') {
return Response.json({ error: 'test hooks disabled' }, { status: 404 });
}
let body: unknown = {};
try {
const text = await req.text();
body = text ? JSON.parse(text) : {};
} catch {
return Response.json({ error: 'invalid JSON body' }, { status: 400 });
}
if (typeof body !== 'object' || body === null || Array.isArray(body)) {
return Response.json({ error: 'body must be a JSON object' }, { status: 400 });
}
const contractId = (body as Record<string, unknown>).contractId;
if (contractId !== undefined && typeof contractId !== 'string') {
return Response.json({ error: 'contractId must be a string' }, { status: 400 });
}
try {
const inst = await getGameDb();
const sim = await tickNow(); // clock is current before we set the deadline
const deadlineSimTime = Math.round(sim.clockMs + HOOK_DEADLINE_MS);
if (typeof contractId === 'string') {
const rows = await inst.db.select().from(schema.contracts).where(eq(schema.contracts.id, contractId));
const row = rows[0];
if (!row || row.status !== 'open') {
return Response.json({ error: 'contract not found or not open' }, { status: 404 });
}
await inst.db.update(schema.contracts).set({ deadlineSimTime }).where(eq(schema.contracts.id, contractId));
await persist(inst);
const updated = (await inst.db.select().from(schema.contracts).where(eq(schema.contracts.id, contractId)))[0];
return Response.json(updated ?? row);
}
const fc = await loadAirportsGeoJson();
const airports = fc.features.map((f) => ({
icao: f.properties.icao,
name: f.properties.name,
lat: f.properties.lat,
lon: f.properties.lon,
}));
const rng = mulberry32(randomInt(0, 2 ** 31));
const created = generateContract({ id: randomUUID(), airports, rng, nowMs: sim.clockMs, deadlineOffsetMs: HOOK_DEADLINE_MS });
await inst.db.insert(schema.contracts).values(created);
await persist(inst);
return Response.json(created, { status: 201 });
} catch (err) {
console.error('test hook failed', err);
return Response.json({ error: 'test hook failed' }, { status: 500 });
}
}