188 lines
6.3 KiB
TypeScript
188 lines
6.3 KiB
TypeScript
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
|
|
|
import { createGamePostgresConnector, type GamePrismaClient } from '@sammo-ts/infra';
|
|
|
|
import { readCoreDatabaseSnapshot } from '../src/turn-differential/databaseSnapshot.js';
|
|
import { captureCoreDatabaseTurnTrace } from '../src/turn-differential/trace.js';
|
|
|
|
const databaseUrl = process.env.TURN_DIFFERENTIAL_DATABASE_URL ?? process.env.INPUT_EVENT_DATABASE_URL;
|
|
const integration = describe.skipIf(!databaseUrl);
|
|
const ids = {
|
|
general: 2_147_000_101,
|
|
city: 2_147_000_102,
|
|
nation: 2_147_000_103,
|
|
};
|
|
|
|
integration('core2026 turn state database snapshot adapter', () => {
|
|
let db: GamePrismaClient;
|
|
let disconnect: (() => Promise<void>) | undefined;
|
|
let createdWorldId: number | null = null;
|
|
|
|
beforeAll(async () => {
|
|
const connector = createGamePostgresConnector({ url: databaseUrl! });
|
|
await connector.connect();
|
|
db = connector.prisma;
|
|
disconnect = () => connector.disconnect();
|
|
|
|
const world = await db.worldState.findFirst({ orderBy: { id: 'asc' } });
|
|
if (!world) {
|
|
createdWorldId = (
|
|
await db.worldState.create({
|
|
data: {
|
|
scenarioCode: 'turn-differential-adapter',
|
|
currentYear: 183,
|
|
currentMonth: 1,
|
|
tickSeconds: 600,
|
|
config: {},
|
|
meta: { lastTurnTime: '0183-01-01T00:00:00.000Z', isUnited: 0 },
|
|
},
|
|
})
|
|
).id;
|
|
}
|
|
await db.nation.create({
|
|
data: {
|
|
id: ids.nation,
|
|
name: '비교국',
|
|
color: '#123456',
|
|
capitalCityId: ids.city,
|
|
gold: 100,
|
|
rice: 200,
|
|
tech: 10,
|
|
level: 1,
|
|
typeCode: 'che_중립',
|
|
meta: { gennum: 1, power: 300, war: 0 },
|
|
},
|
|
});
|
|
await db.city.create({
|
|
data: {
|
|
id: ids.city,
|
|
name: '비교도시',
|
|
level: 5,
|
|
nationId: ids.nation,
|
|
population: 10_000,
|
|
populationMax: 20_000,
|
|
agriculture: 1_000,
|
|
agricultureMax: 2_000,
|
|
commerce: 1_000,
|
|
commerceMax: 2_000,
|
|
security: 1_000,
|
|
securityMax: 2_000,
|
|
trust: 80,
|
|
trade: 100,
|
|
defence: 500,
|
|
defenceMax: 1_000,
|
|
wall: 500,
|
|
wallMax: 1_000,
|
|
region: 1,
|
|
meta: { state: 0, term: 0 },
|
|
},
|
|
});
|
|
await db.general.create({
|
|
data: {
|
|
id: ids.general,
|
|
name: '비교장수',
|
|
nationId: ids.nation,
|
|
cityId: ids.city,
|
|
leadership: 70,
|
|
strength: 60,
|
|
intel: 80,
|
|
gold: 1_000,
|
|
rice: 1_000,
|
|
crew: 500,
|
|
crewTypeId: 1100,
|
|
train: 90,
|
|
atmos: 90,
|
|
turnTime: new Date('0183-01-01T00:00:00.000Z'),
|
|
lastTurn: { command: '휴식' },
|
|
meta: { killturn: 24, myset: 6, intel_exp: 3 },
|
|
},
|
|
});
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await db.general.deleteMany({ where: { id: ids.general } });
|
|
await db.city.deleteMany({ where: { id: ids.city } });
|
|
await db.nation.deleteMany({ where: { id: ids.nation } });
|
|
if (createdWorldId !== null) {
|
|
await db.worldState.deleteMany({ where: { id: createdWorldId } });
|
|
}
|
|
await disconnect?.();
|
|
});
|
|
|
|
it('projects selected PostgreSQL rows into the canonical comparison schema', async () => {
|
|
const result = await readCoreDatabaseSnapshot(databaseUrl!, {
|
|
generalIds: [ids.general],
|
|
cityIds: [ids.city],
|
|
nationIds: [ids.nation],
|
|
});
|
|
|
|
expect(result.engine).toBe('core2026');
|
|
expect(result.generals).toContainEqual(
|
|
expect.objectContaining({
|
|
id: ids.general,
|
|
nationId: ids.nation,
|
|
cityId: ids.city,
|
|
intelligence: 80,
|
|
killTurn: 24,
|
|
mySet: 6,
|
|
})
|
|
);
|
|
expect(result.cities).toContainEqual(
|
|
expect.objectContaining({
|
|
id: ids.city,
|
|
agriculture: 1_000,
|
|
defence: 500,
|
|
state: 0,
|
|
term: 0,
|
|
})
|
|
);
|
|
expect(result.nations).toContainEqual(
|
|
expect.objectContaining({
|
|
id: ids.nation,
|
|
generalCount: 1,
|
|
power: 300,
|
|
})
|
|
);
|
|
});
|
|
|
|
it('captures before/after state around a real database execution boundary', async () => {
|
|
const trace = await captureCoreDatabaseTurnTrace(
|
|
databaseUrl!,
|
|
{
|
|
kind: 'general',
|
|
actorGeneralId: ids.general,
|
|
action: 'che_농지개간',
|
|
args: null,
|
|
observe: {
|
|
generalIds: [ids.general],
|
|
cityIds: [ids.city],
|
|
nationIds: [ids.nation],
|
|
},
|
|
},
|
|
async () => {
|
|
await db.$transaction([
|
|
db.general.update({
|
|
where: { id: ids.general },
|
|
data: { gold: { decrement: 10 } },
|
|
}),
|
|
db.city.update({
|
|
where: { id: ids.city },
|
|
data: { agriculture: { increment: 42 } },
|
|
}),
|
|
]);
|
|
return { outcome: { ok: true } };
|
|
}
|
|
);
|
|
|
|
expect(trace.before.generals[0]?.gold).toBe(1_000);
|
|
expect(trace.after.generals[0]?.gold).toBe(990);
|
|
expect(trace.before.cities[0]?.agriculture).toBe(1_000);
|
|
expect(trace.after.cities[0]?.agriculture).toBe(1_042);
|
|
expect(trace.execution).toMatchObject({
|
|
kind: 'general',
|
|
action: 'che_농지개간',
|
|
seedDomain: 'generalCommand',
|
|
});
|
|
});
|
|
});
|