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' })); }); });