/** * lib/services/route53-sync-service.ts unit tests. * * Only buildDriftHistoryRows is exercised here — a pure function taking * hand-constructed NormalizedRecordSet maps, no AWS or Postgres mocking * required (matches the plan's stated test scope for this file). */ import { describe, it, expect } from 'vitest'; import { buildDriftHistoryRows } from './route53-sync-service'; import type { NormalizedRecordSet } from './route53-record-key'; function makeRecordSet(overrides: Partial = {}): NormalizedRecordSet { return { recordKey: 'Z123:www.example.com.:A:', zoneId: 'Z123', name: 'www.example.com.', type: 'A', setIdentifier: null, ttl: 300, resourceRecords: [{ value: '10.0.0.1' }], aliasTarget: null, ...overrides, }; } describe('buildDriftHistoryRows', () => { it('produces one update row with non-null before/after when TTL changes', () => { const key = 'Z123:www.example.com.:A:'; const prev = makeRecordSet({ ttl: 300 }); const next = makeRecordSet({ ttl: 600 }); const rows = buildDriftHistoryRows( new Map([[key, prev]]), new Map([[key, next]]), 'Z123' ); expect(rows).toHaveLength(1); expect(rows[0].changeAction).toBe('update'); expect(rows[0].zoneId).toBe('Z123'); expect(rows[0].recordKey).toBe(key); expect(rows[0].beforeValue).not.toBeNull(); expect(rows[0].afterValue).not.toBeNull(); }); it('produces a delete row with non-null before and null after when the mirror row has no AWS match', () => { const key = 'Z123:gone.example.com.:A:'; const prev = makeRecordSet({ recordKey: key, name: 'gone.example.com.' }); const rows = buildDriftHistoryRows(new Map([[key, prev]]), new Map(), 'Z123'); expect(rows).toHaveLength(1); expect(rows[0].changeAction).toBe('delete'); expect(rows[0].beforeValue).not.toBeNull(); expect(rows[0].afterValue).toBeNull(); }); it('produces a create row with null before and non-null after when AWS has no matching mirror row', () => { const key = 'Z123:new.example.com.:A:'; const next = makeRecordSet({ recordKey: key, name: 'new.example.com.' }); const rows = buildDriftHistoryRows(new Map(), new Map([[key, next]]), 'Z123'); expect(rows).toHaveLength(1); expect(rows[0].changeAction).toBe('create'); expect(rows[0].beforeValue).toBeNull(); expect(rows[0].afterValue).not.toBeNull(); }); it('produces zero rows when mirror and AWS recordsets are identical', () => { const key = 'Z123:www.example.com.:A:'; const prev = makeRecordSet(); const next = makeRecordSet(); const rows = buildDriftHistoryRows( new Map([[key, prev]]), new Map([[key, next]]), 'Z123' ); expect(rows).toHaveLength(0); }); it('stores the whole recordset (name, type, setIdentifier, ttl, resourceRecords, aliasTarget) in before/after, not a per-field delta', () => { const key = 'Z123:www.example.com.:A:'; const prev = makeRecordSet({ ttl: 300 }); const next = makeRecordSet({ ttl: 600 }); const rows = buildDriftHistoryRows( new Map([[key, prev]]), new Map([[key, next]]), 'Z123' ); expect(rows[0].afterValue).toEqual({ name: 'www.example.com.', type: 'A', setIdentifier: null, ttl: 600, resourceRecords: [{ value: '10.0.0.1' }], aliasTarget: null, }); }); it('a record changed by Pulse CRUD in the same window is still tagged as drift by the sync (sync cannot distinguish actor)', () => { // The sync has no notion of "who changed this" — it only compares // mirror vs. AWS-live state. A CRUD-originated change looks identical // to an out-of-band AWS console edit from the sync's perspective, so it // still produces a drift row here. The CRUD route's own pulse_crud row // (written elsewhere, not by this function) is the authoritative one; // both rows are expected to coexist in the ledger. const key = 'Z123:www.example.com.:A:'; const prev = makeRecordSet({ ttl: 300 }); const next = makeRecordSet({ ttl: 900 }); // as if Pulse CRUD had just updated this const rows = buildDriftHistoryRows( new Map([[key, prev]]), new Map([[key, next]]), 'Z123' ); expect(rows).toHaveLength(1); expect(rows[0].changeAction).toBe('update'); }); });