146 lines
4.9 KiB
TypeScript
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);
|
|
});
|
|
});
|