247 lines
9.2 KiB
TypeScript
247 lines
9.2 KiB
TypeScript
import { createGamePostgresConnector } from '@sammo-ts/infra';
|
|
import { describe, expect, test } from 'vitest';
|
|
import { resolveDatabaseUrl } from '../src/scenario/databaseUrl.js';
|
|
import { loadScenarioDefinitionById } from '../src/scenario/scenarioLoader.js';
|
|
import { seedScenarioToDatabase } from '../src/scenario/scenarioSeeder.js';
|
|
|
|
const scenarioId = 1010;
|
|
const schema = process.env.POSTGRES_SCHEMA ?? 'public';
|
|
process.env.POSTGRES_SCHEMA = schema;
|
|
const databaseUrl = await resolveDatabaseUrl({ schema });
|
|
|
|
type ScenarioSeederPrismaClient = {
|
|
$queryRawUnsafe(query: string): Promise<unknown>;
|
|
nation: {
|
|
count(): Promise<number>;
|
|
};
|
|
city: {
|
|
count(): Promise<number>;
|
|
findUnique(args: { where: { id: number } }): Promise<{
|
|
population: number;
|
|
agriculture: number;
|
|
commerce: number;
|
|
security: number;
|
|
trust: number;
|
|
defence: number;
|
|
wall: number;
|
|
} | null>;
|
|
};
|
|
general: {
|
|
count(): Promise<number>;
|
|
findFirst(): Promise<{ age: number; startAge: number; meta: unknown } | null>;
|
|
};
|
|
diplomacy: {
|
|
count(): Promise<number>;
|
|
findFirst(args: {
|
|
where: { srcNationId: number; destNationId: number };
|
|
}): Promise<{ stateCode: number; term: number } | null>;
|
|
};
|
|
event: {
|
|
count(): Promise<number>;
|
|
};
|
|
worldState: {
|
|
findFirst(): Promise<{
|
|
config: unknown;
|
|
meta: unknown;
|
|
tickSeconds: number;
|
|
currentYear: number;
|
|
currentMonth: number;
|
|
} | null>;
|
|
};
|
|
};
|
|
|
|
const requiredTables = [
|
|
'world_state',
|
|
'nation',
|
|
'city',
|
|
'general',
|
|
'diplomacy',
|
|
'troop',
|
|
'event',
|
|
];
|
|
|
|
const hasRequiredTables = async (prisma: ScenarioSeederPrismaClient, schemaName: string): Promise<boolean> => {
|
|
for (const table of requiredTables) {
|
|
const result = (await prisma.$queryRawUnsafe(
|
|
`SELECT to_regclass('${schemaName}.${table}')::text as regclass`
|
|
)) as Array<{ regclass: string | null }>;
|
|
if (!Array.isArray(result) || result.length === 0 || result[0]?.regclass === null) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
|
|
const canConnectToDatabase = async (url: string): Promise<boolean> => {
|
|
const connector = createGamePostgresConnector({ url });
|
|
try {
|
|
await connector.connect();
|
|
const prisma = connector.prisma as unknown as ScenarioSeederPrismaClient;
|
|
await prisma.$queryRawUnsafe('SELECT 1');
|
|
if (!(await hasRequiredTables(prisma, schema))) {
|
|
return false;
|
|
}
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
} finally {
|
|
await connector.disconnect();
|
|
}
|
|
};
|
|
|
|
const canRun = await canConnectToDatabase(databaseUrl);
|
|
const describeDb = describe.runIf(canRun);
|
|
|
|
describeDb('scenario database seed', () => {
|
|
test('writes scenario data into tables', async () => {
|
|
const { seed } = await seedScenarioToDatabase({
|
|
scenarioId,
|
|
databaseUrl,
|
|
});
|
|
|
|
const connector = createGamePostgresConnector({ url: databaseUrl });
|
|
await connector.connect();
|
|
try {
|
|
const prisma = connector.prisma as unknown as ScenarioSeederPrismaClient;
|
|
const [nationCount, cityCount, generalCount, diplomacyCount, eventCount] = await Promise.all([
|
|
prisma.nation.count(),
|
|
prisma.city.count(),
|
|
prisma.general.count(),
|
|
prisma.diplomacy.count(),
|
|
prisma.event.count(),
|
|
]);
|
|
|
|
expect(nationCount).toBe(seed.nations.length);
|
|
expect(cityCount).toBe(seed.cities.length);
|
|
expect(generalCount).toBe(seed.generals.length);
|
|
expect(diplomacyCount).toBe(seed.nations.length * Math.max(0, seed.nations.length - 1));
|
|
expect(eventCount).toBe(seed.events.length);
|
|
expect(generalCount).toBeGreaterThan(0);
|
|
const seededGeneral = await prisma.general.findFirst();
|
|
expect(seededGeneral?.startAge).toBe(seededGeneral?.age);
|
|
expect(seededGeneral?.meta).toEqual(
|
|
expect.objectContaining({
|
|
specage: expect.any(Number),
|
|
specage2: expect.any(Number),
|
|
})
|
|
);
|
|
|
|
const freeCity = seed.cities.find((city) => city.nationId === 0);
|
|
const occupiedCity = seed.cities.find((city) => city.nationId !== 0);
|
|
expect(freeCity).toMatchObject({
|
|
population: freeCity ? Math.round(freeCity.populationMax * 0.7) : undefined,
|
|
agriculture: freeCity ? Math.round(freeCity.agricultureMax * 0.7) : undefined,
|
|
commerce: freeCity ? Math.round(freeCity.commerceMax * 0.7) : undefined,
|
|
security: freeCity ? Math.round(freeCity.securityMax * 0.7) : undefined,
|
|
trust: 80,
|
|
});
|
|
expect(occupiedCity).toMatchObject({
|
|
population: occupiedCity ? Math.round(occupiedCity.populationMax * 0.7) : undefined,
|
|
defence: occupiedCity ? Math.round(occupiedCity.defenceMax * 0.7) : undefined,
|
|
wall: occupiedCity ? Math.round(occupiedCity.wallMax * 0.7) : undefined,
|
|
trust: 80,
|
|
});
|
|
for (const city of [freeCity, occupiedCity]) {
|
|
expect(city).toBeDefined();
|
|
if (!city) {
|
|
continue;
|
|
}
|
|
expect(await prisma.city.findUnique({ where: { id: city.id } })).toMatchObject({
|
|
population: city.population,
|
|
agriculture: city.agriculture,
|
|
commerce: city.commerce,
|
|
security: city.security,
|
|
trust: city.trust,
|
|
defence: city.defence,
|
|
wall: city.wall,
|
|
});
|
|
}
|
|
|
|
if (seed.diplomacy.length > 0) {
|
|
const sample = seed.diplomacy[0];
|
|
const row = await prisma.diplomacy.findFirst({
|
|
where: {
|
|
srcNationId: sample.fromNationId,
|
|
destNationId: sample.toNationId,
|
|
},
|
|
});
|
|
expect(row).not.toBeNull();
|
|
if (row) {
|
|
expect(row.stateCode).toBe(sample.state);
|
|
expect(row.term).toBe(sample.durationMonths);
|
|
}
|
|
const reverse = await prisma.diplomacy.findFirst({
|
|
where: {
|
|
srcNationId: sample.toNationId,
|
|
destNationId: sample.fromNationId,
|
|
},
|
|
});
|
|
expect(reverse).not.toBeNull();
|
|
if (reverse) {
|
|
expect(reverse.stateCode).toBe(sample.state);
|
|
expect(reverse.term).toBe(sample.durationMonths);
|
|
}
|
|
}
|
|
} finally {
|
|
await connector.disconnect();
|
|
}
|
|
});
|
|
|
|
test('applies install options to world state', async () => {
|
|
const scenario = await loadScenarioDefinitionById(scenarioId);
|
|
const { seed } = await seedScenarioToDatabase({
|
|
scenarioId,
|
|
databaseUrl,
|
|
now: new Date('2030-01-01T00:00:00Z'),
|
|
installOptions: {
|
|
turnTermMinutes: 60,
|
|
sync: false,
|
|
fiction: 1,
|
|
extend: false,
|
|
blockGeneralCreate: 2,
|
|
npcMode: 0,
|
|
showImgLevel: 3,
|
|
tournamentTrig: true,
|
|
joinMode: 'full',
|
|
autorunUser: {
|
|
limitMinutes: 60,
|
|
options: {
|
|
develop: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
// Future-born entries are converted to delayed events, so the raw
|
|
// scenario array length is not the installed general count.
|
|
expect(seed.generals.length).toBeGreaterThan(0);
|
|
expect(seed.generals.length).toBeLessThan(
|
|
scenario.generals.length + scenario.generalsNeutral.length + scenario.generalsEx.length
|
|
);
|
|
|
|
const connector = createGamePostgresConnector({ url: databaseUrl });
|
|
await connector.connect();
|
|
try {
|
|
const prisma = connector.prisma as unknown as ScenarioSeederPrismaClient;
|
|
const worldState = await prisma.worldState.findFirst();
|
|
expect(worldState).not.toBeNull();
|
|
if (!worldState) {
|
|
return;
|
|
}
|
|
expect(worldState.tickSeconds).toBe(3600);
|
|
expect(worldState.currentMonth).toBe(1);
|
|
|
|
const config = (worldState.config ?? {}) as Record<string, unknown>;
|
|
expect(config.extendedGeneral).toBe(false);
|
|
expect(config.joinMode).toBe('full');
|
|
|
|
const meta = (worldState.meta ?? {}) as Record<string, unknown>;
|
|
const autorun = (meta.autorun_user ?? {}) as Record<string, unknown>;
|
|
const autorunOptions = (autorun.options ?? {}) as Record<string, unknown>;
|
|
expect(autorunOptions.develop).toBe(true);
|
|
} finally {
|
|
await connector.disconnect();
|
|
}
|
|
});
|
|
});
|