merge: preserve pre-month notice log date
This commit is contained in:
@@ -32,7 +32,7 @@ export const createProcessIncomeActionHandler = (incomeHandler: IncomeHandler):
|
||||
export const createNoticeToHistoryLogHandler = (options: {
|
||||
getWorld: () => InMemoryTurnWorld | null;
|
||||
}): MonthlyEventActionHandler => {
|
||||
return (args) => {
|
||||
return (args, environment) => {
|
||||
const text = args[0];
|
||||
if (typeof text !== 'string') {
|
||||
throw new Error('NoticeToHistoryLog message must be a string.');
|
||||
@@ -46,6 +46,8 @@ export const createNoticeToHistoryLogHandler = (options: {
|
||||
category: LogCategory.HISTORY,
|
||||
text,
|
||||
format: parseLogFormat(args[1]),
|
||||
year: environment.year,
|
||||
month: environment.month,
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,341 @@
|
||||
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
||||
import { createGamePostgresConnector, type GamePrismaClient } from '@sammo-ts/infra';
|
||||
|
||||
import { createTurnDaemonRuntime } from '../src/turn/turnDaemon.js';
|
||||
|
||||
const databaseUrl = process.env.INPUT_EVENT_DATABASE_URL;
|
||||
const integration = describe.skipIf(!databaseUrl);
|
||||
const profile = 'monthly-catalog-boundary';
|
||||
const eventIds = [991_001, 991_002] as const;
|
||||
|
||||
const scenarioConfig = {
|
||||
stat: { total: 300, min: 10, max: 100, npcTotal: 150, npcMax: 50, npcMin: 10, chiefMin: 70 },
|
||||
iconPath: '.',
|
||||
map: {},
|
||||
const: {},
|
||||
environment: { mapName: 'che', unitSet: 'che' },
|
||||
};
|
||||
|
||||
integration('monthly catalog boundary persistence', () => {
|
||||
let db: GamePrismaClient;
|
||||
let closeDb: (() => Promise<void>) | undefined;
|
||||
|
||||
const clean = async () => {
|
||||
await db.logEntry.deleteMany({});
|
||||
await db.yearbookHistory.deleteMany({});
|
||||
await db.generalTurn.deleteMany({});
|
||||
await db.nationTurn.deleteMany({});
|
||||
await db.rankData.deleteMany({});
|
||||
await db.generalAccessLog.deleteMany({});
|
||||
await db.diplomacy.deleteMany({});
|
||||
await db.event.deleteMany({});
|
||||
await db.general.deleteMany({});
|
||||
await db.nation.deleteMany({});
|
||||
await db.city.deleteMany({});
|
||||
await db.worldState.deleteMany({});
|
||||
await db.turnDaemonLease.deleteMany({ where: { profile } });
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
const connector = createGamePostgresConnector({ url: databaseUrl! });
|
||||
await connector.connect();
|
||||
db = connector.prisma;
|
||||
closeDb = () => connector.disconnect();
|
||||
await clean();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await clean();
|
||||
await closeDb?.();
|
||||
});
|
||||
|
||||
it('matches the ref PRE_MONTH, month actions, and post-month projection in one daemon boundary', async () => {
|
||||
await db.city.createMany({
|
||||
data: [
|
||||
{
|
||||
id: 1,
|
||||
name: '경계도시',
|
||||
nationId: 1,
|
||||
level: 1,
|
||||
supplyState: 1,
|
||||
frontState: 2,
|
||||
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: 50,
|
||||
trade: 100,
|
||||
defence: 1_000,
|
||||
defenceMax: 2_000,
|
||||
wall: 1_000,
|
||||
wallMax: 2_000,
|
||||
region: 1,
|
||||
conflict: {},
|
||||
meta: { state: 31, term: 1, officer_set: 1 },
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '중립도시',
|
||||
nationId: 0,
|
||||
level: 8,
|
||||
supplyState: 0,
|
||||
frontState: 0,
|
||||
population: 12_000,
|
||||
populationMax: 22_000,
|
||||
agriculture: 1_200,
|
||||
agricultureMax: 2_200,
|
||||
commerce: 1_200,
|
||||
commerceMax: 2_200,
|
||||
security: 1_200,
|
||||
securityMax: 2_200,
|
||||
trust: 55,
|
||||
trade: 100,
|
||||
defence: 1_200,
|
||||
defenceMax: 2_200,
|
||||
wall: 1_200,
|
||||
wallMax: 2_200,
|
||||
region: 1,
|
||||
conflict: {},
|
||||
meta: { state: 0, term: 0, officer_set: 1 },
|
||||
},
|
||||
],
|
||||
});
|
||||
await db.nation.create({
|
||||
data: {
|
||||
id: 1,
|
||||
name: '경계국',
|
||||
color: '#123456',
|
||||
capitalCityId: 1,
|
||||
chiefGeneralId: 1,
|
||||
gold: 10_000,
|
||||
rice: 20_000,
|
||||
tech: 100,
|
||||
level: 2,
|
||||
typeCode: 'che_유가',
|
||||
meta: {
|
||||
power: 10,
|
||||
gennum: 1,
|
||||
bill: 100,
|
||||
rate: 20,
|
||||
rate_tmp: 10,
|
||||
scout: 1,
|
||||
chief_set: 1,
|
||||
strategic_cmd_limit: 2,
|
||||
surlimit: 1,
|
||||
spy: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
await db.general.createMany({
|
||||
data: [
|
||||
{
|
||||
id: 1,
|
||||
name: '경계군주',
|
||||
nationId: 1,
|
||||
cityId: 1,
|
||||
officerLevel: 12,
|
||||
npcState: 0,
|
||||
leadership: 50,
|
||||
strength: 50,
|
||||
intel: 50,
|
||||
age: 30,
|
||||
bornYear: 160,
|
||||
deadYear: 250,
|
||||
gold: 1_000,
|
||||
rice: 1_000,
|
||||
crew: 100,
|
||||
crewTypeId: 1100,
|
||||
train: 80,
|
||||
atmos: 70,
|
||||
turnTime: new Date('2026-07-25T01:50:00.000Z'),
|
||||
meta: { belong: 3, makelimit: 2, killturn: 0 },
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '경계재야',
|
||||
nationId: 0,
|
||||
cityId: 2,
|
||||
officerLevel: 0,
|
||||
npcState: 0,
|
||||
leadership: 40,
|
||||
strength: 40,
|
||||
intel: 40,
|
||||
age: 25,
|
||||
bornYear: 165,
|
||||
deadYear: 250,
|
||||
gold: 500,
|
||||
rice: 500,
|
||||
crew: 50,
|
||||
crewTypeId: 1100,
|
||||
train: 60,
|
||||
atmos: 50,
|
||||
turnTime: new Date('2026-07-25T01:50:00.000Z'),
|
||||
meta: { belong: 4, makelimit: 0, killturn: 0 },
|
||||
},
|
||||
],
|
||||
});
|
||||
await db.generalTurn.createMany({
|
||||
data: [1, 2].flatMap((generalId) =>
|
||||
Array.from({ length: 30 }, (_, turnIdx) => ({
|
||||
generalId,
|
||||
turnIdx,
|
||||
actionCode: '휴식',
|
||||
arg: {},
|
||||
}))
|
||||
),
|
||||
});
|
||||
await db.event.createMany({
|
||||
data: [
|
||||
{
|
||||
id: eventIds[0],
|
||||
targetCode: 'PRE_MONTH',
|
||||
priority: 1_000,
|
||||
condition: true,
|
||||
action: [['NoticeToHistoryLog', 'aggregate-pre', 6]],
|
||||
meta: {},
|
||||
},
|
||||
{
|
||||
id: eventIds[1],
|
||||
targetCode: 'MONTH',
|
||||
priority: 1_000,
|
||||
condition: true,
|
||||
action: [
|
||||
['ProcessIncome', 'gold'],
|
||||
['NewYear'],
|
||||
['ResetOfficerLock'],
|
||||
['RandomizeCityTradeRate'],
|
||||
['ChangeCity', 'all', { pop: '+10', trust: 60 }],
|
||||
['NoticeToHistoryLog', 'aggregate-month', 6],
|
||||
['DeleteEvent'],
|
||||
],
|
||||
meta: {},
|
||||
},
|
||||
],
|
||||
});
|
||||
await db.worldState.create({
|
||||
data: {
|
||||
scenarioCode: profile,
|
||||
currentYear: 190,
|
||||
currentMonth: 12,
|
||||
tickSeconds: 600,
|
||||
config: scenarioConfig,
|
||||
meta: {
|
||||
hiddenSeed: 'monthly-boundary-core-catalog',
|
||||
lastTurnTime: '2026-07-25T01:50:00.000Z',
|
||||
isunited: 1,
|
||||
refreshLimit: 3,
|
||||
block_change_scout: false,
|
||||
develcost: 18,
|
||||
scenarioMeta: {
|
||||
title: 'monthly catalog boundary',
|
||||
startYear: 190,
|
||||
life: null,
|
||||
fiction: null,
|
||||
history: [],
|
||||
ignoreDefaultEvents: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const runtime = await createTurnDaemonRuntime({
|
||||
profile,
|
||||
databaseUrl: databaseUrl!,
|
||||
enableLeaseHeartbeat: false,
|
||||
});
|
||||
try {
|
||||
await runtime.world.advanceMonth(new Date('2026-07-25T02:00:00.000Z'));
|
||||
await runtime.hooks?.flushChanges?.({
|
||||
lastTurnTime: runtime.world.getState().lastTurnTime.toISOString(),
|
||||
processedGenerals: 0,
|
||||
processedTurns: 1,
|
||||
durationMs: 0,
|
||||
partial: false,
|
||||
});
|
||||
|
||||
expect(await db.worldState.findFirstOrThrow()).toMatchObject({
|
||||
currentYear: 191,
|
||||
currentMonth: 1,
|
||||
meta: expect.objectContaining({ develcost: 20 }),
|
||||
});
|
||||
expect(
|
||||
await db.general.findMany({
|
||||
orderBy: { id: 'asc' },
|
||||
select: { id: true, age: true, gold: true, meta: true },
|
||||
})
|
||||
).toEqual([
|
||||
{ id: 1, age: 31, gold: 1_400, meta: expect.objectContaining({ belong: 4, makelimit: 1 }) },
|
||||
{ id: 2, age: 26, gold: 500, meta: expect.objectContaining({ belong: 4, makelimit: 0 }) },
|
||||
]);
|
||||
expect(
|
||||
await db.city.findMany({
|
||||
orderBy: { id: 'asc' },
|
||||
select: {
|
||||
id: true,
|
||||
population: true,
|
||||
trust: true,
|
||||
trade: true,
|
||||
frontState: true,
|
||||
meta: true,
|
||||
},
|
||||
})
|
||||
).toEqual([
|
||||
{
|
||||
id: 1,
|
||||
population: 10_010,
|
||||
trust: 60,
|
||||
trade: null,
|
||||
frontState: 0,
|
||||
meta: expect.objectContaining({ state: 0, term: 0, officer_set: 0 }),
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
population: 12_010,
|
||||
trust: 60,
|
||||
trade: 99,
|
||||
frontState: 0,
|
||||
meta: expect.objectContaining({ state: 0, term: 0, officer_set: 0 }),
|
||||
},
|
||||
]);
|
||||
expect(await db.nation.findUniqueOrThrow({ where: { id: 1 } })).toMatchObject({
|
||||
gold: 9_753,
|
||||
rice: 20_000,
|
||||
meta: expect.objectContaining({
|
||||
power: 68,
|
||||
gennum: 1,
|
||||
prev_income_gold: 153,
|
||||
rate_tmp: 20,
|
||||
strategic_cmd_limit: 1,
|
||||
surlimit: 0,
|
||||
chief_set: 0,
|
||||
}),
|
||||
});
|
||||
expect(
|
||||
await db.logEntry.findMany({
|
||||
orderBy: { id: 'asc' },
|
||||
select: { year: true, month: true, text: true },
|
||||
})
|
||||
).toEqual([
|
||||
{ year: 190, month: 12, text: '<S>◆</>190년 12월:aggregate-pre' },
|
||||
{ year: 191, month: 1, text: '<C>●</>이번 수입은 금 <C>153</>입니다.' },
|
||||
{ year: 191, month: 1, text: '<C>●</>봉급으로 금 <C>400</>을 받았습니다.' },
|
||||
{
|
||||
year: 191,
|
||||
month: 1,
|
||||
text: '<C>●</>191년 1월:<W><b>【지급】</b></>봄이 되어 봉록에 따라 자금이 지급됩니다.',
|
||||
},
|
||||
{ year: 191, month: 1, text: '<C>●</>1월:<C>191</>년이 되었습니다.' },
|
||||
{ year: 191, month: 1, text: '<S>◆</>191년 1월:aggregate-month' },
|
||||
]);
|
||||
expect(await db.event.findMany({ select: { id: true }, orderBy: { id: 'asc' } })).toEqual([
|
||||
{ id: eventIds[0] },
|
||||
]);
|
||||
} finally {
|
||||
await runtime.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -186,6 +186,8 @@ describe('core monthly event actions at the real month boundary', () => {
|
||||
category: LogCategory.HISTORY,
|
||||
text: '<S>새해 알림</>',
|
||||
format: LogFormat.EVENT_YEAR_MONTH,
|
||||
year: 191,
|
||||
month: 1,
|
||||
},
|
||||
{
|
||||
scope: LogScope.SYSTEM,
|
||||
|
||||
Reference in New Issue
Block a user