140 lines
4.2 KiB
TypeScript
140 lines
4.2 KiB
TypeScript
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: {},
|
|
};
|
|
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");
|
|
});
|
|
});
|
|
});
|