Add Prettier and ESLint tooling (Refs #77)
This commit is contained in:
@@ -3,25 +3,62 @@ import { newFixture, type Fixture } from '../helpers/fixture.js';
|
||||
import { betaFlow, campaignFlow } from '../../src/lib/operation-flow.js';
|
||||
|
||||
let fixture: Fixture;
|
||||
beforeEach(async () => { vi.resetModules(); fixture = await newFixture(); });
|
||||
afterEach(async () => { await fixture.cleanup(); });
|
||||
beforeEach(async () => {
|
||||
vi.resetModules();
|
||||
fixture = await newFixture();
|
||||
});
|
||||
afterEach(async () => {
|
||||
await fixture.cleanup();
|
||||
});
|
||||
|
||||
describe('persistent activity', () => {
|
||||
it('keeps beta failures and retries across reopening without duplicating polls', async () => {
|
||||
const { db, closeDb } = await import('../../src/lib/server/db/index.js');
|
||||
const handle = db();
|
||||
handle.prepare("INSERT INTO servers (name) VALUES ('server')").run();
|
||||
handle.prepare("INSERT INTO scripts (id, name, target_rel_path) VALUES (1, 'test', 'test.gsc')").run();
|
||||
handle.prepare("INSERT INTO script_versions (id, script_id, version, sha256, size, content_hash) VALUES (1, 1, 1, 'hash', 0, NULL)").run();
|
||||
handle.prepare("INSERT INTO betas (id, script_id, script_version_id, trigger_kind, state) VALUES (1, 1, 1, 'ui', 'running')").run();
|
||||
handle.prepare("INSERT INTO beta_servers (beta_id, server, had_original, stage) VALUES (1, 'server', 0, 'deploying')").run();
|
||||
handle.prepare("UPDATE beta_servers SET stage = 'failed', error = 'write failed' WHERE beta_id = 1").run();
|
||||
handle.prepare("UPDATE beta_servers SET stage = 'failed', error = 'write failed', updated_at = 'later' WHERE beta_id = 1").run();
|
||||
handle.prepare("UPDATE beta_servers SET stage = 'awaiting-empty', error = NULL WHERE beta_id = 1").run();
|
||||
handle
|
||||
.prepare(
|
||||
"INSERT INTO scripts (id, name, target_rel_path) VALUES (1, 'test', 'test.gsc')"
|
||||
)
|
||||
.run();
|
||||
handle
|
||||
.prepare(
|
||||
"INSERT INTO script_versions (id, script_id, version, sha256, size, content_hash) VALUES (1, 1, 1, 'hash', 0, NULL)"
|
||||
)
|
||||
.run();
|
||||
handle
|
||||
.prepare(
|
||||
"INSERT INTO betas (id, script_id, script_version_id, trigger_kind, state) VALUES (1, 1, 1, 'ui', 'running')"
|
||||
)
|
||||
.run();
|
||||
handle
|
||||
.prepare(
|
||||
"INSERT INTO beta_servers (beta_id, server, had_original, stage) VALUES (1, 'server', 0, 'deploying')"
|
||||
)
|
||||
.run();
|
||||
handle
|
||||
.prepare(
|
||||
"UPDATE beta_servers SET stage = 'failed', error = 'write failed' WHERE beta_id = 1"
|
||||
)
|
||||
.run();
|
||||
handle
|
||||
.prepare(
|
||||
"UPDATE beta_servers SET stage = 'failed', error = 'write failed', updated_at = 'later' WHERE beta_id = 1"
|
||||
)
|
||||
.run();
|
||||
handle
|
||||
.prepare(
|
||||
"UPDATE beta_servers SET stage = 'awaiting-empty', error = NULL WHERE beta_id = 1"
|
||||
)
|
||||
.run();
|
||||
handle.prepare("UPDATE betas SET state = 'rolled-back' WHERE id = 1").run();
|
||||
const before = handle.prepare('SELECT * FROM beta_events ORDER BY id').all();
|
||||
expect(before).toHaveLength(5);
|
||||
expect(before).toEqual(expect.arrayContaining([expect.objectContaining({ message: 'server: Failed — write failed' })]));
|
||||
expect(before).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ message: 'server: Failed — write failed' })
|
||||
])
|
||||
);
|
||||
closeDb();
|
||||
expect(db().prepare('SELECT * FROM beta_events ORDER BY id').all()).toEqual(before);
|
||||
});
|
||||
@@ -30,15 +67,33 @@ describe('persistent activity', () => {
|
||||
const { db } = await import('../../src/lib/server/db/index.js');
|
||||
const handle = db();
|
||||
handle.prepare("INSERT INTO servers (name) VALUES ('server')").run();
|
||||
handle.prepare("INSERT INTO deployments (id, action, started_at) VALUES ('campaign', 'promote', '2026-09-17T00:00:00Z')").run();
|
||||
handle.prepare("INSERT INTO deployment_files (deployment_id, server, verb, rel_path) VALUES ('campaign', 'server', 'updated', 'test.gsc')").run();
|
||||
handle.prepare("INSERT INTO deployment_servers (deployment_id, server, files_changed, restart_state) VALUES ('campaign', 'server', 1, 'pending')").run();
|
||||
handle.prepare("UPDATE deployment_servers SET attempts = attempts + 1, checked_at = 'later'").run();
|
||||
handle
|
||||
.prepare(
|
||||
"INSERT INTO deployments (id, action, started_at) VALUES ('campaign', 'promote', '2026-09-17T00:00:00Z')"
|
||||
)
|
||||
.run();
|
||||
handle
|
||||
.prepare(
|
||||
"INSERT INTO deployment_files (deployment_id, server, verb, rel_path) VALUES ('campaign', 'server', 'updated', 'test.gsc')"
|
||||
)
|
||||
.run();
|
||||
handle
|
||||
.prepare(
|
||||
"INSERT INTO deployment_servers (deployment_id, server, files_changed, restart_state) VALUES ('campaign', 'server', 1, 'pending')"
|
||||
)
|
||||
.run();
|
||||
handle
|
||||
.prepare("UPDATE deployment_servers SET attempts = attempts + 1, checked_at = 'later'")
|
||||
.run();
|
||||
expect(handle.prepare('SELECT * FROM campaign_events').all()).toHaveLength(3);
|
||||
handle.prepare("UPDATE deployment_servers SET restarted_at = '2026-09-17T00:01:00Z'").run();
|
||||
handle.prepare("UPDATE deployment_servers SET restart_state = 'done'").run();
|
||||
handle.prepare("UPDATE deployments SET finished_at = '2026-09-17T00:02:00Z'").run();
|
||||
const messages = (handle.prepare('SELECT message FROM campaign_events ORDER BY id').all() as { message: string }[]).map(e => e.message);
|
||||
const messages = (
|
||||
handle.prepare('SELECT message FROM campaign_events ORDER BY id').all() as {
|
||||
message: string;
|
||||
}[]
|
||||
).map((e) => e.message);
|
||||
expect(messages).toHaveLength(6);
|
||||
expect(messages).toContain('server: Restart requested; verifying health');
|
||||
expect(messages).toContain('server: Server complete');
|
||||
@@ -48,26 +103,75 @@ describe('persistent activity', () => {
|
||||
});
|
||||
|
||||
describe('operation progress', () => {
|
||||
const server = { stage: 'awaiting-empty', deployed_at: 'now', restarted_at: null, healthy_at: null, match_seen_at: null };
|
||||
const server = {
|
||||
stage: 'awaiting-empty',
|
||||
deployed_at: 'now',
|
||||
restarted_at: null,
|
||||
healthy_at: null,
|
||||
match_seen_at: null
|
||||
};
|
||||
it('uses the slowest beta server and omits disabled gates', () => {
|
||||
const flow = betaFlow({ state: 'running', wait_for_health: 0, wait_for_match: 0 }, [server, { ...server, stage: 'ready' }]);
|
||||
expect(flow.map(s => s.label)).toEqual(['Stage file changes', 'Wait for empty servers', 'Apply files and restart', 'Review and promote']);
|
||||
expect(flow.map(s => s.state)).toEqual(['done', 'current', 'pending', 'pending']);
|
||||
const flow = betaFlow({ state: 'running', wait_for_health: 0, wait_for_match: 0 }, [
|
||||
server,
|
||||
{ ...server, stage: 'ready' }
|
||||
]);
|
||||
expect(flow.map((s) => s.label)).toEqual([
|
||||
'Stage file changes',
|
||||
'Wait for empty servers',
|
||||
'Apply files and restart',
|
||||
'Review and promote'
|
||||
]);
|
||||
expect(flow.map((s) => s.state)).toEqual(['done', 'current', 'pending', 'pending']);
|
||||
});
|
||||
it('does not mark a failed deployment as completed', () => {
|
||||
expect(betaFlow({ state: 'running', wait_for_health: 1, wait_for_match: 1 }, [{ ...server, stage: 'failed', deployed_at: null }])[0].state).toBe('failed');
|
||||
expect(campaignFlow([{ restart_state: 'failed', files_changed: 0, restarted_at: null }], false)[0].state).toBe('failed');
|
||||
expect(
|
||||
betaFlow({ state: 'running', wait_for_health: 1, wait_for_match: 1 }, [
|
||||
{ ...server, stage: 'failed', deployed_at: null }
|
||||
])[0].state
|
||||
).toBe('failed');
|
||||
expect(
|
||||
campaignFlow(
|
||||
[{ restart_state: 'failed', files_changed: 0, restarted_at: null }],
|
||||
false
|
||||
)[0].state
|
||||
).toBe('failed');
|
||||
});
|
||||
it('requires health verification after campaign restart and preserves manual work', () => {
|
||||
expect(campaignFlow([{ restart_state: 'pending', files_changed: 1, restarted_at: 'now' }], false).map(s => s.state)).toEqual(['done', 'done', 'done', 'done', 'current']);
|
||||
expect(campaignFlow([{ restart_state: 'manual', files_changed: 1, restarted_at: null }], true).some(s => s.state === 'current')).toBe(false);
|
||||
expect(campaignFlow([{ restart_state: 'done', files_changed: 1, restarted_at: 'now' }], true).every(s => s.state === 'done')).toBe(true);
|
||||
expect(
|
||||
campaignFlow(
|
||||
[{ restart_state: 'pending', files_changed: 1, restarted_at: 'now' }],
|
||||
false
|
||||
).map((s) => s.state)
|
||||
).toEqual(['done', 'done', 'done', 'done', 'current']);
|
||||
expect(
|
||||
campaignFlow(
|
||||
[{ restart_state: 'manual', files_changed: 1, restarted_at: null }],
|
||||
true
|
||||
).some((s) => s.state === 'current')
|
||||
).toBe(false);
|
||||
expect(
|
||||
campaignFlow(
|
||||
[{ restart_state: 'done', files_changed: 1, restarted_at: 'now' }],
|
||||
true
|
||||
).every((s) => s.state === 'done')
|
||||
).toBe(true);
|
||||
});
|
||||
it('shows snapshot confirmation before file application and restart', () => {
|
||||
const servers = [{ server: 'test', restart_state: 'pending', files_changed: 1, restarted_at: null }];
|
||||
const servers = [
|
||||
{ server: 'test', restart_state: 'pending', files_changed: 1, restarted_at: null }
|
||||
];
|
||||
const files = [{ server: 'test', snapshot_pending: 1, apply_state: 'applying' }];
|
||||
expect(campaignFlow(servers, false, files)[1]).toEqual({ label: 'Capture rollback snapshots', state: 'current' });
|
||||
expect(campaignFlow(servers, false, [{ ...files[0], snapshot_pending: 0 }])[2].state).toBe('current');
|
||||
expect(campaignFlow(servers, false, [{ ...files[0], snapshot_pending: 0, apply_state: 'applied' }])[3].state).toBe('current');
|
||||
expect(campaignFlow(servers, false, files)[1]).toEqual({
|
||||
label: 'Capture rollback snapshots',
|
||||
state: 'current'
|
||||
});
|
||||
expect(campaignFlow(servers, false, [{ ...files[0], snapshot_pending: 0 }])[2].state).toBe(
|
||||
'current'
|
||||
);
|
||||
expect(
|
||||
campaignFlow(servers, false, [
|
||||
{ ...files[0], snapshot_pending: 0, apply_state: 'applied' }
|
||||
])[3].state
|
||||
).toBe('current');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user