- MongoDB storage: cwmp_exchanges collection with indexes (deviceId+timestamp, deviceId+sequenceNumber unique, TTL 24h) - ExchangeStoreService: CWMP method extraction, per-device queue for ordering, retry logic, message cap (5000/device) - Exchange API: GET/DELETE/SSE stream endpoints with Last-Event-ID reconnection - CwmpLoggerMiddleware: dual-write (MongoDB + legacy files) with fire-and-forget - Frontend: SequenceViewer (CPE↔ACS lifelines, arrows, auto-scroll, virtual scroll) + InspectorPanel (formatted XML, copy, syntax highlighting) - Tr069Trace page: integrated new viewer with col-6/col-6 layout - DeviceDetail page: collapsible exchange section when device is traced - 14 property-based tests, 40+ unit tests, 5 integration tests - Health check includes index verification - Config: feature flags, SSE timeouts, retry policy
146 lines
5.1 KiB
TypeScript
146 lines
5.1 KiB
TypeScript
import { describe, expect, it, vi, beforeEach } from 'vitest'
|
|
import { verifyCwmpExchangesIndexes, verifyIndexesAtStartup } from '../../../app/services/exchange_index_checker.js'
|
|
|
|
describe('exchange_index_checker', () => {
|
|
describe('verifyCwmpExchangesIndexes', () => {
|
|
it('should report all indexes present when they all exist', async () => {
|
|
const mockDb = {
|
|
collection: vi.fn().mockReturnValue({
|
|
indexes: vi.fn().mockResolvedValue([
|
|
{ name: '_id_', key: { _id: 1 } },
|
|
{ name: 'idx_deviceId_timestamp', key: { deviceId: 1, timestamp: 1 } },
|
|
{ name: 'idx_deviceId_sequenceNumber', key: { deviceId: 1, sequenceNumber: 1 } },
|
|
{ name: 'idx_createdAt_ttl', key: { createdAt: 1 } },
|
|
]),
|
|
}),
|
|
} as any
|
|
|
|
const result = await verifyCwmpExchangesIndexes(mockDb)
|
|
|
|
expect(result.collection).toBe('cwmp_exchanges')
|
|
expect(result.allPresent).toBe(true)
|
|
expect(result.indexes).toHaveLength(3)
|
|
expect(result.indexes.every((idx) => idx.exists)).toBe(true)
|
|
})
|
|
|
|
it('should report missing indexes when some are absent', async () => {
|
|
const mockDb = {
|
|
collection: vi.fn().mockReturnValue({
|
|
indexes: vi.fn().mockResolvedValue([
|
|
{ name: '_id_', key: { _id: 1 } },
|
|
{ name: 'idx_deviceId_timestamp', key: { deviceId: 1, timestamp: 1 } },
|
|
]),
|
|
}),
|
|
} as any
|
|
|
|
const result = await verifyCwmpExchangesIndexes(mockDb)
|
|
|
|
expect(result.allPresent).toBe(false)
|
|
expect(result.indexes[0]).toEqual({
|
|
name: 'idx_deviceId_timestamp',
|
|
description: 'Compound index (deviceId, timestamp)',
|
|
exists: true,
|
|
})
|
|
expect(result.indexes[1]).toEqual({
|
|
name: 'idx_deviceId_sequenceNumber',
|
|
description: 'Unique index (deviceId, sequenceNumber)',
|
|
exists: false,
|
|
})
|
|
expect(result.indexes[2]).toEqual({
|
|
name: 'idx_createdAt_ttl',
|
|
description: 'TTL index (createdAt, 86400s)',
|
|
exists: false,
|
|
})
|
|
})
|
|
|
|
it('should report all indexes missing when collection does not exist', async () => {
|
|
const mockDb = {
|
|
collection: vi.fn().mockReturnValue({
|
|
indexes: vi.fn().mockRejectedValue(new Error('ns not found')),
|
|
}),
|
|
} as any
|
|
|
|
const result = await verifyCwmpExchangesIndexes(mockDb)
|
|
|
|
expect(result.allPresent).toBe(false)
|
|
expect(result.indexes.every((idx) => !idx.exists)).toBe(true)
|
|
})
|
|
|
|
it('should report all indexes missing when no custom indexes exist', async () => {
|
|
const mockDb = {
|
|
collection: vi.fn().mockReturnValue({
|
|
indexes: vi.fn().mockResolvedValue([{ name: '_id_', key: { _id: 1 } }]),
|
|
}),
|
|
} as any
|
|
|
|
const result = await verifyCwmpExchangesIndexes(mockDb)
|
|
|
|
expect(result.allPresent).toBe(false)
|
|
expect(result.indexes.every((idx) => !idx.exists)).toBe(true)
|
|
})
|
|
})
|
|
|
|
describe('verifyIndexesAtStartup', () => {
|
|
let mockLog: { warn: ReturnType<typeof vi.fn>; info: ReturnType<typeof vi.fn> }
|
|
|
|
beforeEach(() => {
|
|
mockLog = {
|
|
warn: vi.fn(),
|
|
info: vi.fn(),
|
|
}
|
|
})
|
|
|
|
it('should log info when all indexes are present', async () => {
|
|
const mockDb = {
|
|
collection: vi.fn().mockReturnValue({
|
|
indexes: vi.fn().mockResolvedValue([
|
|
{ name: '_id_', key: { _id: 1 } },
|
|
{ name: 'idx_deviceId_timestamp', key: { deviceId: 1, timestamp: 1 } },
|
|
{ name: 'idx_deviceId_sequenceNumber', key: { deviceId: 1, sequenceNumber: 1 } },
|
|
{ name: 'idx_createdAt_ttl', key: { createdAt: 1 } },
|
|
]),
|
|
}),
|
|
} as any
|
|
|
|
await verifyIndexesAtStartup(mockDb, mockLog)
|
|
|
|
expect(mockLog.info).toHaveBeenCalledWith('[ExchangeIndexCheck] All cwmp_exchanges indexes verified OK')
|
|
expect(mockLog.warn).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('should log warnings for each missing index', async () => {
|
|
const mockDb = {
|
|
collection: vi.fn().mockReturnValue({
|
|
indexes: vi.fn().mockResolvedValue([
|
|
{ name: '_id_', key: { _id: 1 } },
|
|
{ name: 'idx_deviceId_timestamp', key: { deviceId: 1, timestamp: 1 } },
|
|
]),
|
|
}),
|
|
} as any
|
|
|
|
await verifyIndexesAtStartup(mockDb, mockLog)
|
|
|
|
expect(mockLog.info).not.toHaveBeenCalled()
|
|
expect(mockLog.warn).toHaveBeenCalledTimes(2)
|
|
expect(mockLog.warn).toHaveBeenCalledWith(
|
|
'[ExchangeIndexCheck] Missing index on cwmp_exchanges: idx_deviceId_sequenceNumber (Unique index (deviceId, sequenceNumber))'
|
|
)
|
|
expect(mockLog.warn).toHaveBeenCalledWith(
|
|
'[ExchangeIndexCheck] Missing index on cwmp_exchanges: idx_createdAt_ttl (TTL index (createdAt, 86400s))'
|
|
)
|
|
})
|
|
|
|
it('should log warnings for all indexes when collection does not exist', async () => {
|
|
const mockDb = {
|
|
collection: vi.fn().mockReturnValue({
|
|
indexes: vi.fn().mockRejectedValue(new Error('ns not found')),
|
|
}),
|
|
} as any
|
|
|
|
await verifyIndexesAtStartup(mockDb, mockLog)
|
|
|
|
expect(mockLog.warn).toHaveBeenCalledTimes(3)
|
|
})
|
|
})
|
|
})
|