deadhead-space-freight-tycoon/packages/sim/test/upgrade-validation.test.ts

127 lines
3.9 KiB
TypeScript

import { content } from "@deadhead/content";
import { describe, expect, it } from "vitest";
import {
upgradeCost,
upgradeShopMs,
validateDispatch,
validateUpgrade,
type Contract,
type PilotState,
type ShipState,
type UpgradeProposal,
} from "../src/index";
/** Fuel enough for any leg in these tests (decision 0.5.94), bought at 10 cr/fu. */
const PLENTY = { fuelFu: 10_000, avgPrice: 10 };
const now = Date.parse("2100-01-01T00:30:00Z");
const freighter = content.shipModel("freighter")!;
const engines = content.upgrade("engines")!;
const ship: ShipState = {
id: "s1",
modelId: "freighter",
locationId: "earth",
inFlight: false,
tank: PLENTY,
};
const valid: UpgradeProposal = {
ship,
upgrade: engines,
atHq: true,
tutorialActive: false,
credits: 1_000_000,
now,
};
const reason = (proposal: UpgradeProposal) => {
const result = validateUpgrade(proposal, content);
return result.ok ? "valid" : result.reason;
};
describe("upgrade cost and shop time (Step 10I.3)", () => {
it("costs 10 % of a Freighter's price at level 1 and 35 % at level 3", () => {
expect(upgradeCost(freighter, 1, content.config)).toBe(freighter.price * 0.1);
expect(upgradeCost(freighter, 3, content.config)).toBe(freighter.price * 0.35);
expect(upgradeShopMs(2, content.config)).toBe(20 * 60_000);
});
it("starts the next level, with its cost and shop time", () => {
expect(
validateUpgrade({ ...valid, ship: { ...ship, upgrades: { engines: 1 } } }, content),
).toEqual({
ok: true,
level: 2,
cost: upgradeCost(freighter, 2, content.config),
shopUntil: now + upgradeShopMs(2, content.config),
});
});
});
describe("upgrade validation (Step 10I.3)", () => {
it("accepts a valid upgrade", () => expect(reason(valid)).toBe("valid"));
it("refuses during the tutorial", () =>
expect(reason({ ...valid, tutorialActive: true })).toBe("tutorial-active"));
it("refuses the company loaner", () =>
expect(reason({ ...valid, ship: { ...ship, modelId: "company-courier" } })).toBe(
"not-upgradable",
));
it("refuses a ship in flight", () =>
expect(reason({ ...valid, ship: { ...ship, inFlight: true } })).toBe("ship-not-idle"));
it("refuses a ship already in the shop", () =>
expect(reason({ ...valid, ship: { ...ship, inShopUntil: now + 1000 } })).toBe("ship-in-shop"));
it("refuses away from an HQ", () => expect(reason({ ...valid, atHq: false })).toBe("not-at-hq"));
it("refuses a maxed upgrade", () =>
expect(reason({ ...valid, ship: { ...ship, upgrades: { engines: 3 } } })).toBe(
"upgrade-maxed",
));
it("refuses without the credits", () =>
expect(reason({ ...valid, credits: upgradeCost(freighter, 1, content.config) - 1 })).toBe(
"insufficient-funds",
));
});
describe("ships in the shop can't fly (Step 10I.3)", () => {
const pilot: PilotState = {
id: "p1",
isAvatar: false,
assignedShipId: "s1",
inFlight: false,
skills: {},
restEndsAt: null,
};
const contract: Contract = {
id: "c1",
originId: "earth",
destinationId: "moon",
cargoTypeId: "standard",
goodId: "consumer-goods",
shipper: "Pacific Orbital Logistics",
priority: false,
deliveryWindowGameHours: null,
tier: 0,
massTonnes: 10,
payout: 2000,
requiredSkill: null,
requiredLongHaulLevel: 0,
offeredAt: now,
expiresAt: now + 3_600_000,
firstContact: false,
};
const at = (time: number, shopUntil: number) =>
validateDispatch(
{
ship: { ...ship, inShopUntil: shopUntil },
pilot,
contract,
tutorial: false,
now: time,
},
content,
);
it("fails ship-in-shop one second before in-shop-until and passes at it", () => {
const until = now + 10 * 60_000;
expect(at(until - 1000, until)).toEqual({ ok: false, reason: "ship-in-shop" });
expect(at(until, until)).toEqual({ ok: true });
});
});