91 lines
3.5 KiB
TypeScript
91 lines
3.5 KiB
TypeScript
// Ported from apps/server/test (CR-014 Step 14.5): now runs on the engine.
|
|
import { content } from "@deadhead/content";
|
|
import { describe, expect, it } from "vitest";
|
|
import {
|
|
useTestApp,
|
|
signUp,
|
|
call,
|
|
getState,
|
|
setCredits,
|
|
testContract,
|
|
putBoard,
|
|
makeOwner,
|
|
signUpOwner,
|
|
} from "../support";
|
|
|
|
const testApp = useTestApp();
|
|
|
|
describe("concurrency and money safety", () => {
|
|
it("lets only one of two simultaneous purchases through when only one is affordable", async () => {
|
|
const { app } = testApp();
|
|
const guest = await signUpOwner(app);
|
|
const price = content.shipModel("courier")!.price;
|
|
await setCredits(testApp().app, price + price / 2);
|
|
|
|
const responses = await Promise.all([
|
|
call(app, guest, "POST", "/api/ships", { modelId: "courier", bayId: guest.earthBayId }),
|
|
call(app, guest, "POST", "/api/ships", { modelId: "courier", bayId: guest.earthBayId }),
|
|
]);
|
|
expect(responses.map((r) => r.statusCode).sort()).toEqual([200, 409]);
|
|
|
|
const state = await getState(app, guest);
|
|
// The owner already had one ship.
|
|
expect(state.ships).toHaveLength(2);
|
|
expect(state.company.credits).toBe(price / 2);
|
|
});
|
|
|
|
it("never goes negative under many simultaneous purchases", async () => {
|
|
const { app } = testApp();
|
|
const guest = await signUpOwner(app);
|
|
const price = content.shipModel("courier")!.price;
|
|
await setCredits(testApp().app, price * 3);
|
|
|
|
const responses = await Promise.all(
|
|
Array.from({ length: 8 }, () =>
|
|
call(app, guest, "POST", "/api/ships", { modelId: "courier", bayId: guest.earthBayId }),
|
|
),
|
|
);
|
|
expect(responses.filter((r) => r.statusCode === 200)).toHaveLength(3);
|
|
const state = await getState(app, guest);
|
|
expect(state.ships).toHaveLength(1 + 3);
|
|
expect(state.company.credits).toBe(0);
|
|
});
|
|
|
|
it("accepts a contract only once when dispatched twice at the same moment", async () => {
|
|
const { app } = testApp();
|
|
const guest = await signUp(app);
|
|
const { shipId } = await makeOwner(app, guest.avatarId);
|
|
await setCredits(app, 10_000);
|
|
const contract = testContract();
|
|
await putBoard(app, [contract]);
|
|
|
|
const body = { kind: "contract", contractId: contract.id, shipId, pilotId: guest.avatarId };
|
|
const responses = await Promise.all([
|
|
call(app, guest, "POST", "/api/dispatches", body),
|
|
call(app, guest, "POST", "/api/dispatches", body),
|
|
]);
|
|
expect(responses.map((r) => r.statusCode).sort()).toEqual([200, 409]);
|
|
const state = await getState(app, guest);
|
|
expect(state.activeDispatches).toHaveLength(1);
|
|
// One leg's fuel left the tank, once; no credits were charged (CR-015).
|
|
expect(state.company.credits).toBe(10_000);
|
|
expect(state.ships[0]!.fuelFu).toBeCloseTo(
|
|
state.ships[0]!.stats.tankCapacityFu - state.activeDispatches[0]!.fuelBurnedFu,
|
|
);
|
|
});
|
|
|
|
it("leases one bay when the same lease is sent twice at once", async () => {
|
|
const { app } = testApp();
|
|
const guest = await signUpOwner(app);
|
|
await setCredits(testApp().app, 100_000);
|
|
const responses = await Promise.all([
|
|
call(app, guest, "POST", "/api/bays", { locationId: "moon" }),
|
|
call(app, guest, "POST", "/api/bays", { locationId: "moon" }),
|
|
]);
|
|
expect(responses.map((r) => r.statusCode).sort()).toEqual([200, 409]);
|
|
const state = await getState(app, guest);
|
|
expect(state.bays.map((b) => b.locationId).sort()).toEqual(["earth", "moon"]);
|
|
expect(state.company.credits).toBe(100_000 - content.location("moon")!.bayLeasePrice);
|
|
});
|
|
});
|