118 lines
5.1 KiB
TypeScript
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' }));
|
|
});
|
|
});
|