feat: add Watchdog recovery and drift rebuild workflows

This commit is contained in:
2026-09-27 15:13:38 -04:00
parent a7044f21a1
commit b9f5902945
36 changed files with 2131 additions and 424 deletions
+17 -4
View File
@@ -54,6 +54,19 @@ async function tick(seconds = 15, players = 4, healthy = true) {
const scriptPath = (name: string) => join(fixture.t5, name, 'runtime/plutonium/storage/t5/raw/scripts/sp/test.gsc');
describe('durable beta test plans', () => {
it('waits for occupied telemetry to clear before restarting a beta despite an empty RCON response', async () => {
const id = await openPlan(defaultPlan());
const { listBetaServers } = await import('../../src/lib/server/fleet/beta.js');
const { komodoRestart } = await import('../../src/lib/server/integrations/komodo.js');
await postTelemetry(gameEvent('heartbeat', 4, 2));
await tick();
expect(komodoRestart).not.toHaveBeenCalled();
expect(listBetaServers(id)[0].stage).toBe('awaiting-empty');
await postTelemetry(gameEvent('heartbeat', 4, 0));
await tick();
expect(komodoRestart).toHaveBeenCalledTimes(1);
});
it('holds file deployment until each wave passes, and rollback leaves undeployed files alone', async () => {
await writeFile(scriptPath(names[1]), 'original untracked file');
const id = await openPlan({ ...defaultPlan(), waveSize: 1 }, names);
@@ -81,7 +94,7 @@ describe('durable beta test plans', () => {
it('uses script events instead of logs and keeps match progress across app restarts', async () => {
await postTelemetry(gameEvent('game_end'));
const id = await openPlan({ ...defaultPlan(), requirements: [requirement('matches', 'matches')] });
await tick(); await tick();
await tick(46); await tick();
const { testView } = await import('../../src/lib/server/fleet/beta-tests.js');
await appendFile(join(fixture.t5, names[0], 'logs/games_sp.log'), 'InitGame:\n 0:00 J;new;1;Josh\nExitLevel: executed\n');
await tick();
@@ -98,7 +111,7 @@ describe('durable beta test plans', () => {
it('automatically credits ordinary rounds only to active deployed beta servers', async () => {
const id = await openPlan({ ...defaultPlan(), waveSize: 1, requirements: [requirement('round', 'round')] }, names);
await postTelemetry(gameEvent('round_change'));
await tick(); await tick();
await tick(46); await tick();
const { testView } = await import('../../src/lib/server/fleet/beta-tests.js');
expect(testView(id)?.servers[0].progress[0].current).toBe(0);
await postTelemetry(gameEvent('round_change', 99, 4, names[1]));
@@ -117,7 +130,7 @@ describe('durable beta test plans', () => {
const first = currentRun(id, names[0])!;
expect(testView(id)?.servers[0].progress[0].current).toBe(0);
await postTelemetry(gameEvent('game_start'));
await tick(); await tick();
await tick(46); await tick();
expect(currentRun(id, names[0])?.id).not.toBe(first.id);
expect(currentRun(id, names[0])?.phase).toBe(1);
expect(testView(id)?.servers[0].progress[0].current).toBe(0);
@@ -212,7 +225,7 @@ describe('durable beta test plans', () => {
expect(retry.status).toBe(200);
await postTelemetry(gameEvent('round_change'));
expect(testView(id)?.servers).toHaveLength(0);
await tick(); await tick();
await tick(46); await tick();
expect(testView(id)?.servers[0].progress[0].current).toBe(0);
await postTelemetry(gameEvent('round_change'));
expect(testView(id)?.servers[0].progress[0].current).toBe(25);