fix(turn): roll back checkpoint with world state

This commit is contained in:
2026-07-31 16:11:56 +00:00
parent b193b2a0f6
commit 885018c7fd
4 changed files with 58 additions and 9 deletions
@@ -4,7 +4,6 @@ import type { InMemoryTurnWorld } from './inMemoryWorld.js';
export class InMemoryTurnStateStore implements TurnStateStore {
// 인메모리 월드의 턴 상태를 TurnDaemonLifecycle에 제공한다.
private readonly world: InMemoryTurnWorld;
private checkpoint?: TurnCheckpoint;
constructor(world: InMemoryTurnWorld) {
this.world = world;
@@ -15,7 +14,7 @@ export class InMemoryTurnStateStore implements TurnStateStore {
}
async loadNextGeneralTurnTime(): Promise<Date | null> {
return this.world.getNextGeneralTurnTime(this.checkpoint);
return this.world.getNextGeneralTurnTime(this.world.getCheckpoint());
}
async saveLastTurnTime(turnTime: Date): Promise<void> {
@@ -23,11 +22,10 @@ export class InMemoryTurnStateStore implements TurnStateStore {
}
async loadCheckpoint(): Promise<TurnCheckpoint | undefined> {
return this.checkpoint;
return this.world.getCheckpoint();
}
async saveCheckpoint(checkpoint?: TurnCheckpoint): Promise<void> {
this.checkpoint = checkpoint;
this.world.setCheckpoint(checkpoint);
}
}
@@ -1,6 +1,7 @@
import { describe, expect, it } from 'vitest';
import { EngineStateManager } from '../src/turn/engineStateManager.js';
import { InMemoryTurnStateStore } from '../src/turn/inMemoryStateStore.js';
import { InMemoryTurnWorld } from '../src/turn/inMemoryWorld.js';
import { InMemoryReservedTurnStore } from '../src/turn/reservedTurnStore.js';
import type { TurnGeneral, TurnWorldSnapshot, TurnWorldState } from '../src/turn/types.js';
@@ -181,6 +182,46 @@ describe('EngineStateManager', () => {
expect(world.listEvents()).toEqual([]);
});
it('restores the concrete state-store checkpoint when a flush transaction fails', async () => {
const world = buildWorld();
const stateStore = new InMemoryTurnStateStore(world);
const previousCheckpoint = {
turnTime: '0189-01-01T00:00:00.000Z',
generalId: 1,
year: 189,
month: 1,
};
const nextCheckpoint = {
turnTime: '0189-01-01T00:10:00.000Z',
generalId: 2,
year: 189,
month: 1,
};
await stateStore.saveCheckpoint(previousCheckpoint);
const manager = new EngineStateManager();
manager.register('world', {
capture: () => world.captureState(),
restore: (snapshot) => world.restoreState(snapshot),
});
await expect(
manager.transaction(async () => {
await stateStore.saveCheckpoint(nextCheckpoint);
throw new Error('injected flush failure');
})
).rejects.toThrow('injected flush failure');
expect(await stateStore.loadCheckpoint()).toEqual(previousCheckpoint);
expect(world.getCheckpoint()).toEqual(previousCheckpoint);
expect(manager.getRevision()).toBe(0);
await manager.transaction(() => stateStore.saveCheckpoint(nextCheckpoint));
expect(await stateStore.loadCheckpoint()).toEqual(nextCheckpoint);
expect(world.getCheckpoint()).toEqual(nextCheckpoint);
expect(manager.getRevision()).toBe(1);
});
it('creates reusable test savepoints without sharing mutable inspection data', async () => {
const manager = new EngineStateManager();
let state = { nested: { value: 1 } };