/** * lib/services/route53-record-key.ts unit tests. * * Pure functions, no mocking required — no `pg`, no AWS client construction. */ import { describe, it, expect } from 'vitest'; import { buildRecordKey, normalizeRecordSet, recordSetsEqual, classifyDrift, toHistoryPayload, type NormalizedRecordSet, } from './route53-record-key'; import type { ResourceRecordSet } from '@aws-sdk/client-route-53'; describe('buildRecordKey', () => { it('builds a key with an empty setIdentifier segment when none is given', () => { expect( buildRecordKey({ zoneId: 'Z123', name: 'www.example.com.', type: 'A', setIdentifier: null }) ).toBe('Z123:www.example.com.:A:'); }); it('appends a non-null setIdentifier after the final colon', () => { expect( buildRecordKey({ zoneId: 'Z123', name: 'www.example.com.', type: 'A', setIdentifier: 'primary' }) ).toBe('Z123:www.example.com.:A:primary'); }); }); describe('normalizeRecordSet', () => { it('lowercases name, preserves trailing dot, uppercases type, coerces missing TTL to null, sorts resourceRecords', () => { const rs: ResourceRecordSet = { Name: 'WWW.Example.COM.', Type: 'a' as ResourceRecordSet['Type'], ResourceRecords: [{ Value: '10.0.0.2' }, { Value: '10.0.0.1' }], }; const normalized = normalizeRecordSet(rs, 'Z123'); expect(normalized.name).toBe('www.example.com.'); expect(normalized.type).toBe('A'); expect(normalized.ttl).toBeNull(); expect(normalized.resourceRecords).toEqual([{ value: '10.0.0.1' }, { value: '10.0.0.2' }]); }); it('normalizes an alias record with no TTL/ResourceRecords but a populated AliasTarget', () => { const rs: ResourceRecordSet = { Name: 'alias.example.com.', Type: 'A' as ResourceRecordSet['Type'], AliasTarget: { HostedZoneId: 'Z2FDTNDATAQYW2', DNSName: 'd123.cloudfront.net.', EvaluateTargetHealth: false, }, }; const normalized = normalizeRecordSet(rs, 'Z123'); expect(normalized.ttl).toBeNull(); expect(normalized.resourceRecords).toEqual([]); expect(normalized.aliasTarget).toEqual({ HostedZoneId: 'Z2FDTNDATAQYW2', DNSName: 'd123.cloudfront.net.', EvaluateTargetHealth: false, }); }); }); describe('recordSetsEqual', () => { const base: NormalizedRecordSet = { recordKey: 'Z123:www.example.com.:A:', zoneId: 'Z123', name: 'www.example.com.', type: 'A', setIdentifier: null, ttl: 300, resourceRecords: [{ value: '10.0.0.1' }, { value: '10.0.0.2' }], aliasTarget: null, }; it('is true when compared with itself', () => { expect(recordSetsEqual(base, base)).toBe(true); }); it('is false when a single resourceRecords value changes', () => { const changed: NormalizedRecordSet = { ...base, resourceRecords: [{ value: '10.0.0.1' }, { value: '10.0.0.3' }], }; expect(recordSetsEqual(base, changed)).toBe(false); }); it('is true when only the order of resourceRecords changes', () => { const reordered: NormalizedRecordSet = { ...base, resourceRecords: [{ value: '10.0.0.2' }, { value: '10.0.0.1' }], }; // Both are pre-sorted by normalizeRecordSet in real use; simulate that // order doesn't matter by sorting here too, matching normalization. const sorted = [...reordered.resourceRecords].sort((a, b) => a.value.localeCompare(b.value)); expect(recordSetsEqual(base, { ...reordered, resourceRecords: sorted })).toBe(true); }); it('is false when TTL differs', () => { const changed: NormalizedRecordSet = { ...base, ttl: 600 }; expect(recordSetsEqual(base, changed)).toBe(false); }); it('is true when both are null', () => { expect(recordSetsEqual(null, null)).toBe(true); }); it('is false when only one side is null', () => { expect(recordSetsEqual(base, null)).toBe(false); expect(recordSetsEqual(null, base)).toBe(false); }); }); describe('classifyDrift', () => { const prev: NormalizedRecordSet = { 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, }; it('returns create when prev is null', () => { expect(classifyDrift(null, prev)).toBe('create'); }); it('returns delete when next is null', () => { expect(classifyDrift(prev, null)).toBe('delete'); }); it('returns update when normalized sets differ', () => { const next: NormalizedRecordSet = { ...prev, ttl: 600 }; expect(classifyDrift(prev, next)).toBe('update'); }); it('returns null when normalized sets are equal (no history row)', () => { expect(classifyDrift(prev, { ...prev })).toBeNull(); }); }); describe('toHistoryPayload', () => { it('returns null for a null input', () => { expect(toHistoryPayload(null)).toBeNull(); }); it('returns the whole-recordset snapshot shape for a non-null input', () => { const ns: NormalizedRecordSet = { 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, }; expect(toHistoryPayload(ns)).toEqual({ name: 'www.example.com.', type: 'A', setIdentifier: null, ttl: 300, resourceRecords: [{ value: '10.0.0.1' }], aliasTarget: null, }); }); });