import { describe, expect, it } from "vitest"; import { createMemoryDb, repos, runMigrations } from "@cfdm/db"; import { SYNC_SYNCED } from "@cfdm/shared"; import type { CloudflareClient } from "../src/lib/cf-client.js"; import * as dnsService from "../src/services/dns-service.js"; import { updateConfig } from "../src/services/service-config-service.js"; type CfRec = { id: string; type: string; name: string; content: string; ttl: number; proxied: boolean; }; function setupDb() { const { db, sqlite } = createMemoryDb(); runMigrations(sqlite); return db; } describe("DNS identity by name + content", () => { it("deletes by name+IP when cached cf_record_id is stale/missing in CF", async () => { const db = setupDb(); const remote: CfRec[] = [ { id: "cf-live", type: "A", name: "nsgt.example.com", content: "130.49.213.176", ttl: 1, proxied: false, }, ]; const deleted: string[] = []; const cf = { listDnsRecords: async () => [...remote], createDnsRecord: async () => { throw new Error("create should not run"); }, updateDnsRecord: async () => { throw new Error("update should not run"); }, deleteDnsRecord: async (_zoneId: string, id: string) => { deleted.push(id); const idx = remote.findIndex((r) => r.id === id); if (idx >= 0) remote.splice(idx, 1); }, verifyToken: async () => true, listZones: async () => [], } as unknown as CloudflareClient; const domain = repos.createDomain(db, null, "example.com", "zone-1"); const local = repos.insertDnsRecord( db, domain.id, "A", "nsgt.example.com", "130.49.213.176", 1, false, null, SYNC_SYNCED, "local", "cf-stale-gone", // not present in Cloudflare ); await dnsService.deleteRecord(db, cf, domain.id, local.id); expect(deleted).toEqual(["cf-live"]); expect(repos.listDnsByDomain(db, domain.id)).toEqual([]); expect(remote).toEqual([]); }); it("delete is no-op success when record already removed outside CFDM", async () => { const db = setupDb(); const cf = { listDnsRecords: async () => [], deleteDnsRecord: async () => { throw new Error("Record does not exist"); }, verifyToken: async () => true, listZones: async () => [], } as unknown as CloudflareClient; const domain = repos.createDomain(db, null, "example.com", "zone-1"); const local = repos.insertDnsRecord( db, domain.id, "A", "nsgt.example.com", "130.49.213.176", 1, false, null, SYNC_SYNCED, "local", "cf-already-gone", ); await expect( dnsService.deleteRecord(db, cf, domain.id, local.id), ).resolves.toBeUndefined(); expect(repos.listDnsByDomain(db, domain.id)).toEqual([]); }); it("updateConfig can remove extra FQDN when CF id is stale", async () => { const db = setupDb(); const remote: CfRec[] = [ { id: "cf-gt", type: "A", name: "gt.example.com", content: "130.49.213.176", ttl: 1, proxied: false, }, { id: "cf-nsgt-live", type: "A", name: "nsgt.example.com", content: "130.49.213.176", ttl: 1, proxied: false, }, ]; const deleted: string[] = []; const cf = { listDnsRecords: async () => [...remote], createDnsRecord: async ( _zoneId: string, payload: { type: string; name: string; content: string }, ) => { const rec: CfRec = { id: `cf-new-${remote.length}`, type: payload.type, name: payload.name, content: payload.content, ttl: 1, proxied: false, }; remote.push(rec); return rec; }, updateDnsRecord: async ( _zoneId: string, id: string, payload: { type: string; name: string; content: string }, ) => { const idx = remote.findIndex((r) => r.id === id); if (idx < 0) throw new Error("Record does not exist"); const rec: CfRec = { id, type: payload.type, name: payload.name, content: payload.content, ttl: 1, proxied: false, }; remote[idx] = rec; return rec; }, deleteDnsRecord: async (_zoneId: string, id: string) => { const idx = remote.findIndex((r) => r.id === id); if (idx < 0) throw new Error("Record does not exist"); deleted.push(id); remote.splice(idx, 1); }, verifyToken: async () => true, listZones: async () => [ { id: "zone-1", name: "example.com", status: "active" }, ], } as unknown as CloudflareClient; repos.createDomain(db, null, "example.com", "zone-1"); const service = repos.createService(db, "Main TG", "tg-pr"); repos.setServiceEnabled(db, service.id, true); await updateConfig(db, cf, service.id, { ips: ["130.49.213.176"], domains: [ { fqdn: "gt.example.com", target_ips: ["130.49.213.176"] }, { fqdn: "nsgt.example.com", target_ips: ["130.49.213.176"] }, ], }); // Poison cached id on extra binding's DNS row. const nsgtBinding = repos .listBindingsByService(db, service.id) .find((b) => b.hostname === "nsgt")!; const nsgtRecords = repos.listRecordsForBinding(db, nsgtBinding.id); for (const row of nsgtRecords) { repos.updateDnsFields( db, row.id, row.record_type, row.name, row.content, row.ttl, row.proxied, row.priority, SYNC_SYNCED, "cf-stale-nsgt", null, ); } const view = await updateConfig(db, cf, service.id, { ips: ["130.49.213.176"], domains: [{ fqdn: "gt.example.com", target_ips: ["130.49.213.176"] }], }); expect(view.domains.map((d) => d.fqdn)).toEqual(["gt.example.com"]); expect(deleted).toContain("cf-nsgt-live"); expect(remote.some((r) => r.name.includes("nsgt"))).toBe(false); }); });