Add Prettier and ESLint tooling (Refs #77)
This commit is contained in:
@@ -5,12 +5,19 @@ import { routePermission, roleAllows } from '../../src/lib/server/auth/access.js
|
||||
|
||||
let fixture: Fixture;
|
||||
beforeEach(async () => {
|
||||
vi.resetModules(); fixture = await newFixture([]);
|
||||
vi.resetModules();
|
||||
fixture = await newFixture([]);
|
||||
await prepareNativeHost();
|
||||
const { db } = await import('../../src/lib/server/db/index.js');
|
||||
for (const n of [3, 8, 9, 10]) db().prepare("INSERT INTO server_keys(id,game,pluto_key) VALUES(?,'t6',?)").run(`key-${n}`, `secret-${n}`);
|
||||
for (const n of [3, 8, 9, 10])
|
||||
db()
|
||||
.prepare("INSERT INTO server_keys(id,game,pluto_key) VALUES(?,'t6',?)")
|
||||
.run(`key-${n}`, `secret-${n}`);
|
||||
});
|
||||
afterEach(async () => {
|
||||
await fixture.cleanup();
|
||||
vi.useRealTimers();
|
||||
});
|
||||
afterEach(async () => { await fixture.cleanup(); vi.useRealTimers(); });
|
||||
const settings = { game: 't6', mapSlug: 'town', maximum: 4, mode: 'create-destroy' };
|
||||
|
||||
it('persists deduplicated paused decisions across restart without provisioning or reservations', async () => {
|
||||
@@ -18,7 +25,8 @@ it('persists deduplicated paused decisions across restart without provisioning o
|
||||
const scaling = await import('../../src/lib/server/autoscaling/index.js');
|
||||
const saved = scaling.saveScaling(settings);
|
||||
scaling.setScalingGlobalPause(true);
|
||||
await scaling.runAutoscaling(); await scaling.runAutoscaling();
|
||||
await scaling.runAutoscaling();
|
||||
await scaling.runAutoscaling();
|
||||
closeDb();
|
||||
await scaling.runAutoscaling();
|
||||
const view = scaling.scalingReport().policies[0];
|
||||
@@ -26,7 +34,8 @@ it('persists deduplicated paused decisions across restart without provisioning o
|
||||
expect(view.history).toHaveLength(1);
|
||||
expect(view.decision).toMatchObject({ action: 'wait', server: null });
|
||||
expect(JSON.stringify(view)).not.toContain('secret');
|
||||
for (const table of ['server_deployments', 'native_creation_intents', 'native_instances']) expect(db().prepare(`SELECT COUNT(*) AS n FROM ${table}`).get()).toEqual({ n: 0 });
|
||||
for (const table of ['server_deployments', 'native_creation_intents', 'native_instances'])
|
||||
expect(db().prepare(`SELECT COUNT(*) AS n FROM ${table}`).get()).toEqual({ n: 0 });
|
||||
});
|
||||
|
||||
it('saves edits directly while rejecting duplicate policies and stale revisions', async () => {
|
||||
@@ -44,23 +53,38 @@ it('checks current fleet blockers during save and leaves rejected edits unchange
|
||||
const { db } = await import('../../src/lib/server/db/index.js');
|
||||
const scaling = await import('../../src/lib/server/autoscaling/index.js');
|
||||
const saved = scaling.saveScaling(settings);
|
||||
db().prepare("INSERT INTO servers(name,game,map_slug) VALUES('unknown-map','t6','unknown')").run();
|
||||
expect(() => scaling.saveScaling({ ...settings, maximum: 5 }, saved.id, saved.revision)).toThrow('ambiguous map identity');
|
||||
db()
|
||||
.prepare("INSERT INTO servers(name,game,map_slug) VALUES('unknown-map','t6','unknown')")
|
||||
.run();
|
||||
expect(() =>
|
||||
scaling.saveScaling({ ...settings, maximum: 5 }, saved.id, saved.revision)
|
||||
).toThrow('ambiguous map identity');
|
||||
expect(scaling.getScalingPolicy(saved.id)).toEqual(saved);
|
||||
});
|
||||
|
||||
it('creates and updates policies through the API without a preview or token', async () => {
|
||||
const { POST } = await import('../../src/routes/api/autoscaling/+server.js');
|
||||
const post = (body: object) => POST({ request: new Request('http://localhost/api/autoscaling', {
|
||||
method: 'POST', body: JSON.stringify({ action: 'save', ...body })
|
||||
}) });
|
||||
const post = (body: object) =>
|
||||
POST({
|
||||
request: new Request('http://localhost/api/autoscaling', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ action: 'save', ...body })
|
||||
})
|
||||
});
|
||||
const response = await post({ settings });
|
||||
expect(response.status).toBe(200);
|
||||
const { policy } = await response.json();
|
||||
const update = { settings: { ...settings, maximum: 3 }, id: policy.id, revision: policy.revision };
|
||||
const update = {
|
||||
settings: { ...settings, maximum: 3 },
|
||||
id: policy.id,
|
||||
revision: policy.revision
|
||||
};
|
||||
const changed = await post(update);
|
||||
expect(changed.status).toBe(200);
|
||||
expect((await changed.json()).policy).toMatchObject({ maximum: 3, revision: policy.revision + 1 });
|
||||
expect((await changed.json()).policy).toMatchObject({
|
||||
maximum: 3,
|
||||
revision: policy.revision + 1
|
||||
});
|
||||
expect((await post(update)).status).toBe(409);
|
||||
expect((await post({ action: 'preview', settings })).status).toBe(400);
|
||||
});
|
||||
@@ -71,7 +95,8 @@ it('pauses evaluations and prunes expired observations without deleting current
|
||||
db().prepare('DELETE FROM server_keys').run();
|
||||
scaling.saveScaling(settings);
|
||||
await scaling.runAutoscaling();
|
||||
scaling.setScalingGlobalPause(true); await scaling.runAutoscaling();
|
||||
scaling.setScalingGlobalPause(true);
|
||||
await scaling.runAutoscaling();
|
||||
expect(scaling.scalingReport().policies[0].decision.reason).toContain('globally paused');
|
||||
const id = scaling.scalingReport().policies[0].history[0].id;
|
||||
db().prepare('UPDATE autoscaling_decisions SET last_seen_at=0 WHERE id!=?').run(id);
|
||||
@@ -86,30 +111,46 @@ it('uses fleet authorization for page, reads, and mutations', () => {
|
||||
expect(roleAllows('operator', routePermission('/api/autoscaling', 'POST'))).toBe(true);
|
||||
});
|
||||
|
||||
it.each(['dry-run', 'create-only'])('rejects removed %s mode through the API', async mode => {
|
||||
it.each(['dry-run', 'create-only'])('rejects removed %s mode through the API', async (mode) => {
|
||||
const { POST } = await import('../../src/routes/api/autoscaling/+server.js');
|
||||
for (const action of ['save']) {
|
||||
const response = await POST({ request: new Request('http://localhost/api/autoscaling', {
|
||||
method: 'POST', body: JSON.stringify({ action, settings: { ...settings, mode } })
|
||||
}) });
|
||||
const response = await POST({
|
||||
request: new Request('http://localhost/api/autoscaling', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ action, settings: { ...settings, mode } })
|
||||
})
|
||||
});
|
||||
expect(response.status).toBe(400);
|
||||
expect(await response.json()).toMatchObject({ error: 'Choose a valid autoscaling mode.' });
|
||||
}
|
||||
});
|
||||
|
||||
it.each(['dry-run', 'create-only'])('turns saved %s policies off once on upgrade', async mode => {
|
||||
it.each(['dry-run', 'create-only'])('turns saved %s policies off once on upgrade', async (mode) => {
|
||||
const { db, closeDb } = await import('../../src/lib/server/db/index.js');
|
||||
const scaling = await import('../../src/lib/server/autoscaling/index.js');
|
||||
const saved = scaling.saveScaling(settings);
|
||||
const otherSettings = { ...settings, mapSlug: 'farm' };
|
||||
const other = scaling.saveScaling(otherSettings);
|
||||
db().prepare('UPDATE autoscaling_policies SET mode=?,settings=json_set(settings,\'$.mode\',?) WHERE id=?').run(mode, mode, saved.id);
|
||||
db()
|
||||
.prepare(
|
||||
"UPDATE autoscaling_policies SET mode=?,settings=json_set(settings,'$.mode',?) WHERE id=?"
|
||||
)
|
||||
.run(mode, mode, saved.id);
|
||||
closeDb();
|
||||
const migrated = scaling.getScalingPolicy(saved.id)!;
|
||||
expect(migrated).toMatchObject({ mode: 'off', revision: saved.revision + 1, maximum: saved.maximum,
|
||||
pausedReason: expect.stringContaining('mode was removed') });
|
||||
expect(db().prepare('SELECT owns_map,json_extract(settings,\'$.mode\') AS mode FROM autoscaling_policies WHERE id=?').get(saved.id))
|
||||
.toEqual({ owns_map: 0, mode: 'off' });
|
||||
expect(migrated).toMatchObject({
|
||||
mode: 'off',
|
||||
revision: saved.revision + 1,
|
||||
maximum: saved.maximum,
|
||||
pausedReason: expect.stringContaining('mode was removed')
|
||||
});
|
||||
expect(
|
||||
db()
|
||||
.prepare(
|
||||
"SELECT owns_map,json_extract(settings,'$.mode') AS mode FROM autoscaling_policies WHERE id=?"
|
||||
)
|
||||
.get(saved.id)
|
||||
).toEqual({ owns_map: 0, mode: 'off' });
|
||||
expect(scaling.getScalingPolicy(other.id)).toEqual(other);
|
||||
closeDb();
|
||||
expect(scaling.getScalingPolicy(saved.id)).toEqual(migrated);
|
||||
@@ -117,40 +158,76 @@ it.each(['dry-run', 'create-only'])('turns saved %s policies off once on upgrade
|
||||
expect(resumed).toMatchObject({ mode: 'create-destroy', pausedReason: null });
|
||||
});
|
||||
|
||||
it.each([false, true])('reconciles removed-mode creation while preserving submitted work: %s', async submitted => {
|
||||
const { db, closeDb } = await import('../../src/lib/server/db/index.js');
|
||||
const scaling = await import('../../src/lib/server/autoscaling/index.js');
|
||||
const saved = scaling.saveScaling(settings);
|
||||
await scaling.runAutoscaling();
|
||||
const { operation_id: id } = db().prepare('SELECT operation_id FROM autoscaling_decisions WHERE resolved=0').get() as { operation_id: string };
|
||||
db().prepare("UPDATE autoscaling_policies SET mode='create-only',settings=json_set(settings,'$.mode','create-only') WHERE id=?").run(saved.id);
|
||||
if (submitted) db().prepare('UPDATE server_deployments SET submitted_at=? WHERE id=?').run(new Date().toISOString(), id);
|
||||
closeDb();
|
||||
expect(scaling.getScalingPolicy(saved.id)?.mode).toBe('off');
|
||||
expect(db().prepare('SELECT owns_map FROM autoscaling_policies WHERE id=?').get(saved.id)).toEqual({ owns_map: 1 });
|
||||
expect(await scaling.runAutoscaling()).toBe(false);
|
||||
expect(db().prepare('SELECT state FROM server_deployments WHERE id=?').get(id)).toEqual({ state: submitted ? 'queued' : 'cancelled' });
|
||||
expect(db().prepare('SELECT owns_map FROM autoscaling_policies WHERE id=?').get(saved.id)).toEqual({ owns_map: submitted ? 1 : 0 });
|
||||
expect(db().prepare('SELECT COUNT(*) AS n FROM server_key_leases').get()).toEqual({ n: submitted ? 1 : 0 });
|
||||
expect(db().prepare("SELECT COUNT(*) AS n FROM server_deployments WHERE action='create'").get()).toEqual({ n: 1 });
|
||||
});
|
||||
it.each([false, true])(
|
||||
'reconciles removed-mode creation while preserving submitted work: %s',
|
||||
async (submitted) => {
|
||||
const { db, closeDb } = await import('../../src/lib/server/db/index.js');
|
||||
const scaling = await import('../../src/lib/server/autoscaling/index.js');
|
||||
const saved = scaling.saveScaling(settings);
|
||||
await scaling.runAutoscaling();
|
||||
const { operation_id: id } = db()
|
||||
.prepare('SELECT operation_id FROM autoscaling_decisions WHERE resolved=0')
|
||||
.get() as { operation_id: string };
|
||||
db()
|
||||
.prepare(
|
||||
"UPDATE autoscaling_policies SET mode='create-only',settings=json_set(settings,'$.mode','create-only') WHERE id=?"
|
||||
)
|
||||
.run(saved.id);
|
||||
if (submitted)
|
||||
db()
|
||||
.prepare('UPDATE server_deployments SET submitted_at=? WHERE id=?')
|
||||
.run(new Date().toISOString(), id);
|
||||
closeDb();
|
||||
expect(scaling.getScalingPolicy(saved.id)?.mode).toBe('off');
|
||||
expect(
|
||||
db().prepare('SELECT owns_map FROM autoscaling_policies WHERE id=?').get(saved.id)
|
||||
).toEqual({ owns_map: 1 });
|
||||
expect(await scaling.runAutoscaling()).toBe(false);
|
||||
expect(db().prepare('SELECT state FROM server_deployments WHERE id=?').get(id)).toEqual({
|
||||
state: submitted ? 'queued' : 'cancelled'
|
||||
});
|
||||
expect(
|
||||
db().prepare('SELECT owns_map FROM autoscaling_policies WHERE id=?').get(saved.id)
|
||||
).toEqual({ owns_map: submitted ? 1 : 0 });
|
||||
expect(db().prepare('SELECT COUNT(*) AS n FROM server_key_leases').get()).toEqual({
|
||||
n: submitted ? 1 : 0
|
||||
});
|
||||
expect(
|
||||
db().prepare("SELECT COUNT(*) AS n FROM server_deployments WHERE action='create'").get()
|
||||
).toEqual({ n: 1 });
|
||||
}
|
||||
);
|
||||
|
||||
it('removes saved cooldowns on upgrade without changing the empty hold or pausing the policy', async () => {
|
||||
const { db, closeDb } = await import('../../src/lib/server/db/index.js');
|
||||
const scaling = await import('../../src/lib/server/autoscaling/index.js');
|
||||
const input = { ...settings, emptyMinutes: 17 };
|
||||
const saved = scaling.saveScaling(input);
|
||||
db().exec('ALTER TABLE autoscaling_policies ADD COLUMN next_action_at INTEGER NOT NULL DEFAULT 0');
|
||||
db().prepare("UPDATE autoscaling_policies SET next_action_at=?,settings=json_set(settings,'$.cooldownMinutes',60) WHERE id=?")
|
||||
db().exec(
|
||||
'ALTER TABLE autoscaling_policies ADD COLUMN next_action_at INTEGER NOT NULL DEFAULT 0'
|
||||
);
|
||||
db()
|
||||
.prepare(
|
||||
"UPDATE autoscaling_policies SET next_action_at=?,settings=json_set(settings,'$.cooldownMinutes',60) WHERE id=?"
|
||||
)
|
||||
.run(Date.now() + 3600_000, saved.id);
|
||||
closeDb();
|
||||
const restored = scaling.getScalingPolicy(saved.id)!;
|
||||
expect(restored).toEqual(saved);
|
||||
expect(restored).not.toHaveProperty('cooldownMinutes');
|
||||
expect(restored).not.toHaveProperty('nextActionAt');
|
||||
expect(db().pragma('table_info(autoscaling_policies)')).not.toContainEqual(expect.objectContaining({ name: 'next_action_at' }));
|
||||
expect(JSON.parse((db().prepare('SELECT settings FROM autoscaling_policies WHERE id=?').get(saved.id) as { settings: string }).settings))
|
||||
.not.toHaveProperty('cooldownMinutes');
|
||||
expect(db().pragma('table_info(autoscaling_policies)')).not.toContainEqual(
|
||||
expect.objectContaining({ name: 'next_action_at' })
|
||||
);
|
||||
expect(
|
||||
JSON.parse(
|
||||
(
|
||||
db()
|
||||
.prepare('SELECT settings FROM autoscaling_policies WHERE id=?')
|
||||
.get(saved.id) as { settings: string }
|
||||
).settings
|
||||
)
|
||||
).not.toHaveProperty('cooldownMinutes');
|
||||
closeDb();
|
||||
expect(scaling.getScalingPolicy(saved.id)).toEqual(saved);
|
||||
expect(await scaling.runAutoscaling()).toBe(true);
|
||||
|
||||
Reference in New Issue
Block a user