98 lines
4.3 KiB
TypeScript
98 lines
4.3 KiB
TypeScript
import fs from 'node:fs/promises';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
|
import * as schema from '@/lib/db/schema';
|
|
import { getGameDb } from '@/lib/db/client';
|
|
import { POST as acceptPost } from '../../../contracts/[id]/accept/route';
|
|
import { POST } from './route';
|
|
|
|
let dir: string;
|
|
|
|
beforeAll(async () => {
|
|
// Same pattern as app/api/state/route.test.ts: point the DB singleton at a
|
|
// throwaway file (fresh module registry per test file → fresh singleton).
|
|
dir = await fs.mkdtemp(path.join(os.tmpdir(), 'flight-dispatch-api-'));
|
|
process.env.FLIGHT_DB_PATH = path.join(dir, 'dispatch.db');
|
|
});
|
|
|
|
afterAll(async () => {
|
|
delete process.env.FLIGHT_DB_PATH;
|
|
await fs.rm(dir, { recursive: true, force: true });
|
|
});
|
|
|
|
const post = (planeId: string, body: unknown) =>
|
|
POST(new Request(`http://localhost/api/planes/${planeId}/dispatch`, { method: 'POST', body: JSON.stringify(body) }), {
|
|
params: Promise.resolve({ planeId }),
|
|
});
|
|
|
|
describe('POST /api/planes/:planeId/dispatch', () => {
|
|
it('rejects a missing/non-string contractId and unknown fields with 400', async () => {
|
|
for (const body of [{}, { contractId: 42 }, { bogus: 1 }]) {
|
|
const res = await post('plane-starter', body);
|
|
expect(res.status).toBe(400);
|
|
expect(((await res.json()) as { error: string }).error).toBeTruthy();
|
|
}
|
|
});
|
|
|
|
it('rejects dispatching an OPEN contract (no accept) with 409 — the e2e "dispatch without accept" path', async () => {
|
|
const { db } = await getGameDb();
|
|
// A known open contract at the starter plane's home base.
|
|
await db.insert(schema.contracts).values({
|
|
id: 'c-open-at-home', originIcao: 'EDDF', destIcao: 'LEMD', reward: 1, cargoType: 'Mail', cargoCount: 1,
|
|
deadlineSimTime: Date.now() + 3_600_000, status: 'open', planeId: null, createdSimTime: Date.now(),
|
|
});
|
|
|
|
const res = await post('plane-starter', { contractId: 'c-open-at-home' });
|
|
expect(res.status).toBe(409);
|
|
expect(((await res.json()) as { error: string }).error).toMatch(/open/);
|
|
});
|
|
|
|
it('rejects unknown plane and contract ids with 404', async () => {
|
|
const { db } = await getGameDb();
|
|
// Contract checks run before plane checks, so the ghost-plane case needs an
|
|
// ACCEPTED contract to reach the plane lookup.
|
|
await db.insert(schema.contracts).values({
|
|
id: 'c-acc-ghost', originIcao: 'EDDF', destIcao: 'LEMD', reward: 1, cargoType: 'Mail', cargoCount: 1,
|
|
deadlineSimTime: Date.now() + 3_600_000, status: 'accepted', planeId: null, createdSimTime: Date.now(),
|
|
});
|
|
|
|
const ghostPlane = await post('ghost-plane', { contractId: 'c-acc-ghost' });
|
|
expect(ghostPlane.status).toBe(404);
|
|
|
|
const ghostContract = await post('plane-starter', { contractId: 'nope' });
|
|
expect(ghostContract.status).toBe(404);
|
|
});
|
|
|
|
it('accepts then dispatches: snapshot shows the plane flying with departure set and the contract bound', async () => {
|
|
const { db } = await getGameDb();
|
|
// Accept the home-base contract through its own route, then dispatch.
|
|
const acceptRes = await acceptPost(
|
|
new Request('http://localhost/api/contracts/c-open-at-home/accept'),
|
|
{ params: Promise.resolve({ id: 'c-open-at-home' }) },
|
|
);
|
|
expect(acceptRes.status).toBe(200);
|
|
|
|
const res = await post('plane-starter', { contractId: 'c-open-at-home' });
|
|
expect(res.status).toBe(200);
|
|
|
|
const body = (await res.json()) as {
|
|
planes: Array<{ id: string; status: string; departureSimTime: number | null; locationIcao: string }>;
|
|
contracts: Array<{ id: string; status: string; planeId: string | null }>;
|
|
};
|
|
const plane = body.planes.find((p) => p.id === 'plane-starter')!;
|
|
expect(plane.status).toBe('flying');
|
|
expect(typeof plane.departureSimTime).toBe('number'); // departure set at dispatch time
|
|
expect(plane.locationIcao).toBe('EDDF');
|
|
|
|
const contract = body.contracts.find((c) => c.id === 'c-open-at-home')!;
|
|
expect(contract.status).toBe('accepted');
|
|
expect(contract.planeId).toBe('plane-starter');
|
|
|
|
// A second dispatch of the same (now bound, still accepted) contract by the
|
|
// flying plane is rejected — the plane is no longer available.
|
|
const again = await post('plane-starter', { contractId: 'c-open-at-home' });
|
|
expect(again.status).toBe(409);
|
|
});
|
|
});
|