- Fix state machine start timing: don't call start() in startStateMachine, let the CPE's first empty body trigger the state machine via continue(). This ensures GPN is sent at the correct point in the TR-069 session. - Add unit tests for reclassifyDeviceFromCatalog (10 tests) - Add unit tests for MainStateMachine transitions and GPN callbacks (11 tests) - Test both scenarios: Huawei (GPN supported) and Livebox (GPN unsupported + fallback) - Fix tr069_trace_service test path assertion (config/ → data/) - Update steering doc with Livebox bootstrap command
149 lines
4.4 KiB
TypeScript
149 lines
4.4 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
const fsMocks = vi.hoisted(() => ({
|
|
appendFileSync: vi.fn(),
|
|
mkdirSync: vi.fn(),
|
|
readFileSync: vi.fn(),
|
|
renameSync: vi.fn(),
|
|
statSync: vi.fn(),
|
|
}))
|
|
|
|
const fsPromisesMocks = vi.hoisted(() => ({
|
|
writeFile: vi.fn(),
|
|
}))
|
|
|
|
vi.mock('node:fs', () => ({
|
|
appendFileSync: fsMocks.appendFileSync,
|
|
mkdirSync: fsMocks.mkdirSync,
|
|
readFileSync: fsMocks.readFileSync,
|
|
renameSync: fsMocks.renameSync,
|
|
statSync: fsMocks.statSync,
|
|
}))
|
|
|
|
vi.mock('node:fs/promises', () => ({
|
|
writeFile: fsPromisesMocks.writeFile,
|
|
}))
|
|
|
|
import { Tr069TraceService } from '../../../app/services/tr069_trace_service.js'
|
|
|
|
describe('Tr069TraceService', () => {
|
|
const baseEntry = {
|
|
deviceId: 'device-123',
|
|
manufacturer: 'Orange',
|
|
oui: 'A1B2C3',
|
|
productClass: 'Livebox',
|
|
serialNumber: 'SN123456',
|
|
}
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
fsMocks.readFileSync.mockImplementation(() => {
|
|
throw new Error('missing config')
|
|
})
|
|
fsMocks.statSync.mockImplementation(() => {
|
|
throw new Error('missing log')
|
|
})
|
|
;(Tr069TraceService as any).instance = undefined
|
|
})
|
|
|
|
it('returns false for untracked devices', () => {
|
|
const service = Tr069TraceService.getInstance()
|
|
|
|
expect(service.isTraced('unknown-device')).toBe(false)
|
|
})
|
|
|
|
it('returns true after adding a device', async () => {
|
|
const service = Tr069TraceService.getInstance()
|
|
|
|
await service.addDevice(baseEntry)
|
|
|
|
expect(service.isTraced(baseEntry.deviceId)).toBe(true)
|
|
})
|
|
|
|
it('lists all traced devices', async () => {
|
|
const service = Tr069TraceService.getInstance()
|
|
|
|
await service.addDevice(baseEntry)
|
|
await service.addDevice({
|
|
...baseEntry,
|
|
deviceId: 'device-456',
|
|
serialNumber: 'SN654321',
|
|
})
|
|
|
|
expect(service.list()).toHaveLength(2)
|
|
expect(service.list().map((entry) => entry.deviceId)).toEqual(['device-123', 'device-456'])
|
|
})
|
|
|
|
it('adds a device with an auto-generated startedAt value', async () => {
|
|
const service = Tr069TraceService.getInstance()
|
|
|
|
await service.addDevice(baseEntry)
|
|
|
|
const [entry] = service.list()
|
|
expect(entry.startedAt).toBeTypeOf('string')
|
|
expect(Number.isNaN(Date.parse(entry.startedAt))).toBe(false)
|
|
})
|
|
|
|
it('removes a traced device and returns true', async () => {
|
|
const service = Tr069TraceService.getInstance()
|
|
|
|
await service.addDevice(baseEntry)
|
|
|
|
await expect(service.removeDevice(baseEntry.deviceId)).resolves.toBe(true)
|
|
expect(service.isTraced(baseEntry.deviceId)).toBe(false)
|
|
})
|
|
|
|
it('returns false when removing an unknown device', async () => {
|
|
const service = Tr069TraceService.getInstance()
|
|
|
|
await expect(service.removeDevice('unknown-device')).resolves.toBe(false)
|
|
})
|
|
|
|
it('persists config after add and remove operations', async () => {
|
|
const service = Tr069TraceService.getInstance()
|
|
|
|
await service.addDevice(baseEntry)
|
|
await service.removeDevice(baseEntry.deviceId)
|
|
|
|
expect(fsPromisesMocks.writeFile).toHaveBeenCalledTimes(2)
|
|
expect(fsPromisesMocks.writeFile).toHaveBeenNthCalledWith(
|
|
1,
|
|
expect.stringContaining('tr069-trace.json.tmp'),
|
|
expect.stringContaining(baseEntry.deviceId),
|
|
'utf-8'
|
|
)
|
|
expect(fsPromisesMocks.writeFile).toHaveBeenNthCalledWith(
|
|
2,
|
|
expect.stringContaining('tr069-trace.json.tmp'),
|
|
JSON.stringify({ entries: [] }, null, 2),
|
|
'utf-8'
|
|
)
|
|
expect(fsMocks.renameSync).toHaveBeenCalledWith(
|
|
expect.stringContaining('tr069-trace.json.tmp'),
|
|
expect.stringContaining('tr069-trace.json')
|
|
)
|
|
})
|
|
|
|
it('rotates log files when size exceeds the limit', async () => {
|
|
const service = Tr069TraceService.getInstance()
|
|
fsMocks.statSync.mockReturnValue({ size: 10 * 1024 * 1024 })
|
|
|
|
await (service as any).doWrite(baseEntry.deviceId, 'CPE→ACS', '<Inform />')
|
|
|
|
expect(fsMocks.renameSync).toHaveBeenNthCalledWith(
|
|
1,
|
|
expect.stringContaining(`${baseEntry.deviceId}.log.4`),
|
|
expect.stringContaining(`${baseEntry.deviceId}.log.5`)
|
|
)
|
|
expect(fsMocks.renameSync).toHaveBeenLastCalledWith(
|
|
expect.stringContaining(`${baseEntry.deviceId}.log`),
|
|
expect.stringContaining(`${baseEntry.deviceId}.log.1`)
|
|
)
|
|
expect(fsMocks.appendFileSync).toHaveBeenCalledWith(
|
|
expect.stringContaining(`${baseEntry.deviceId}.log`),
|
|
expect.stringContaining('CPE→ACS'),
|
|
'utf-8'
|
|
)
|
|
})
|
|
})
|