import { content as defaultContent, type ShipModel } from "@deadhead/content"; import { describe, expect, it } from "vitest"; import { validateDispatch, type Contract, type DispatchProposal, type PilotState, type ShipState, type SimContent, } from "../src/index"; /** Default content plus a test-only ship that cannot land on atmospheric worlds. */ const vacuumModel: ShipModel = { ...defaultContent.shipModel("courier")!, id: "test-vacuum", landingClass: "vacuum", }; const content: SimContent = { ...defaultContent, shipModels: [...defaultContent.shipModels, vacuumModel], }; const now = Date.parse("2100-01-01T00:30:00Z"); const contract: Contract = { id: "c1", originId: "earth", destinationId: "moon", cargoTypeId: "standard", tier: 0, massTonnes: 10, payout: 2000, requiredSkill: null, requiredLongHaulLevel: 0, offeredAt: Date.parse("2100-01-01T00:00:00Z"), expiresAt: Date.parse("2100-01-01T01:00:00Z"), firstContact: false, }; const ship: ShipState = { id: "s1", modelId: "courier", locationId: "earth", inFlight: false }; const pilot: PilotState = { id: "p1", isAvatar: false, assignedShipId: "s1", inFlight: false, skills: {}, restEndsAt: null, }; const valid: DispatchProposal = { ship, pilot, contract, tutorial: false, credits: 10_000, now }; const reason = (proposal: DispatchProposal) => { const result = validateDispatch(proposal, content); return result.ok ? "valid" : result.reason; }; describe("dispatch validation", () => { it("accepts a fully valid dispatch", () => { expect(reason(valid)).toBe("valid"); }); it("rejects a ship that is in flight", () => { expect(reason({ ...valid, ship: { ...ship, inFlight: true } })).toBe("ship-not-idle"); }); it("rejects a ship that is not at the contract origin", () => { expect(reason({ ...valid, ship: { ...ship, locationId: "moon" } })).toBe("ship-not-at-origin"); }); it("rejects a pilot who is in flight", () => { expect(reason({ ...valid, pilot: { ...pilot, inFlight: true } })).toBe("pilot-not-idle"); }); it("rejects a pilot assigned to another ship", () => { expect(reason({ ...valid, pilot: { ...pilot, assignedShipId: "s2" } })).toBe( "pilot-not-assigned", ); }); it("rejects cargo heavier than the ship's capacity", () => { expect(reason({ ...valid, contract: { ...contract, massTonnes: 50 } })).toBe("over-capacity"); }); it("rejects a destination the ship cannot land at", () => { expect( reason({ ...valid, ship: { ...ship, modelId: "test-vacuum", locationId: "moon" }, contract: { ...contract, originId: "moon", destinationId: "earth" }, }), ).toBe("cannot-land"); }); it("rejects a destination out of the ship's range", () => { expect(reason({ ...valid, contract: { ...contract, destinationId: "mars" } })).toBe( "out-of-range", ); }); it("rejects a pilot without the required cargo skill", () => { const heavy = { ...contract, cargoTypeId: "heavy", tier: 1, requiredSkill: { skillId: "heavy-cargo", level: 1 }, }; expect(reason({ ...valid, contract: heavy })).toBe("cargo-skill-too-low"); }); it("rejects a pilot without the required Long Haul level", () => { expect(reason({ ...valid, contract: { ...contract, requiredLongHaulLevel: 1 } })).toBe( "long-haul-too-low", ); }); it("rejects an expired contract", () => { expect(reason({ ...valid, now: contract.expiresAt })).toBe("contract-expired"); }); it("rejects a dispatch the player cannot afford fuel for", () => { expect(reason({ ...valid, credits: 0 })).toBe("insufficient-credits"); }); describe("tutorial runs", () => { const loaner: ShipState = { id: "loaner", modelId: "courier", locationId: "earth", inFlight: false, }; const avatar: PilotState = { ...pilot, id: "avatar", isAvatar: true, assignedShipId: null }; const tutorial: DispatchProposal = { ...valid, ship: loaner, pilot: avatar, tutorial: true }; it("accepts the avatar in the loaner without credits for fuel", () => { expect(reason({ ...tutorial, credits: 0 })).toBe("valid"); }); it("rejects a hired pilot in the loaner", () => { expect(reason({ ...tutorial, pilot })).toBe("pilot-not-assigned"); }); }); });