감사 정책 head 유실을 복구하고 데몬 준비 상태로 배포를 검증한다
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { initializeAuditPolicies } from '../src/playAudit/policy.js';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import type { City, Nation } from '@sammo-ts/logic';
|
||||
|
||||
@@ -186,6 +187,31 @@ describe('invader monthly actions', () => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('preserves new nation audit heads through ruler initialization and restart', async () => {
|
||||
const harness = buildHarness();
|
||||
const { world, environment } = harness;
|
||||
world.updateWorldMeta({ serverId: 'new-nation-audit' });
|
||||
initializeAuditPolicies(world);
|
||||
const handler = createRaiseInvaderHandler({
|
||||
getWorld: () => world,
|
||||
reservedTurns: harness.reservedTurns,
|
||||
env: buildCommandEnv(scenarioConfig),
|
||||
});
|
||||
await handler([10, 150, 100, 20], environment, event);
|
||||
const created = world.peekDirtyState().createdNations;
|
||||
expect(created.length).toBeGreaterThan(0);
|
||||
for (const nation of created) {
|
||||
expect(Object.keys(nation.meta._playAuditPolicy ?? {})).toHaveLength(4);
|
||||
}
|
||||
world.consumeDirtyState();
|
||||
initializeAuditPolicies(world);
|
||||
expect(
|
||||
world
|
||||
.peekDirtyState()
|
||||
.pendingAuditPolicies.filter((policy) => created.some((nation) => nation.id === policy.nationId))
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it('creates the invader nation, generals, diplomacy, follow-up events, and city state', async () => {
|
||||
const harness = buildHarness();
|
||||
const handler = createRaiseInvaderHandler({
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { initializeAuditPolicies } from '../src/playAudit/policy.js';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import {
|
||||
parseScenarioGeneralPoolCandidate,
|
||||
@@ -228,6 +229,21 @@ describe('RaiseNPCNation monthly action', () => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('preserves new nation audit heads through ruler initialization and restart', async () => {
|
||||
const { world, handler, environment } = buildHarness();
|
||||
world.updateWorldMeta({ serverId: 'new-nation-audit' });
|
||||
initializeAuditPolicies(world);
|
||||
await handler([], environment, event);
|
||||
const created = world.peekDirtyState().createdNations;
|
||||
expect(created.length).toBeGreaterThan(0);
|
||||
for (const nation of created) {
|
||||
expect(Object.keys(nation.meta._playAuditPolicy ?? {})).toHaveLength(4);
|
||||
}
|
||||
world.consumeDirtyState();
|
||||
initializeAuditPolicies(world);
|
||||
expect(world.peekDirtyState().pendingAuditPolicies).toEqual([]);
|
||||
});
|
||||
|
||||
it('uses a U30 subordinate name/dex/special while preserving RaiseNPCNation random stats', async () => {
|
||||
const info = {
|
||||
generalName: '부장후보',
|
||||
@@ -337,6 +353,32 @@ describe('RaiseNPCNation monthly action', () => {
|
||||
"id": 2,
|
||||
"level": 2,
|
||||
"meta": {
|
||||
"_playAuditPolicy": {
|
||||
"DEFENCE": {
|
||||
"hash": "ffb1e0b6bbf1680af65800cfc1b166afbd68149217e2b039c95b4ce4db0d20aa",
|
||||
"id": "6329bea23c4db57a8c6bfb7d4c81e9f5a3e59171c883e9d22c5ba97dfb7d486c",
|
||||
"revision": 1,
|
||||
"serverId": "fixture-server",
|
||||
},
|
||||
"NPC_GENERAL_PRIORITY": {
|
||||
"hash": "0a6202f188859cb41dee0f08388e9da21babf6f4b569f7b18abf0453caaa1062",
|
||||
"id": "28bc6ff38cc5b1b250f6a738a6745b5d581badf34bbcaf191e5e8710c17263ec",
|
||||
"revision": 1,
|
||||
"serverId": "fixture-server",
|
||||
},
|
||||
"NPC_NATION_PRIORITY": {
|
||||
"hash": "0a6202f188859cb41dee0f08388e9da21babf6f4b569f7b18abf0453caaa1062",
|
||||
"id": "feefa645ba10e0fb8c2c78e0c189f9effe8a1c3d2adf4c0d866300d4123f3748",
|
||||
"revision": 1,
|
||||
"serverId": "fixture-server",
|
||||
},
|
||||
"NPC_VALUES": {
|
||||
"hash": "1906fb445a401a46f1279dbba3f3d572e9b213daf6c0f0d31cf8beeee3c77104",
|
||||
"id": "c6fe336ffe5870cc90e2abab797d050810400404f7ad51b3ceea987b21eb3baa",
|
||||
"revision": 1,
|
||||
"serverId": "fixture-server",
|
||||
},
|
||||
},
|
||||
"bill": 100,
|
||||
"can_국기변경": 1,
|
||||
"gennum": 1,
|
||||
|
||||
@@ -235,6 +235,64 @@ integration('initial audit durability before runtime readiness', () => {
|
||||
).rejects.toMatchObject({ code: 'P2002' });
|
||||
}, 30_000);
|
||||
|
||||
it('restores missing policy heads from immutable history before restart without rewriting history', async () => {
|
||||
await runtime?.close();
|
||||
runtime = undefined;
|
||||
const policies = await db.playAuditPolicy.findMany({ where: { serverId }, orderBy: { id: 'asc' } });
|
||||
const nation = await db.nation.findUniqueOrThrow({ where: { id: 91990 } });
|
||||
const { _playAuditPolicy: heads, ...meta } = asRecord(nation.meta);
|
||||
await db.nation.update({ where: { id: nation.id }, data: { meta: meta as GamePrisma.InputJsonObject } });
|
||||
runtime = await start();
|
||||
expect(
|
||||
asRecord((await db.nation.findUniqueOrThrow({ where: { id: nation.id } })).meta)._playAuditPolicy
|
||||
).toEqual(heads);
|
||||
expect(await db.playAuditPolicy.findMany({ where: { serverId }, orderBy: { id: 'asc' } })).toEqual(policies);
|
||||
expect((await db.turnDaemonLease.findUniqueOrThrow({ where: { profile } })).clockReady).toBe(true);
|
||||
await runtime.close();
|
||||
runtime = undefined;
|
||||
runtime = await start();
|
||||
expect(await db.playAuditPolicy.findMany({ where: { serverId }, orderBy: { id: 'asc' } })).toEqual(policies);
|
||||
}, 30_000);
|
||||
|
||||
it('links an observed policy gap to the recovered head and keeps it idempotent', async () => {
|
||||
await runtime?.close();
|
||||
runtime = undefined;
|
||||
const nation = await db.nation.findUniqueOrThrow({ where: { id: 91990 } });
|
||||
const meta = asRecord(nation.meta);
|
||||
const heads = asRecord(meta._playAuditPolicy);
|
||||
const previous = asRecord(heads.DEFENCE);
|
||||
const { DEFENCE: _lost, ...remainingHeads } = heads;
|
||||
await db.nation.update({
|
||||
where: { id: nation.id },
|
||||
data: {
|
||||
meta: { ...meta, scout: 1, _playAuditPolicy: remainingHeads } as GamePrisma.InputJsonObject,
|
||||
},
|
||||
});
|
||||
runtime = await start();
|
||||
const rows = await db.playAuditPolicy.findMany({
|
||||
where: { serverId, nationId: nation.id, area: 'DEFENCE' },
|
||||
orderBy: { revision: 'asc' },
|
||||
});
|
||||
expect(rows.at(-1)).toMatchObject({
|
||||
source: 'OBSERVED_GAP',
|
||||
previousId: previous.id,
|
||||
revision: Number(previous.revision) + 1,
|
||||
actor: null,
|
||||
before: null,
|
||||
after: { scout: 1 },
|
||||
});
|
||||
expect(asRecord((await db.nation.findUniqueOrThrow({ where: { id: nation.id } })).meta).scout).toBe(1);
|
||||
await runtime.close();
|
||||
runtime = undefined;
|
||||
runtime = await start();
|
||||
expect(
|
||||
await db.playAuditPolicy.findMany({
|
||||
where: { serverId, nationId: nation.id, area: 'DEFENCE' },
|
||||
orderBy: { revision: 'asc' },
|
||||
})
|
||||
).toEqual(rows);
|
||||
}, 30_000);
|
||||
|
||||
it('captures newly observed policies after durable clock recovery without replacing the initial sample', async () => {
|
||||
await runtime?.close();
|
||||
runtime = undefined;
|
||||
|
||||
Reference in New Issue
Block a user