122 lines
3.9 KiB
TypeScript
122 lines
3.9 KiB
TypeScript
import { randomUUID } from "node:crypto";
|
|
import { eq, sql } from "drizzle-orm";
|
|
import { afterAll, beforeAll, beforeEach } from "vitest";
|
|
import { createDatabase, type Database, type Db } from "../src/db/client";
|
|
import { runMigrations } from "../src/db/migrate";
|
|
import { user } from "../src/db/auth-schema";
|
|
import { bays, companies, pilots, players, ships } from "../src/db/schema";
|
|
import { createPlayer, type NewPlayer } from "../src/game/new-player";
|
|
import { testConfig } from "./helpers";
|
|
|
|
export const TEST_NOW = Date.parse("2100-01-01T00:00:00Z");
|
|
|
|
/**
|
|
* Connects a test file to the test database: migrates once, empties every
|
|
* table before each test, and closes the pool at the end. Use `database()`
|
|
* inside tests.
|
|
*/
|
|
export function useTestDatabase(): () => Database {
|
|
let database: Database | undefined;
|
|
const url = testConfig().databaseUrl;
|
|
|
|
beforeAll(async () => {
|
|
await runMigrations(url);
|
|
database = createDatabase(url);
|
|
});
|
|
beforeEach(async () => {
|
|
await resetDatabase(database!.db);
|
|
});
|
|
afterAll(async () => {
|
|
await database?.close();
|
|
});
|
|
|
|
return () => {
|
|
if (!database) throw new Error("useTestDatabase: database not ready (use it inside tests)");
|
|
return database;
|
|
};
|
|
}
|
|
|
|
/** Empties every table in the public schema. */
|
|
export async function resetDatabase(db: Db): Promise<void> {
|
|
const result = await db.execute<{ tablename: string }>(
|
|
sql`select tablename from pg_tables where schemaname = 'public'`,
|
|
);
|
|
const names = result.rows.map((r) => `"${r.tablename}"`);
|
|
if (names.length > 0) await db.execute(sql.raw(`truncate ${names.join(", ")} cascade`));
|
|
}
|
|
|
|
export type SeedState = "tutorial" | "owner";
|
|
|
|
export interface SeededPlayer extends NewPlayer {
|
|
/** The Courier owned in the "owner" state. */
|
|
shipId?: string;
|
|
}
|
|
|
|
/**
|
|
* Creates a player in a named state:
|
|
* - `tutorial`: brand new player (what guest sign-up creates).
|
|
* - `owner`: tutorial over, owns one Courier at Earth crewed by the avatar, has an Earth bay.
|
|
*/
|
|
export async function seedPlayer(
|
|
db: Db,
|
|
state: SeedState,
|
|
options: { worldSeed?: number; credits?: number } = {},
|
|
): Promise<SeededPlayer> {
|
|
const userId = randomUUID();
|
|
await db.insert(user).values({
|
|
id: userId,
|
|
name: "Guest",
|
|
email: `${userId}@guest.test`,
|
|
isAnonymous: true,
|
|
});
|
|
const player = await createPlayer(db, {
|
|
userId,
|
|
worldSeed: options.worldSeed ?? 12345,
|
|
now: TEST_NOW,
|
|
});
|
|
if (options.credits !== undefined) await setCredits(db, player.playerId, options.credits);
|
|
if (state === "tutorial") return player;
|
|
return { ...player, ...(await makeOwner(db, player.playerId, player.avatarId)) };
|
|
}
|
|
|
|
export async function setCredits(db: Db, playerId: string, credits: number): Promise<void> {
|
|
await db.update(companies).set({ credits }).where(eq(companies.playerId, playerId));
|
|
}
|
|
|
|
/**
|
|
* Turns a tutorial player into an owner: tutorial over, one Courier at Earth
|
|
* crewed by the avatar, an Earth bay. Returns the ship and bay ids.
|
|
*/
|
|
export async function makeOwner(
|
|
db: Db,
|
|
playerId: string,
|
|
avatarId: string,
|
|
): Promise<{ shipId: string; earthBayId: string }> {
|
|
const shipId = randomUUID();
|
|
const earthBayId = randomUUID();
|
|
await db.transaction(async (tx) => {
|
|
await tx
|
|
.update(players)
|
|
.set({ tutorialActive: false, loanerLocationId: null })
|
|
.where(eq(players.id, playerId));
|
|
await tx.insert(ships).values({
|
|
id: shipId,
|
|
playerId,
|
|
modelId: "courier",
|
|
name: "Test Courier",
|
|
locationId: "earth",
|
|
// Stands in for the deal's Courier (not a Market purchase, CR-013).
|
|
fromDeal: true,
|
|
createdAt: TEST_NOW,
|
|
});
|
|
await tx
|
|
.update(pilots)
|
|
.set({ assignedShipId: shipId, locationId: null })
|
|
.where(eq(pilots.id, avatarId));
|
|
await tx
|
|
.insert(bays)
|
|
.values({ id: earthBayId, playerId, locationId: "earth", createdAt: TEST_NOW });
|
|
});
|
|
return { shipId, earthBayId };
|
|
}
|