import { afterEach, describe, expect, it, vi } from "vitest"; import { repos } from "@cfdm/db"; import { CERT_ERROR, CERT_MONITOR_REQUIRED, CERT_MONITOR_SKIPPED, } from "@cfdm/shared"; import { buildApp } from "../src/app.js"; import { loadConfig } from "../src/config.js"; import * as certificateService from "../src/services/certificate-service.js"; async function authHeaders(app: Awaited>) { const config = loadConfig(); const res = await app.inject({ method: "POST", url: "/api/v1/auth/login", payload: { username: config.adminUsername, password: "admin" }, }); expect(res.statusCode).toBe(200); const { token } = res.json() as { token: string }; return { authorization: `Bearer ${token}` }; } describe("certificates", () => { afterEach(() => { vi.restoreAllMocks(); }); it("auto mode does not monitor DNS-only domain", async () => { const testApp = await buildApp({ config: { ...loadConfig(), staticDir: null }, memory: true, }); const headers = await authHeaders(testApp); const domain = repos.createDomain( testApp.db, null, "dns-only.example.com", "cf-zone-dns", ); vi.spyOn(certificateService, "checkHostname").mockResolvedValue({ expiresAt: null, error: "connection refused", }); const checkRes = await testApp.inject({ method: "POST", url: "/api/v1/certificates/check", headers, }); expect(checkRes.statusCode).toBe(200); const certs = repos.listCertificates(testApp.db); expect(certs.find((c) => c.hostname === domain.zone_name)).toBeUndefined(); await testApp.close(); }); it("monitors host with enabled service binding", async () => { const testApp = await buildApp({ config: { ...loadConfig(), staticDir: null }, memory: true, }); const headers = await authHeaders(testApp); const domain = repos.createDomain( testApp.db, null, "app.example.com", "cf-zone-app", ); const service = repos.createService(testApp.db, "Web", "web"); repos.setServiceEnabled(testApp.db, service.id, true); repos.insertBinding(testApp.db, domain.id, service.id, "api", null); const expiresAt = new Date(Date.now() + 90 * 24 * 60 * 60 * 1000); vi.spyOn(certificateService, "checkHostname").mockResolvedValue({ expiresAt, error: null, }); await testApp.inject({ method: "POST", url: "/api/v1/certificates/check", headers, }); const certs = repos.listCertificates(testApp.db); expect(certs.some((c) => c.hostname === "api.app.example.com")).toBe(true); expect(certs.some((c) => c.hostname === "app.example.com")).toBe(false); await testApp.close(); }); it("does not monitor host when service is disabled", async () => { const testApp = await buildApp({ config: { ...loadConfig(), staticDir: null }, memory: true, }); const headers = await authHeaders(testApp); const domain = repos.createDomain( testApp.db, null, "off.example.com", "cf-zone-off", ); const service = repos.createService(testApp.db, "Off", "off"); repos.insertBinding(testApp.db, domain.id, service.id, "@", null); vi.spyOn(certificateService, "checkHostname").mockResolvedValue({ expiresAt: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000), error: null, }); await testApp.inject({ method: "POST", url: "/api/v1/certificates/check", headers, }); expect( repos.listCertificates(testApp.db).some((c) => c.hostname === domain.zone_name), ).toBe(false); await testApp.close(); }); it("required apex is monitored without bindings", async () => { const testApp = await buildApp({ config: { ...loadConfig(), staticDir: null }, memory: true, }); const headers = await authHeaders(testApp); const domain = repos.createDomain( testApp.db, null, "required.example.com", "cf-zone-req", ); repos.updateDomain( testApp.db, domain.id, null, "active", CERT_MONITOR_REQUIRED, ); vi.spyOn(certificateService, "checkHostname").mockResolvedValue({ expiresAt: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000), error: null, }); await testApp.inject({ method: "POST", url: "/api/v1/certificates/check", headers, }); expect( repos.listCertificates(testApp.db).some( (c) => c.hostname === domain.zone_name, ), ).toBe(true); await testApp.close(); }); it("skipped apex removes stale certificate on check", async () => { const testApp = await buildApp({ config: { ...loadConfig(), staticDir: null }, memory: true, }); const headers = await authHeaders(testApp); const domain = repos.createDomain( testApp.db, null, "skipped.example.com", "cf-zone-skip", ); repos.upsertCertificateCheck( testApp.db, domain.id, null, domain.zone_name, null, CERT_ERROR, "stale", ); repos.updateDomain( testApp.db, domain.id, null, "active", CERT_MONITOR_SKIPPED, ); vi.spyOn(certificateService, "checkHostname").mockResolvedValue({ expiresAt: null, error: "should not be called", }); await testApp.inject({ method: "POST", url: "/api/v1/certificates/check", headers, }); expect(repos.listCertificates(testApp.db)).toHaveLength(0); expect(certificateService.checkHostname).not.toHaveBeenCalled(); await testApp.close(); }); it("TLS failure on monitored host is stored as error", async () => { const testApp = await buildApp({ config: { ...loadConfig(), staticDir: null }, memory: true, }); const headers = await authHeaders(testApp); const domain = repos.createDomain( testApp.db, null, "broken.example.com", "cf-zone-broken", ); repos.updateDomain( testApp.db, domain.id, null, "active", CERT_MONITOR_REQUIRED, ); vi.spyOn(certificateService, "checkHostname").mockResolvedValue({ expiresAt: null, error: "certificate has expired", }); await testApp.inject({ method: "POST", url: "/api/v1/certificates/check", headers, }); const cert = repos.listCertificates(testApp.db)[0]; expect(cert?.status).toBe(CERT_ERROR); expect(cert?.last_error).toBeTruthy(); await testApp.close(); }); });