deadhead-space-freight-tycoon/packages/engine/test/ported/resolution.test.ts

146 lines
4.9 KiB
TypeScript

// Ported from apps/server/test (CR-014 Step 14.5): now runs on the engine.
import { describe, expect, it } from "vitest";
import { resolveArrivals } from "../../src/index";
import {
useTestApp,
signUp,
getState,
readSave,
setCredits,
testContract,
insertDispatch,
makeOwner,
} from "../support";
const testApp = useTestApp();
/** An owner (one Courier at Earth, crewed by the avatar) with 1,000 credits. */
async function owner() {
const { app } = testApp();
const guest = await signUp(app);
const { shipId } = await makeOwner(testApp().app, guest.avatarId);
await setCredits(testApp().app, 1000);
return { guest, shipId };
}
describe("lazy resolution on read", () => {
it("applies a past arrival's credits, XP, ship location and discovery", async () => {
const { app, clock } = testApp();
const { guest, shipId } = await owner();
// A delivery to Mars, which the player has not discovered yet.
const contract = testContract({ destinationId: "mars", payout: 5000 });
const record = await insertDispatch(testApp().app, {
shipId,
pilotId: guest.avatarId,
departAt: clock.now,
contract,
});
clock.now = record.arrivalAt + 60_000;
const state = await getState(app, guest);
// The avatar flew it, so no pilot pay is deducted.
expect(state.company.credits).toBe(1000 + 5000);
expect(state.pilots[0]!.xp).toBeGreaterThan(0);
expect(state.pilots[0]!.status).toBe("idle");
expect(state.ships[0]).toMatchObject({ status: "idle", locationId: "mars", dispatchId: null });
expect(state.locations.find((l) => l.id === "mars")!.discovered).toBe(true);
expect(state.activeDispatches).toHaveLength(0);
expect(state.summaries).toEqual([
expect.objectContaining({
dispatchId: record.id,
credits: 5000,
discoveredLocationId: "mars",
}),
]);
});
it("leaves dispatches that have not arrived yet in flight", async () => {
const { app, clock } = testApp();
const { guest, shipId } = await owner();
const record = await insertDispatch(testApp().app, {
shipId,
pilotId: guest.avatarId,
departAt: clock.now,
});
clock.now = record.arrivalAt - 1;
const state = await getState(app, guest);
expect(state.company.credits).toBe(1000);
expect(state.ships[0]).toMatchObject({
status: "in-flight",
locationId: null,
dispatchId: record.id,
});
expect(state.activeDispatches.map((d) => d.id)).toEqual([record.id]);
});
it("does not apply an arrival twice on a second fetch", async () => {
const { app, clock } = testApp();
const { guest, shipId } = await owner();
const record = await insertDispatch(testApp().app, {
shipId,
pilotId: guest.avatarId,
departAt: clock.now,
});
clock.now = record.arrivalAt;
const first = await getState(app, guest);
const second = await getState(app, guest);
expect(second.company.credits).toBe(first.company.credits);
expect(second.pilots[0]!.xp).toBe(first.pilots[0]!.xp);
expect(first.company.credits).toBe(1000 + 2000);
});
it("applies an arrival exactly once under two concurrent fetches", async () => {
const { app, clock } = testApp();
const { guest, shipId } = await owner();
const record = await insertDispatch(testApp().app, {
shipId,
pilotId: guest.avatarId,
departAt: clock.now,
});
clock.now = record.arrivalAt;
const [a, b] = await Promise.all([getState(app, guest), getState(app, guest)]);
expect(a.company.credits).toBe(3000);
expect(b.company.credits).toBe(3000);
});
it("resolves exactly once when resolution runs again on the same save", async () => {
const { clock } = testApp();
const { guest, shipId } = await owner();
const record = await insertDispatch(testApp().app, {
shipId,
pilotId: guest.avatarId,
departAt: clock.now,
});
const save = await readSave(testApp().app);
const counts = [1, 2, 3].map(() => resolveArrivals(save, record.arrivalAt));
expect(counts).toEqual([1, 0, 0]);
});
it("resolves several arrivals in arrival order", async () => {
const { app, clock } = testApp();
const { guest, shipId } = await owner();
// Fly to the Moon, then (already resolved in the DB as of arrival) on to Earth.
const first = await insertDispatch(testApp().app, {
shipId,
pilotId: guest.avatarId,
departAt: clock.now,
});
clock.now = first.arrivalAt;
await getState(app, guest);
const second = await insertDispatch(testApp().app, {
shipId,
pilotId: guest.avatarId,
departAt: first.arrivalAt,
contract: testContract({ originId: "moon", destinationId: "earth", payout: 700 }),
});
clock.now = second.arrivalAt + 1;
const state = await getState(app, guest);
expect(state.company.credits).toBe(1000 + 2000 + 700);
expect(state.ships[0]!.locationId).toBe("earth");
expect(state.summaries).toHaveLength(2);
});
});