Save autoscaling policies without a preview step

This commit is contained in:
2026-10-07 15:56:13 -04:00
parent 7ede98ff87
commit daf78bc11c
12 changed files with 127 additions and 124 deletions
+26 -26
View File
@@ -17,12 +17,12 @@ async function setup() {
it.each(['off', 'create-destroy'])('saves a %s policy without preallocating keys for its maximum', async mode => {
const { db, scaling } = await setup();
const input = { ...settings, mode, maximum: 20 };
const preview = scaling.previewScaling(input);
expect(preview.blockers).toEqual([]);
expect(preview.decision.action).toBe('wait');
if (mode !== 'off') expect(preview.decision.reason).toContain('No Plutonium keys');
const evaluation = scaling.evaluateScaling(input);
expect(evaluation.blockers).toEqual([]);
expect(evaluation.decision.action).toBe('wait');
if (mode !== 'off') expect(evaluation.decision.reason).toContain('No Plutonium keys');
const { POST } = await import('../../src/routes/api/autoscaling/+server.js');
const response = await POST({ request: new Request('http://localhost/api/autoscaling', { method: 'POST', body: JSON.stringify({ action: 'save', settings: input, token: preview.token }) }) });
const response = await POST({ request: new Request('http://localhost/api/autoscaling', { method: 'POST', body: JSON.stringify({ action: 'save', settings: input }) }) });
expect(response.status).toBe(200);
expect(db().prepare('SELECT COUNT(*) AS n FROM autoscaling_policies').get()).toEqual({ n: 1 });
expect(await scaling.runAutoscaling()).toBe(false);
@@ -32,36 +32,36 @@ it('uses any free key for the selected game independently of its label or map',
const { scaling, saveServerKey } = await setup();
saveServerKey('t5', 'other-game-key', 'Town Lobby 1');
saveServerKey('t6', 'game-key', 'Farm Lobby 8');
const preview = scaling.previewScaling(settings);
expect(preview.snapshot.keyPool).toEqual({ total: 1, available: 1 });
expect(preview.decision).toMatchObject({ action: 'create', server: 't6_zombie_town_1' });
expect(scaling.saveScaling(settings, undefined, undefined, preview.token).maximum).toBe(2);
const evaluation = scaling.evaluateScaling(settings);
expect(evaluation.snapshot.keyPool).toEqual({ total: 1, available: 1 });
expect(evaluation.decision).toMatchObject({ action: 'create', server: 't6_zombie_town_1' });
expect(scaling.saveScaling(settings).maximum).toBe(2);
});
it('uses one shared pool for every map without requiring a key for each maximum', async () => {
const { scaling, saveServerKey } = await setup();
saveServerKey('t6', 'game-key');
const input = { ...settings, mapSlug: '*', maximum: 20 };
const preview = scaling.previewScaling(input);
expect(preview.blockers).toEqual([]);
expect(preview.snapshot.keyPool).toEqual({ total: 1, available: 1 });
expect(scaling.saveScaling(input, undefined, undefined, preview.token).mapSlug).toBe('*');
const evaluation = scaling.evaluateScaling(input);
expect(evaluation.blockers).toEqual([]);
expect(evaluation.snapshot.keyPool).toEqual({ total: 1, available: 1 });
expect(scaling.saveScaling(input).mapSlug).toBe('*');
});
it('rechecks game pool deletion and reservations after preview', async () => {
it('saves against the current key pool without an intermediate confirmation', async () => {
const { db, scaling, saveServerKey, reserveServerKey } = await setup();
const keyId = saveServerKey('t6', 'game-key');
const preview = scaling.previewScaling(settings);
db().prepare('DELETE FROM server_keys WHERE id=?').run(keyId);
expect(() => scaling.saveScaling(settings, undefined, undefined, preview.token)).toThrow('preview changed');
const saved = scaling.saveScaling(settings);
expect(scaling.scalingReport().policies[0].decision.reason).toContain('No Plutonium keys');
saveServerKey('t6', 'game-key');
const available = scaling.previewScaling(settings);
reserveServerKey('t6_zombie_farm_1', 't6');
expect(() => scaling.saveScaling(settings, undefined, undefined, available.token)).toThrow('preview changed');
const held = scaling.previewScaling(settings);
expect(held.decision.reason).toContain('keys are in use');
expect(held.snapshot.members).toEqual([]);
expect(scaling.saveScaling(settings, undefined, undefined, held.token).maximum).toBe(2);
expect(scaling.saveScaling({ ...settings, maximum: 3 }, saved.id, saved.revision).maximum).toBe(3);
const view = scaling.scalingReport().policies[0];
expect(view.decision.reason).toContain('keys are in use');
expect(view.snapshot.members).toEqual([]);
expect(await scaling.runAutoscaling()).toBe(false);
expect(db().prepare('SELECT COUNT(*) AS n FROM server_creation_jobs').get()).toEqual({ n: 0 });
});
it('shares capacity between live map policies and admits the job holding the last key', async () => {
@@ -70,7 +70,7 @@ it('shares capacity between live map policies and admits the job holding the las
saveServerKey('t5', 'other-game-key');
for (const mapSlug of ['town', 'farm']) {
const input = { ...settings, mapSlug, mode: 'create-destroy' };
scaling.saveScaling(input, undefined, undefined, scaling.previewScaling(input).token);
scaling.saveScaling(input);
}
expect(await scaling.runAutoscaling()).toBe(true);
const jobs = db().prepare('SELECT deployment_id,server FROM server_creation_jobs').all() as { deployment_id: string; server: string }[];
@@ -91,7 +91,7 @@ it('reserves a lobby identity for an unsettled failed launch with a retained key
saveServerKey('t6', 'held-key');
reserveServerKey('t6_zombie_town_1', 't6');
saveServerKey('t6', 'free-key');
const preview = scaling.previewScaling(settings);
expect(preview.snapshot.reservedNames).toEqual(['t6_zombie_town_1']);
expect(preview.decision).toMatchObject({ action: 'create', server: 't6_zombie_town_2' });
const evaluation = scaling.evaluateScaling(settings);
expect(evaluation.snapshot.reservedNames).toEqual(['t6_zombie_town_1']);
expect(evaluation.decision).toMatchObject({ action: 'create', server: 't6_zombie_town_2' });
});