merge: 최신 main을 내부 예약 명령 변경에 통합한다

This commit is contained in:
2026-08-24 16:02:47 +00:00
12 changed files with 312 additions and 41 deletions
+24 -1
View File
@@ -39,8 +39,12 @@ const buildContext = (
nation: {
count: vi.fn(async () => 0),
},
turnDaemonLease: {
findUnique: vi.fn(async () => ({ leaseUntil: new Date('2099-01-01T00:00:00.000Z') })),
},
} as unknown as DatabaseClient,
}) as GameApiContext;
profileStatusSource: { get: vi.fn(async () => 'RUNNING' as const) },
}) as unknown as GameApiContext;
describe('lobby season state', () => {
it.each([0, 1, 2, 3])('returns legacy isunited state %i', async (isunited) => {
@@ -70,9 +74,28 @@ describe('lobby season state', () => {
expect(result.clockMode).toBe('manual');
expect(result.clockRunning).toBe(false);
expect(result.clockStartsAt).toBeNull();
expect(result.turnEngineRunning).toBe(true);
expect(new Date(result.serverWallTime).getTime()).not.toBeNaN();
});
it('projects the explicit Gateway turn-running capability independently of the game clock mode', async () => {
const context = buildContext(
{},
{
baseTime: new Date('2026-08-15T00:00:00.000Z'),
tick: 72_000_000n,
mode: 'realtime',
wallAnchor: new Date('2026-08-15T00:00:00.000Z'),
}
);
context.profileStatusSource = { get: vi.fn(async () => 'PAUSED' as const) };
const result = await appRouter.createCaller(context).lobby.info();
expect(result.clockRunning).toBe(true);
expect(result.turnEngineRunning).toBe(false);
});
it('exposes the future realtime wall anchor without advancing the preopen clock', async () => {
const wallAnchor = new Date('2099-08-21T11:00:00.000Z');
const result = await appRouter
@@ -0,0 +1,80 @@
import { describe, expect, it, vi } from 'vitest';
import { CachedTurnEngineStatus, loadTurnEngineRunning } from '../src/services/turnEngineStatus.js';
describe('turn engine status projection', () => {
it('maps Gateway profile capabilities and keeps unavailable status unknown', async () => {
const activeLease = {
turnDaemonLease: {
findUnique: vi.fn(async () => ({ leaseUntil: new Date('2026-08-24T00:01:00.000Z') })),
},
};
const now = new Date('2026-08-24T00:00:00.000Z');
await expect(loadTurnEngineRunning({ get: async () => 'RUNNING' }, activeLease, 'che:default', now)).resolves.toBe(
true
);
await expect(loadTurnEngineRunning({ get: async () => 'PREOPEN' }, activeLease, 'che:default', now)).resolves.toBe(
false
);
await expect(loadTurnEngineRunning({ get: async () => 'PAUSED' }, activeLease, 'che:default', now)).resolves.toBe(
false
);
await expect(loadTurnEngineRunning({ get: async () => null }, activeLease, 'che:default', now)).resolves.toBeNull();
await expect(
loadTurnEngineRunning(
{ get: async () => Promise.reject(new Error('gateway unavailable')) },
activeLease,
'che:default',
now
)
).resolves.toBeNull();
});
it('marks a RUNNING profile stopped when its daemon lease is missing or expired', async () => {
const source = { get: async () => 'RUNNING' as const };
const now = new Date('2026-08-24T00:00:00.000Z');
await expect(
loadTurnEngineRunning(
source,
{ turnDaemonLease: { findUnique: async () => null } },
'che:default',
now
)
).resolves.toBe(false);
await expect(
loadTurnEngineRunning(
source,
{
turnDaemonLease: {
findUnique: async () => ({ leaseUntil: new Date('2026-08-23T23:59:59.999Z') }),
},
},
'che:default',
now
)
).resolves.toBe(false);
});
it('coalesces concurrent heartbeat reads and refreshes after the bounded cache window', async () => {
let now = 1_000;
const get = vi.fn(async () => 'RUNNING' as const);
const findUnique = vi.fn(async () => ({ leaseUntil: new Date('2099-01-01T00:00:00.000Z') }));
const cache = new CachedTurnEngineStatus(
{ get },
{ turnDaemonLease: { findUnique } },
'che:default',
2_000,
() => now
);
await expect(Promise.all([cache.get(), cache.get()])).resolves.toEqual([true, true]);
expect(get).toHaveBeenCalledTimes(1);
now += 1_999;
await expect(cache.get()).resolves.toBe(true);
expect(get).toHaveBeenCalledTimes(1);
now += 1;
await expect(cache.get()).resolves.toBe(true);
expect(get).toHaveBeenCalledTimes(2);
expect(findUnique).toHaveBeenCalledTimes(2);
});
});