diff --git a/lib/services/route53-dns-delegation.test.ts b/lib/services/route53-dns-delegation.test.ts new file mode 100644 index 0000000..74d8f26 --- /dev/null +++ b/lib/services/route53-dns-delegation.test.ts @@ -0,0 +1,87 @@ +import { describe, it, expect } from 'vitest'; +import { normalizeNsList, compareNsDelegation } from './route53-dns-delegation'; + +describe('normalizeNsList', () => { + it('lowercases and strips trailing dots', () => { + expect(normalizeNsList(['NS-123.AWSDNS-45.com.', 'ns-999.awsdns-01.org'])).toEqual([ + 'ns-123.awsdns-45.com', + 'ns-999.awsdns-01.org', + ]); + }); + + it('returns [] for null', () => { + expect(normalizeNsList(null)).toEqual([]); + }); + + it('returns [] for undefined', () => { + expect(normalizeNsList(undefined)).toEqual([]); + }); + + it('returns [] for a non-array input', () => { + expect(normalizeNsList('not-an-array')).toEqual([]); + expect(normalizeNsList(42)).toEqual([]); + expect(normalizeNsList({})).toEqual([]); + }); + + it('de-duplicates and sorts so ordering differences never register as a mismatch', () => { + expect(normalizeNsList(['ns-2.awsdns.com.', 'ns-1.awsdns.com', 'ns-1.awsdns.com.'])).toEqual([ + 'ns-1.awsdns.com', + 'ns-2.awsdns.com', + ]); + }); + + it('drops empty strings after trimming', () => { + expect(normalizeNsList([' ', 'ns-1.awsdns.com', ''])).toEqual(['ns-1.awsdns.com']); + }); +}); + +describe('compareNsDelegation', () => { + it('returns mismatch: false with empty diffs when sets are identical', () => { + const result = compareNsDelegation( + ['ns-1.awsdns.com', 'ns-2.awsdns.com'], + ['NS-1.AWSDNS.com.', 'ns-2.awsdns.com.'], + ); + expect(result.mismatch).toBe(false); + expect(result.missingFromLive).toEqual([]); + expect(result.extraInLive).toEqual([]); + }); + + it('returns mismatch: true with populated missingFromLive when an authoritative NS is absent from the live answer', () => { + const result = compareNsDelegation( + ['ns-1.awsdns.com', 'ns-2.awsdns.com'], + ['ns-1.awsdns.com'], + ); + expect(result.mismatch).toBe(true); + expect(result.missingFromLive).toEqual(['ns-2.awsdns.com']); + expect(result.extraInLive).toEqual([]); + }); + + it('returns mismatch: true with populated extraInLive when the live answer contains an NS Route 53 does not consider authoritative', () => { + const result = compareNsDelegation( + ['ns-1.awsdns.com'], + ['ns-1.awsdns.com', 'ns-rogue.example.com'], + ); + expect(result.mismatch).toBe(true); + expect(result.extraInLive).toEqual(['ns-rogue.example.com']); + expect(result.missingFromLive).toEqual([]); + }); + + it('returns mismatch: true when live has no answer but authoritative is non-empty (delegation problem, not a pass)', () => { + const result = compareNsDelegation(['ns1.example.com'], []); + expect(result.mismatch).toBe(true); + }); + + it('returns mismatch: false when authoritative is empty (unjudgeable, must not false-alarm)', () => { + const result = compareNsDelegation([], ['ns1.example.com']); + expect(result.mismatch).toBe(false); + }); + + it('is case-insensitive and trailing-dot-insensitive on both sides', () => { + const result = compareNsDelegation(['NS-1.AWSDNS.COM.'], ['ns-1.awsdns.com']); + expect(result.mismatch).toBe(false); + }); + + it('handles null/undefined inputs on both sides without throwing', () => { + expect(compareNsDelegation(null, undefined)).toMatchObject({ mismatch: false }); + }); +});