/** * Unit tests for the DNS registration ledger * (designs/DISPATCHER_DAEMON_AND_TIMER_EVENTS.md B2): upsert semantics, * the registerHost recording wrapper (success-only), refresh stamping, * companion rows, and the consumer-set lifecycle (design.md D5) — a row * survives every consumer but the last. */ import { afterEach, beforeEach, describe, expect, test } from 'bun:test'; import { mkdtempSync, rmSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import type { DnsRegistrarCapability } from '@celilo/capabilities'; import { eq } from 'drizzle-orm'; import { type DbClient, getDb } from '../db/client'; import { modules } from '../db/schema'; import { resetTestDbPath } from '../test-utils/db-path'; import { listDnsRegistrations, recordDnsRegistration, stampDnsRegistrationsRefreshed, withDnsRegistrationLedger, } from './dns-registrations'; describe('dns_registrations ledger', () => { let tempDir: string; let db: DbClient; beforeEach(() => { tempDir = mkdtempSync(join(tmpdir(), 'celilo-dnsreg-')); process.env.CELILO_DB_PATH = join(tempDir, 'test.db'); db = getDb(); for (const id of ['namecheap', 'caddy', 'authentik']) { db.insert(modules) .values({ id, name: id, sourcePath: join(tempDir, id), version: '1.0.0', manifestData: { celilo_contract: '1.0', id, name: id, version: '1.0.0' }, }) .run(); } }); afterEach(() => { rmSync(tempDir, { recursive: true, force: true }); resetTestDbPath(); }); test('record + list roundtrip; a re-assert is an upsert, not a second row', () => { recordDnsRegistration(db, { providerModuleId: 'namecheap', consumerModuleId: 'caddy', fqdn: 'www.example.net', }); recordDnsRegistration(db, { providerModuleId: 'namecheap', consumerModuleId: 'caddy', fqdn: 'www.example.net', }); const rows = listDnsRegistrations(db, { providerModuleId: 'namecheap' }); expect(rows.length).toBe(1); expect(rows[0].fqdn).toBe('www.example.net'); expect(rows[0].consumerModuleId).toBe('caddy'); expect(rows[0].companion).toBe(false); expect(rows[0].refreshedAt).toBeNull(); }); test('stampDnsRegistrationsRefreshed sets refreshedAt for the provider', () => { recordDnsRegistration(db, { providerModuleId: 'namecheap', consumerModuleId: 'caddy', fqdn: 'git.example.net', }); stampDnsRegistrationsRefreshed(db, 'namecheap'); const [row] = listDnsRegistrations(db, { providerModuleId: 'namecheap' }); expect(row.refreshedAt).not.toBeNull(); }); // ── design.md D5: attribution is a set, and it is load-bearing ───────────── test('a blanket re-assert ADDS a consumer instead of overwriting the introducer', () => { recordDnsRegistration(db, { providerModuleId: 'namecheap', consumerModuleId: 'authentik', fqdn: 'auth.example.net', }); // What `run-hook caddy on_install` does to every served name. recordDnsRegistration(db, { providerModuleId: 'namecheap', consumerModuleId: 'caddy', fqdn: 'auth.example.net', }); const [row] = listDnsRegistrations(db); expect(row.consumerModuleIds).toEqual(['authentik', 'caddy']); // The module that introduced the name is still identifiable. expect(row.consumerModuleId).toBe('authentik'); }); test('the row survives a consumer removal and dies with the last one', () => { recordDnsRegistration(db, { providerModuleId: 'namecheap', consumerModuleId: 'authentik', fqdn: 'auth.example.net', }); recordDnsRegistration(db, { providerModuleId: 'namecheap', consumerModuleId: 'caddy', fqdn: 'auth.example.net', }); // Removing caddy on the live fleet would, under the single-value column, // have cascade-deleted a name authentik still serves. db.delete(modules).where(eq(modules.id, 'caddy')).run(); const [row] = listDnsRegistrations(db); expect(row.fqdn).toBe('auth.example.net'); expect(row.consumerModuleIds).toEqual(['authentik']); db.delete(modules).where(eq(modules.id, 'authentik')).run(); expect(listDnsRegistrations(db).length).toBe(0); }); test('rows die with the provider module (FK cascade)', () => { recordDnsRegistration(db, { providerModuleId: 'namecheap', consumerModuleId: 'caddy', fqdn: 'www.example.net', }); db.delete(modules).where(eq(modules.id, 'namecheap')).run(); expect(listDnsRegistrations(db).length).toBe(0); }); test('withDnsRegistrationLedger records successes only', async () => { const calls: string[] = []; const fake: DnsRegistrarCapability = { async registerHost(request) { calls.push(request.fqdn); const success = !request.fqdn.startsWith('fail.'); return { success, outputs: {}, duration: 1 }; }, }; const wrapped = withDnsRegistrationLedger(fake, { db, providerModuleId: 'namecheap', consumerModuleId: 'caddy', }); await wrapped.registerHost({ fqdn: 'www.example.net' }); await wrapped.registerHost({ fqdn: 'fail.example.net' }); await wrapped.registerHost({ fqdn: 'auto.example.net' }); expect(calls.length).toBe(3); const fqdns = listDnsRegistrations(db) .map((r) => r.fqdn) .sort(); expect(fqdns).toEqual(['auto.example.net', 'www.example.net']); }); test('a companion the provider attempted gets its own watched row', async () => { const fake: DnsRegistrarCapability = { async registerHost(request) { return { success: true, outputs: { companion_fqdn: `www.${request.fqdn}` }, duration: 1, }; }, }; const wrapped = withDnsRegistrationLedger(fake, { db, providerModuleId: 'namecheap', consumerModuleId: 'caddy', }); await wrapped.registerHost({ fqdn: 'example.net' }); const rows = listDnsRegistrations(db).sort((a, b) => a.fqdn.localeCompare(b.fqdn)); expect(rows.map((r) => r.fqdn)).toEqual(['example.net', 'www.example.net']); expect(rows.map((r) => r.companion)).toEqual([false, true]); }); });