import { DyNTS_App, DyNTS_TtlIndexAction, DyNTS_TtlIndexCollection, DyNTS_TtlIndexInfo } from './app.server'; /** * FR-258 / SR-2 — unit tests for the retention TTL-index reconciliation logic * (`DyNTS_App.ensureRetentionTtlIndex`). Pure with respect to a mockable native-driver collection; * no live MongoDB required. Covers create / no-op / differs(boot-safe, no rebuild) and edge cases. */ describe('| DyNTS_App.ensureRetentionTtlIndex (FR-258 / SR-2)', () => { interface MockColl { coll: DyNTS_TtlIndexCollection; created: { keys: Record; options: { expireAfterSeconds: number } }[]; dropped: string[]; } function makeMockCollection(existing: DyNTS_TtlIndexInfo[], opts?: { noIndexesFn?: boolean }): MockColl { const created: { keys: Record; options: { expireAfterSeconds: number } }[] = []; const dropped: string[] = []; const coll: DyNTS_TtlIndexCollection = { createIndex: async (keys: Record, options: { expireAfterSeconds: number }): Promise => { created.push({ keys: keys, options: options }); return '__created_1'; }, dropIndex: async (name: string): Promise => { dropped.push(name); }, }; if (!opts?.noIndexesFn) { coll.indexes = async (): Promise => existing; } return { coll: coll, created: created, dropped: dropped }; } const TTL_14D: number = 14 * 86400; it('| no existing __created index → creates a TTL index (action "created")', async () => { const m: MockColl = makeMockCollection([]); const action: DyNTS_TtlIndexAction = await DyNTS_App.ensureRetentionTtlIndex(m.coll, TTL_14D); expect(action).toBe('created'); expect(m.created.length).toBe(1); expect(m.created[0].keys).toEqual({ __created: 1 }); expect(m.created[0].options).toEqual({ expireAfterSeconds: TTL_14D }); expect(m.dropped.length).toBe(0); }); it('| existing index with the SAME ttl → no-op (action "noop", no create, no drop)', async () => { const m: MockColl = makeMockCollection([ { name: '__created_1', key: { __created: 1 }, expireAfterSeconds: TTL_14D }, ]); const action: DyNTS_TtlIndexAction = await DyNTS_App.ensureRetentionTtlIndex(m.coll, TTL_14D); expect(action).toBe('noop'); expect(m.created.length).toBe(0); expect(m.dropped.length).toBe(0); }); it('| existing index with a DIFFERENT ttl → BOOT-SAFE: left untouched (action "differs", NO drop/recreate)', async () => { const m: MockColl = makeMockCollection([ { name: '__created_1', key: { __created: 1 }, expireAfterSeconds: 999 }, ]); const action: DyNTS_TtlIndexAction = await DyNTS_App.ensureRetentionTtlIndex(m.coll, TTL_14D); expect(action).toBe('differs'); // Critical: never rebuild a live index at boot (heavy op on large collections → health-probe starvation). expect(m.dropped.length).toBe(0); expect(m.created.length).toBe(0); }); it('| existing NON-TTL __created index (no expireAfterSeconds) → BOOT-SAFE: left untouched (action "differs")', async () => { const m: MockColl = makeMockCollection([ { name: '__created_1', key: { __created: 1 } }, ]); const action: DyNTS_TtlIndexAction = await DyNTS_App.ensureRetentionTtlIndex(m.coll, 604800); expect(action).toBe('differs'); expect(m.dropped.length).toBe(0); expect(m.created.length).toBe(0); }); it('| ignores indexes on OTHER fields — only reconciles { __created: 1 }', async () => { const m: MockColl = makeMockCollection([ { name: 'project_1', key: { project: 1 }, expireAfterSeconds: 50 }, { name: '_id_', key: { _id: 1 } }, ]); const action: DyNTS_TtlIndexAction = await DyNTS_App.ensureRetentionTtlIndex(m.coll, 86400); expect(action).toBe('created'); expect(m.created.length).toBe(1); expect(m.dropped.length).toBe(0); }); it('| no indexes() method available → treats as none and creates', async () => { const m: MockColl = makeMockCollection([], { noIndexesFn: true }); const action: DyNTS_TtlIndexAction = await DyNTS_App.ensureRetentionTtlIndex(m.coll, 86400); expect(action).toBe('created'); expect(m.created.length).toBe(1); }); it('| indexes() rejecting → treated as empty (resilient), still creates', async () => { const created: { keys: Record; options: { expireAfterSeconds: number } }[] = []; const coll: DyNTS_TtlIndexCollection = { indexes: async (): Promise => { throw new Error('boom'); }, createIndex: async (keys: Record, options: { expireAfterSeconds: number }): Promise => { created.push({ keys: keys, options: options }); return 'x'; }, }; const action: DyNTS_TtlIndexAction = await DyNTS_App.ensureRetentionTtlIndex(coll, 86400); expect(action).toBe('created'); expect(created.length).toBe(1); }); });