refactor: 턴 경계와 12턴 묶음을 보존하는 2배속 복구 구현
This commit is contained in:
@@ -1566,13 +1566,32 @@ describe('admin runtime clock action API', () => {
|
||||
expect(harness.updatedStatuses).toEqual([]);
|
||||
});
|
||||
|
||||
it('routes direct profile status changes through clock suspension', async () => {
|
||||
const harness = await buildCaller(unusedCreateOperation, { initialProfileStatus: 'RUNNING' });
|
||||
await harness.caller.admin.profiles.setStatus({ profileName: 'che:2', status: 'PAUSED' });
|
||||
expect(harness.lifecycle[0]).toBe('clock:SUSPEND');
|
||||
expect(harness.updatedStatuses).toEqual(['PAUSED']);
|
||||
});
|
||||
|
||||
it('rejects schedule movement that changes within-turn phases', async () => {
|
||||
const harness = await buildCaller(unusedCreateOperation);
|
||||
await expect(
|
||||
harness.caller.admin.profiles.requestAction({
|
||||
profileName: 'che:2',
|
||||
action: 'ACCELERATE',
|
||||
durationMinutes: 15,
|
||||
})
|
||||
).rejects.toMatchObject({ code: 'BAD_REQUEST' });
|
||||
expect(harness.createdRuntimeActions).toEqual([]);
|
||||
});
|
||||
|
||||
it('creates a first-class clock action owned by the authenticated administrator', async () => {
|
||||
const harness = await buildCaller(unusedCreateOperation);
|
||||
|
||||
const result = await harness.caller.admin.profiles.requestAction({
|
||||
profileName: 'che:2',
|
||||
action: 'ACCELERATE',
|
||||
durationMinutes: 15,
|
||||
durationMinutes: 20,
|
||||
reason: '운영 일정 조정',
|
||||
});
|
||||
|
||||
@@ -1580,7 +1599,7 @@ describe('admin runtime clock action API', () => {
|
||||
ok: true,
|
||||
action: {
|
||||
action: 'ACCELERATE',
|
||||
durationMinutes: 15,
|
||||
durationMinutes: 20,
|
||||
status: 'REQUESTED',
|
||||
},
|
||||
});
|
||||
@@ -1589,7 +1608,7 @@ describe('admin runtime clock action API', () => {
|
||||
profileName: 'che:2',
|
||||
action: 'ACCELERATE',
|
||||
payload: {},
|
||||
durationMinutes: 15,
|
||||
durationMinutes: 20,
|
||||
reason: '운영 일정 조정',
|
||||
requestedBy: harness.admin.id,
|
||||
},
|
||||
@@ -1605,7 +1624,7 @@ describe('admin runtime clock action API', () => {
|
||||
harness.caller.admin.profiles.requestAction({
|
||||
profileName: 'che:2',
|
||||
action: 'DELAY',
|
||||
durationMinutes: 5,
|
||||
durationMinutes: 20,
|
||||
})
|
||||
).rejects.toMatchObject({
|
||||
code: 'CONFLICT',
|
||||
|
||||
@@ -193,6 +193,10 @@ const createHarness = (
|
||||
scheduleIntervalMs: 60_000,
|
||||
buildIntervalMs: 60_000,
|
||||
adminActionIntervalMs: 60_000,
|
||||
transitionProfileClock: async (_profileName, action) => {
|
||||
lifecycle.push(`clock:${action}`);
|
||||
return { phase: action === 'SUSPEND' ? 'SUSPENDED' : 'RUNNING', revision: 2 };
|
||||
},
|
||||
now: options.now,
|
||||
cancelGame: options.cancelGame,
|
||||
promoteProfileOpening: options.promoteProfileOpening
|
||||
@@ -218,6 +222,13 @@ const createHarness = (
|
||||
};
|
||||
|
||||
describe('GatewayOrchestrator first-class operations', () => {
|
||||
it.each(['START', 'STOP'] as const)('routes %s through durable clock transition', async (action) => {
|
||||
const harness = createHarness(buildOperation(action));
|
||||
await harness.orchestrator.runOperationsNow();
|
||||
expect(harness.lifecycle[0]).toBe(`clock:${action === 'START' ? 'RESUME' : 'SUSPEND'}`);
|
||||
expect(harness.completions).toEqual(['SUCCEEDED']);
|
||||
});
|
||||
|
||||
it('stops runtime, settles once, and seals a cancelled profile', async () => {
|
||||
const operation: GatewayOperationRecord = {
|
||||
id: '88888888-8888-4888-8888-888888888888',
|
||||
|
||||
@@ -48,14 +48,14 @@ describeDatabase('selected workspace profile seed CLI', () => {
|
||||
JSON.stringify({
|
||||
scenarioId: 1010,
|
||||
tickSeconds: 60,
|
||||
now: '2036-03-03T00:00:00.000Z',
|
||||
now: '2036-03-03T02:10:30.000Z',
|
||||
installOptions: {
|
||||
serverId: 'selected-cli-seed',
|
||||
firstGameIdx: 0,
|
||||
installOperationId: 'selected-cli-operation',
|
||||
installCommitSha: 'selected-cli-commit',
|
||||
preopenAt: '2036-03-03T01:00:00.000Z',
|
||||
openAt: '2036-03-03T02:00:00.000Z',
|
||||
openAt: '2036-03-03T02:10:30.000Z',
|
||||
},
|
||||
adminUser: {
|
||||
id: 'selected-cli-admin',
|
||||
@@ -71,7 +71,8 @@ describeDatabase('selected workspace profile seed CLI', () => {
|
||||
const world = await connector.prisma.worldState.findFirstOrThrow();
|
||||
expect(world).toMatchObject({
|
||||
scenarioCode: '1010',
|
||||
clockWallAnchor: new Date('2036-03-03T02:00:00.000Z'),
|
||||
clockWallAnchor: new Date('2036-03-03T02:11:00.000Z'),
|
||||
clockPhase: 'PREOPEN',
|
||||
meta: {
|
||||
firstGameIdx: 0,
|
||||
gameIdx: completedGameCount,
|
||||
@@ -83,6 +84,8 @@ describeDatabase('selected workspace profile seed CLI', () => {
|
||||
where: { userId: 'selected-cli-admin' },
|
||||
});
|
||||
expect(adminGeneral).toMatchObject({ meta: { createdBy: 'admin-seed' } });
|
||||
expect(adminGeneral.turnTick).toBeGreaterThanOrEqual(0n);
|
||||
expect(adminGeneral.turnTick).toBeLessThan(36_000_000n);
|
||||
const history = await connector.prisma.gameHistory.findUniqueOrThrow({
|
||||
where: { serverId: 'selected-cli-seed' },
|
||||
});
|
||||
|
||||
@@ -39,7 +39,7 @@ describe('readReleaseManifest', () => {
|
||||
await expect(readReleaseManifest(workspaceRoot)).resolves.toMatchObject({
|
||||
controllerProtocol: RELEASE_CONTROLLER_PROTOCOL,
|
||||
gatewaySchemaHead: '20260825000000_add_bulk_release_batches',
|
||||
gameSchemaHead: '20260903201500_complete_invader_game_clock',
|
||||
gameSchemaHead: '20260906090000_add_turn_recovery_window',
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user