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)
})
})