import { describe, expect, test } from "bun:test"; import { MaintenanceService, maintenanceMiddleware, maintenanceRoute, normalizeMaintenanceHost, type MaintenancePersistence, } from "../../server/maintenance"; import { createApp } from "../../server/app"; import { hashToken, MemoryAuthStore } from "../../server/auth"; class MemoryPersistence implements MaintenancePersistence { state: { hosts: string[] } | undefined; resources: Record[] = []; deleted = 0; routePresent = false; failApply = false; failWrite = false; async readState() { return this.state; } async writeState(value: { hosts: string[] }) { if (this.failWrite) throw new Error("state write failed"); this.state = value; } async routeExists() { return this.routePresent; } async apply(resource: Record) { if (this.failApply) throw new Error("route apply failed"); this.resources.push(resource); if (resource.kind === "IngressRoute") this.routePresent = true; } async deleteRoute() { this.deleted += 1; this.routePresent = false; } } const lease = { acquire: async () => ({ release: async () => {} }) }; describe("maintenance override", () => { test("normalizes DNS hostnames and leading wildcards only", () => { expect(normalizeMaintenanceHost(" Sub.Domain.COM. ")).toBe( "sub.domain.com", ); expect(normalizeMaintenanceHost(" *.EXAMPLE.COM. ")).toBe("*.example.com"); expect(normalizeMaintenanceHost(" *.Sub.Example.COM. ")).toBe("*.sub.example.com"); for (const host of [ "http://example.com", "example.com:443", "127.0.0.1", "[::1]", "*", "*.", "example.*.com", "a.*.example.com", "**.example.com", "*.*.example.com", "*.example.com:443", "*.127.0.0.1", "example", "a..com", ]) expect(() => normalizeMaintenanceHost(host)).toThrow(); }); test("enables, deduplicates, and disables the last host", async () => { const persistence = new MemoryPersistence(); const service = new MaintenanceService(persistence, lease); expect((await service.status("a.example.com")).enabled).toBe(false); expect((await service.toggle("A.example.com")).hosts).toEqual([ "a.example.com", ]); persistence.state = { hosts: ["a.example.com", "b.example.com", "A.example.com"], }; expect((await service.toggle("a.example.com")).hosts).toEqual([ "b.example.com", ]); expect((await service.toggle("b.example.com")).hosts).toEqual([]); expect(persistence.deleted).toBe(2); expect(persistence.state).toEqual({ hosts: [] }); }); test("persists normalized wildcards and renders them as Host rules", async () => { const persistence = new MemoryPersistence(); const service = new MaintenanceService(persistence, lease); expect(await service.set(" *.EXAMPLE.COM. ", true)).toMatchObject({ host: "*.example.com", enabled: true, hosts: ["*.example.com"], }); expect(persistence.state).toEqual({ hosts: ["*.example.com"] }); expect(persistence.resources.at(-1)).toMatchObject({ spec: { routes: [{ match: "Host(`*.example.com`)" }] }, }); expect(await service.status("*.EXAMPLE.COM.")).toMatchObject({ host: "*.example.com", enabled: true, }); expect(await service.set("*.example.com", false)).toMatchObject({ enabled: false, hosts: [], }); }); test("renders the single shared error route and rewrite middleware", () => { expect(maintenanceMiddleware()).toMatchObject({ metadata: { name: "maintenance-override", namespace: "routing" }, spec: { replacePathRegex: { regex: "^/.*", replacement: "/__error/1001" }, }, }); expect(maintenanceRoute(["a.example.com", "b.example.com"])).toMatchObject({ metadata: { name: "maintenance-override", namespace: "routing" }, spec: { routes: [ { match: "Host(`a.example.com`) || Host(`b.example.com`)", priority: 1_000_000, services: [ { name: "error-page", namespace: "routing", port: 3000, scheme: "http", }, ], }, ], }, }); expect(maintenanceRoute(["*.example.com", "a.example.com"])).toMatchObject({ spec: { routes: [{ match: "Host(`*.example.com`) || Host(`a.example.com`)" }] }, }); }); test("maps an unavailable global lease to a retryable API conflict", async () => { const persistence = new MemoryPersistence(); const store = new MemoryAuthStore(); await store.putUser({ username: "operator", passwordHash: "hash", roles: ["operator"] }); await store.putSession({ tokenHash: hashToken("operator-token"), username: "operator", roles: ["operator"], expiresAt: "2099-01-01T00:00:00.000Z" }); const app = createApp({ store, maintenance: new MaintenanceService(persistence, { acquire: async () => undefined }), }); const result = await app(new Request("https://kuber.astrxl.dev/api/v2/maintenance/a.example.com", { method: "POST", headers: { authorization: "Bearer operator-token", "content-type": "application/json" }, body: JSON.stringify({ enabled: true }), })); expect(result.status).toBe(409); expect(result.headers.get("retry-after")).toBe("1"); expect(await result.json()).toMatchObject({ code: "MAINTENANCE_BUSY" }); expect(persistence.state).toBeUndefined(); }); test("recovers the persisted desired state after route or state failures", async () => { const persistence = new MemoryPersistence(); const service = new MaintenanceService(persistence, lease); persistence.failApply = true; await expect(service.set("a.example.com", true)).rejects.toThrow("route apply failed"); expect(persistence.state).toBeUndefined(); persistence.failApply = false; expect(await service.status("a.example.com")).toMatchObject({ enabled: false }); persistence.failWrite = true; await expect(service.set("a.example.com", true)).rejects.toThrow("state write failed"); expect(persistence.routePresent).toBe(true); persistence.failWrite = false; expect(await service.status("a.example.com")).toMatchObject({ enabled: false }); expect(persistence.routePresent).toBe(false); expect(await service.set("a.example.com", true)).toMatchObject({ enabled: true }); persistence.routePresent = false; expect(await service.status("a.example.com")).toMatchObject({ enabled: true }); expect(persistence.routePresent).toBe(true); }); test("requires kubernetes write without workspace or trust context", async () => { const store = new MemoryAuthStore(); await store.putUser({ username: "viewer", passwordHash: "hash", roles: ["viewer"], }); await store.putUser({ username: "operator", passwordHash: "hash", roles: ["operator"], }); for (const token of ["viewer-token", "operator-token"]) await store.putSession({ tokenHash: hashToken(token), username: token.startsWith("viewer") ? "viewer" : "operator", roles: token.startsWith("viewer") ? ["viewer"] : ["operator"], expiresAt: "2099-01-01T00:00:00.000Z", }); await store.createApiKey({ id: "maintenance-scoped-key", tokenHash: hashToken("scoped-key"), username: "operator", capabilities: ["kubernetes:write"], workspace: "shop", expiresAt: "2099-01-01T00:00:00.000Z", }); await store.createApiKey({ id: "maintenance-global-key", tokenHash: hashToken("global-key"), username: "operator", capabilities: ["kubernetes:write"], expiresAt: "2099-01-01T00:00:00.000Z", }); const persistence = new MemoryPersistence(); const app = createApp({ store, maintenance: new MaintenanceService(persistence, lease), }); const viewer = await app( new Request("https://kuber.astrxl.dev/api/v2/maintenance/a.example.com", { headers: { authorization: "Bearer viewer-token" }, }), ); expect(viewer.status).toBe(403); const scoped = await app( new Request("https://kuber.astrxl.dev/api/v2/maintenance/a.example.com", { headers: { authorization: "Bearer scoped-key" }, }), ); expect(scoped.status).toBe(403); expect(await scoped.json()).toMatchObject({ code: "MAINTENANCE_GLOBAL_SCOPE_REQUIRED" }); const global = await app( new Request("https://kuber.astrxl.dev/api/v2/maintenance/a.example.com", { headers: { authorization: "Bearer global-key" }, }), ); expect(global.status).toBe(200); const status = await app( new Request("https://kuber.astrxl.dev/api/v2/maintenance/a.example.com", { headers: { authorization: "Bearer operator-token" }, }), ); expect(await status.json()).toMatchObject({ enabled: false }); const toggle = await app( new Request("https://kuber.astrxl.dev/api/v2/maintenance/a.example.com", { method: "POST", headers: { authorization: "Bearer operator-token", "content-type": "application/json", }, body: JSON.stringify({ enabled: true }), }), ); expect(await toggle.json()).toMatchObject({ enabled: true }); }); });