Files
electric-cherry/tests/unit/maintenance-api.test.ts

118 lines
5.1 KiB
TypeScript

import { beforeEach, afterEach, describe, it, expect, vi } from 'vitest';
import { newFixture, type Fixture } from '../helpers/fixture.js';
vi.mock('../../src/lib/server/pipeline/worker.js', () => ({
kick: vi.fn().mockResolvedValue(undefined)
}));
let fixture: Fixture;
beforeEach(async () => {
vi.resetModules();
fixture = await newFixture();
});
afterEach(async () => {
await fixture.cleanup();
});
const request = (body: unknown) =>
new Request('http://localhost/api/maintenance', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
describe('Maintenance API', () => {
it('saves independent policies and the global pause without changing the restart schedule', async () => {
const { POST, GET } = await import('../../src/routes/api/maintenance/+server.js');
expect(
(
await POST({
request: request({
action: 'schedule',
enabled: true,
time: '06:00',
timeZone: 'America/Indiana/Indianapolis'
})
})
).status
).toBe(200);
const before = (await GET().json()).runtime.schedule;
for (const body of [
{ action: 'policy', policy: 'health', enabled: false },
{ action: 'policy', policy: 'updates', enabled: true },
{ action: 'pause', paused: true }
]) {
expect((await POST({ request: request(body) })).status).toBe(200);
}
const data = await GET().json();
expect(data.policies).toEqual({ paused: true, health: false, drift: true, updates: true });
expect(data.runtime.schedule).toEqual(before);
expect((await POST({ request: request({ action: 'update' }) })).status).toBe(400);
});
it('rejects malformed actions without changing policies or queuing work', async () => {
const { POST, GET } = await import('../../src/routes/api/maintenance/+server.js');
const before = await GET().json();
for (const body of [
null,
[],
{},
{ action: 'pause', paused: 'yes' },
{ action: 'policy', policy: 'unknown', enabled: true },
{ action: 'policy', policy: 'updates', enabled: 1 },
{ action: 'schedule', enabled: true, time: '25:00', timeZone: 'UTC' },
{ action: 'schedule', enabled: true, time: '06:00', timeZone: 'invalid' },
{ action: 'update', servers: [] },
{ action: 'update', servers: [1] },
{ action: 'retry', id: '1' }
]) {
expect((await POST({ request: request(body) })).status).toBe(400);
}
expect(await GET().json()).toEqual(before);
});
it('opens one retained request outside the recent history window without changing normal history', async () => {
const { db } = await import('../../src/lib/server/db/index.js');
const insert = db().prepare(
"INSERT INTO maintenance_jobs(server,state,requested_at,kind,origin) VALUES(?,'done',?,'schedule','schedule')"
);
db().transaction(() => {
for (let i = 0; i < 105; i++)
insert.run(
't5_zombie_theater_1',
new Date(Date.UTC(2026, 0, 1, 0, i)).toISOString()
);
})();
const { GET } = await import('../../src/routes/api/maintenance/+server.js');
const ordinary = await GET().json();
expect(ordinary.runtime.jobs).toHaveLength(100);
expect(ordinary.runtime.jobs.some((job: { id: number }) => job.id === 1)).toBe(false);
const selected = await GET({
url: new URL('http://localhost/api/maintenance?job=1')
}).json();
expect(selected.runtime.jobs).toHaveLength(101);
expect(selected.runtime.jobs.find((job: { id: number }) => job.id === 1)).toMatchObject({
state: 'done',
kind: 'schedule'
});
for (const job of ['0', '-1', '', '1.5', 'bad', '99999999999999999999'])
expect(
GET({ url: new URL(`http://localhost/api/maintenance?job=${job}`) }).status
).toBe(400);
expect(await GET().json()).toEqual(ordinary);
});
it('keeps old Watchdog URLs working through query-preserving redirects', async () => {
const index = await import('../../src/routes/watchdog/+page.server.js');
const detail = await import('../../src/routes/watchdog/[id]/+page.server.js');
expect(() =>
index.load({ url: new URL('http://localhost/watchdog?kind=drift&server=test') })
).toThrow(
expect.objectContaining({
status: 308,
location: '/maintenance?kind=drift&server=test'
})
);
expect(() =>
detail.load({
params: { id: 'abc' },
url: new URL('http://localhost/watchdog/abc?test=1')
})
).toThrow(expect.objectContaining({ status: 308, location: '/maintenance/abc?test=1' }));
});
});