- 6 unit tests exercising the pure drift-classification wiring in Route53SyncService - Confirms whole-recordset before/after payloads, not per-field deltas - Confirms CRUD-originated changes still get tagged sync_detected_drift (sync cannot distinguish actor) - Full suite green aside from 2 pre-existing itglue-search.test.ts failures logged in deferred-items.md (unrelated to this plan)
126 lines
4.3 KiB
TypeScript
126 lines
4.3 KiB
TypeScript
/**
|
|
* 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> = {}): 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');
|
|
});
|
|
});
|