112 lines
4.2 KiB
TypeScript
112 lines
4.2 KiB
TypeScript
import { content } from "@deadhead/content";
|
|
import { errorResponseSchema, playerStateSchema } from "@deadhead/schema";
|
|
import { eq } from "drizzle-orm";
|
|
import { describe, expect, it } from "vitest";
|
|
import { bays, companies, pilots, players, ships } from "../src/db/schema";
|
|
import { useTestDatabase } from "./db";
|
|
import { call, getState, signUp, useTestApp, type Guest } from "./game";
|
|
|
|
const database = useTestDatabase();
|
|
const testApp = useTestApp(database);
|
|
|
|
/** Fast-forwards a new guest to "four runs done, loaner back at Earth". */
|
|
async function readyForDeal(guest: Guest) {
|
|
await database()
|
|
.db.update(players)
|
|
.set({
|
|
tutorialRunsCompleted: content.config.tutorialRunsBeforeDeal,
|
|
loanerLocationId: "earth",
|
|
})
|
|
.where(eq(players.id, guest.playerId));
|
|
}
|
|
|
|
const accept = (guest: Guest) => call(testApp().app, guest, "POST", "/api/deal/accept");
|
|
|
|
/** Everything the deal touches, read straight from the database. */
|
|
async function snapshot(guest: Guest) {
|
|
const { db } = database();
|
|
return {
|
|
player: (await db.select().from(players).where(eq(players.id, guest.playerId)))[0],
|
|
company: (await db.select().from(companies).where(eq(companies.playerId, guest.playerId)))[0],
|
|
ships: await db.select().from(ships).where(eq(ships.playerId, guest.playerId)),
|
|
bays: await db.select().from(bays).where(eq(bays.playerId, guest.playerId)),
|
|
pilots: await db.select().from(pilots).where(eq(pilots.playerId, guest.playerId)),
|
|
};
|
|
}
|
|
|
|
describe("accepting the deal", () => {
|
|
it("hands over a crewed Courier at Earth, an Earth bay and the loan, and ends the tutorial", async () => {
|
|
const guest = await signUp(testApp().app);
|
|
await readyForDeal(guest);
|
|
expect((await getState(testApp().app, guest)).tutorial?.dealAvailable).toBe(true);
|
|
|
|
const response = await accept(guest);
|
|
expect(response.statusCode).toBe(200);
|
|
const state = playerStateSchema.parse(response.json());
|
|
|
|
expect(state.tutorial).toBeNull();
|
|
expect(state.ships).toHaveLength(1);
|
|
const [ship] = state.ships;
|
|
expect(ship).toMatchObject({
|
|
modelId: "courier",
|
|
status: "idle",
|
|
locationId: "earth",
|
|
pilotId: guest.avatarId,
|
|
});
|
|
expect(state.bays.map((b) => b.locationId)).toEqual(["earth"]);
|
|
expect(state.pilots[0]).toMatchObject({
|
|
isAvatar: true,
|
|
assignedShipId: ship!.id,
|
|
locationId: "earth",
|
|
});
|
|
expect(state.company.loanBalance).toBe(content.dealLoanAmount);
|
|
|
|
const after = await snapshot(guest);
|
|
expect(after.player).toMatchObject({
|
|
tutorialActive: false,
|
|
dealAccepted: true,
|
|
loanerLocationId: null,
|
|
});
|
|
});
|
|
|
|
it("fails with no changes when the deal is not available", async () => {
|
|
const guest = await signUp(testApp().app);
|
|
const before = await snapshot(guest);
|
|
const response = await accept(guest);
|
|
expect(response.statusCode).toBe(409);
|
|
expect(errorResponseSchema.parse(response.json()).error.code).toBe("deal-not-available");
|
|
expect(await snapshot(guest)).toEqual(before);
|
|
});
|
|
|
|
it("fails while the loaner is at the Moon", async () => {
|
|
const guest = await signUp(testApp().app);
|
|
await readyForDeal(guest);
|
|
await database()
|
|
.db.update(players)
|
|
.set({ loanerLocationId: "moon" })
|
|
.where(eq(players.id, guest.playerId));
|
|
expect((await accept(guest)).statusCode).toBe(409);
|
|
});
|
|
|
|
it("fails the second time", async () => {
|
|
const guest = await signUp(testApp().app);
|
|
await readyForDeal(guest);
|
|
expect((await accept(guest)).statusCode).toBe(200);
|
|
const before = await snapshot(guest);
|
|
const again = await accept(guest);
|
|
expect(errorResponseSchema.parse(again.json()).error.code).toBe("deal-not-available");
|
|
expect(await snapshot(guest)).toEqual(before);
|
|
});
|
|
|
|
it("produces exactly one deal from two simultaneous accepts", async () => {
|
|
const guest = await signUp(testApp().app);
|
|
await readyForDeal(guest);
|
|
const responses = await Promise.all([accept(guest), accept(guest)]);
|
|
expect(responses.map((r) => r.statusCode).sort()).toEqual([200, 409]);
|
|
const after = await snapshot(guest);
|
|
expect(after.ships).toHaveLength(1);
|
|
expect(after.bays).toHaveLength(1);
|
|
expect(after.company!.loanBalance).toBe(content.dealLoanAmount);
|
|
});
|
|
});
|