import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest' import { MongoClient, Db } from 'mongodb' const MONGO_URI = 'mongodb://adam:adam@localhost:27017/adam-acs-ttl-test?authSource=admin' const COLLECTION_NAME = 'cwmp_exchanges' let client: MongoClient let db: Db let skip = false describe('TTL expiration integration test', () => { beforeAll(async () => { try { client = new MongoClient(MONGO_URI, { serverSelectionTimeoutMS: 2000 }) await client.connect() db = client.db('adam-acs-ttl-test') // Ensure the collection exists before creating the index const collections = await db.listCollections({ name: COLLECTION_NAME }).toArray() if (collections.length === 0) { await db.createCollection(COLLECTION_NAME) } // Create the TTL index (as the migration would) const collection = db.collection(COLLECTION_NAME) await collection.createIndex( { createdAt: 1 }, { expireAfterSeconds: 86400, name: 'idx_createdAt_ttl', background: true } ) } catch { skip = true } }) afterAll(async () => { if (skip) return await db.dropDatabase() await client.close() }) beforeEach(async () => { if (skip) return await db.collection(COLLECTION_NAME).deleteMany({}) }) /** * Validates: Requirements 7.3 * * Verify the TTL index is configured with expireAfterSeconds = 86400 (24 hours). * In a real MongoDB deployment, the TTL monitor thread runs every 60s and removes * documents whose createdAt field is older than 86400 seconds. For this test, we * verify the index configuration is correct rather than waiting for the TTL monitor. */ it('should have a TTL index on createdAt with expireAfterSeconds = 86400', async () => { if (skip) return const collection = db.collection(COLLECTION_NAME) const indexes = await collection.indexes() const ttlIndex = indexes.find((idx) => idx.name === 'idx_createdAt_ttl') expect(ttlIndex).toBeDefined() expect(ttlIndex!.key).toEqual({ createdAt: 1 }) expect(ttlIndex!.expireAfterSeconds).toBe(86400) }) it('should store exchanges with createdAt set to 25 hours ago as expired candidates', async () => { if (skip) return const collection = db.collection(COLLECTION_NAME) const twentyFiveHoursAgo = new Date(Date.now() - 25 * 60 * 60 * 1000) // Insert exchanges with createdAt set 25 hours in the past const expiredExchanges = Array.from({ length: 5 }, (_, i) => ({ deviceId: 'device-ttl-test', direction: 'CPE→ACS' as const, timestamp: new Date(twentyFiveHoursAgo.getTime() + i * 1000).toISOString(), sessionId: '10.0.0.1:54321->192.168.1.1:8080', cwmpMethod: 'Inform', rawXml: ``, sequenceNumber: i + 1, createdAt: twentyFiveHoursAgo, })) await collection.insertMany(expiredExchanges) // Verify the documents were inserted const count = await collection.countDocuments({ deviceId: 'device-ttl-test' }) expect(count).toBe(5) // Verify all documents have a createdAt older than 24 hours const docs = await collection.find({ deviceId: 'device-ttl-test' }).toArray() const twentyFourHoursAgo = new Date(Date.now() - 24 * 60 * 60 * 1000) for (const doc of docs) { expect(doc.createdAt).toBeInstanceOf(Date) expect(doc.createdAt.getTime()).toBeLessThan(twentyFourHoursAgo.getTime()) } }) it('should NOT mark recent exchanges as expired candidates', async () => { if (skip) return const collection = db.collection(COLLECTION_NAME) const now = new Date() // Insert exchanges with current createdAt (should NOT be expired) const recentExchanges = Array.from({ length: 3 }, (_, i) => ({ deviceId: 'device-ttl-recent', direction: 'ACS→CPE' as const, timestamp: new Date(now.getTime() + i * 1000).toISOString(), sessionId: '10.0.0.1:54321->192.168.1.1:8080', cwmpMethod: 'GetParameterValuesResponse', rawXml: ``, sequenceNumber: i + 1, createdAt: now, })) await collection.insertMany(recentExchanges) // Verify documents exist and their createdAt is within 24 hours const docs = await collection.find({ deviceId: 'device-ttl-recent' }).toArray() const twentyFourHoursAgo = new Date(Date.now() - 24 * 60 * 60 * 1000) expect(docs).toHaveLength(3) for (const doc of docs) { expect(doc.createdAt).toBeInstanceOf(Date) expect(doc.createdAt.getTime()).toBeGreaterThan(twentyFourHoursAgo.getTime()) } }) it('TTL index expireAfterSeconds should equal exactly 24 hours in seconds', async () => { if (skip) return const collection = db.collection(COLLECTION_NAME) const indexes = await collection.indexes() const ttlIndex = indexes.find((idx) => idx.name === 'idx_createdAt_ttl') expect(ttlIndex).toBeDefined() // 24 hours = 24 * 60 * 60 = 86400 seconds const twentyFourHoursInSeconds = 24 * 60 * 60 expect(ttlIndex!.expireAfterSeconds).toBe(twentyFourHoursInSeconds) }) })