From bc9ac4f125e02b17dbd42e85829b1702564d0d38 Mon Sep 17 00:00:00 2001 From: Kevin L'Hereec Date: Mon, 1 Jun 2026 17:31:33 +0200 Subject: [PATCH] fix: state machine timing + reclassification tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- .kiro/steering/debug-reclassification.md | 70 +++- app/services/cwmp/state_machine_manager.ts | 1 - .../unit/services/main_state_machine.spec.ts | 317 ++++++++++++++++++ tests/unit/services/reclassify_device.spec.ts | 146 ++++++++ .../unit/services/tr069_trace_service.spec.ts | 8 +- 5 files changed, 532 insertions(+), 10 deletions(-) create mode 100644 tests/unit/services/main_state_machine.spec.ts create mode 100644 tests/unit/services/reclassify_device.spec.ts diff --git a/.kiro/steering/debug-reclassification.md b/.kiro/steering/debug-reclassification.md index 5ce378d..89aa7d2 100644 --- a/.kiro/steering/debug-reclassification.md +++ b/.kiro/steering/debug-reclassification.md @@ -78,7 +78,18 @@ curl -s -X PUT "http://localhost:9898/api/v1/device-simulator/create-devices?noO }' ``` -### 6. Attendre l'Inform (30s max) puis vérifier +### 6. Déclencher le bootstrap Inform + +Le simulateur ne fait PAS d'Inform automatiquement après la création. Il faut le déclencher manuellement : + +```bash +sleep 3 +curl -s -X POST "http://localhost:9898/api/v1/device-simulator/device-request" \ + -H 'Content-Type: application/json' \ + -d '{"actions":[{"macaddress":"F8:B7:FF:00:11:22","event":{"0":"BOOTSTRAP"}}]}' +``` + +### 7. Attendre l'Inform (30s max) puis vérifier ```bash sleep 30 @@ -99,7 +110,7 @@ print('Dans parameters (BON):', params) " ``` -### 7. Regarder les logs de reclassification +### 8. Regarder les logs de reclassification ```bash docker compose logs app --tail 50 2>&1 | grep -i "reclassify\|Reclassify" @@ -107,10 +118,19 @@ docker compose logs app --tail 50 2>&1 | grep -i "reclassify\|Reclassify" ## Résultat attendu +**Sagemcom Livebox 4** (GPN unsupported, reclassification via catalogue) : ``` gpnStatus: unsupported -Dans properties (MAUVAIS): [] -Dans parameters (BON): ['Device.ManagementServer.PeriodicInformInterval', 'Device.DeviceInfo.ProvisioningCode'] +properties: 7 +parameters: 2 (PeriodicInformInterval + ProvisioningCode) +``` + +**Huawei BM632w** (GPN supported, classification via GPN response) : +``` +gpnStatus: supported +properties: 16 +parameters: 15 +compliance: compliant ``` ## Identifiants du device de test @@ -125,7 +145,47 @@ Dans parameters (BON): ['Device.ManagementServer.PeriodicInformInterval', 'Devic | MAC (simulateur) | F8:B7:FF:00:11:22 | | Data Model | tr-181 | -## API du simulateur +## Device Huawei (genieacs-sim) + +| Champ | Valeur | +|-------|--------| +| ACS identifier | `f9747826c5545b0e49db7967` | +| Manufacturer | Huawei Technologies Co., Ltd. | +| OUI | 202BC1 | +| Product Class | BM632w | +| Serial Number | 000000 | +| Data Model | tr-098 (InternetGatewayDevice.*) | +| Container | `adam-acs-genieacs-sim-1` | + +### Procédure de test Huawei + +Le Huawei est simulé par le container `genieacs-sim` (défini dans `docker-compose.yml` avec le profile `test`). Il se crée automatiquement au démarrage du container. + +**Supprimer le device :** +```bash +docker stop adam-acs-genieacs-sim-1 && docker rm adam-acs-genieacs-sim-1 +curl -s -X DELETE http://localhost:8080/api/v1/devices/f9747826c5545b0e49db7967 +``` + +**Recréer le device (relancer le container) :** +```bash +docker compose --profile test up -d genieacs-sim +``` + +Le container fait un Inform automatiquement au démarrage vers `http://app:8080/cwmp10`. + +**Vérifier :** +```bash +sleep 15 +curl -s "http://localhost:8080/api/v1/devices/f9747826c5545b0e49db7967" | python3 -c " +import sys,json +d=json.load(sys.stdin) +print('gpnStatus:', d.get('gpnStatus')) +print('dataModel:', d.get('dataModel')) +print('Properties:', len(d.get('properties',[]))) +print('Parameters:', len(d.get('parameters',[]))) +" +``` - Base URL : `http://localhost:9898/api/v1/device-simulator` - Swagger : `http://localhost:9898/swagger/` diff --git a/app/services/cwmp/state_machine_manager.ts b/app/services/cwmp/state_machine_manager.ts index 20a65c8..1e594b1 100644 --- a/app/services/cwmp/state_machine_manager.ts +++ b/app/services/cwmp/state_machine_manager.ts @@ -184,7 +184,6 @@ export class StateMachineManager extends EventEmitter { // Start the machine try { machineInstance.status = MachineStatus.RUNNING - await stateMachine.start() machineInstance.currentState = stateMachine.getCurrentStateName() if (stateMachine.isCompleted()) { diff --git a/tests/unit/services/main_state_machine.spec.ts b/tests/unit/services/main_state_machine.spec.ts new file mode 100644 index 0000000..ae9ec7c --- /dev/null +++ b/tests/unit/services/main_state_machine.spec.ts @@ -0,0 +1,317 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest' + +// Mock AdonisJS logger before imports +vi.mock('@adonisjs/core/services/logger', () => ({ + default: { + info: vi.fn(), + debug: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + }, +})) + +// Mock DeviceModel to avoid MongoDB dependency +vi.mock('../../../app/models/device.js', () => ({ + default: { + createOrUpdate: vi.fn().mockResolvedValue({}), + findByDeviceId: vi.fn().mockResolvedValue(null), + }, +})) + +// Mock CommandModel +vi.mock('../../../app/models/command.js', () => ({ + default: { + getCollection: vi.fn().mockReturnValue({ + find: vi.fn().mockReturnValue({ sort: vi.fn().mockReturnValue({ toArray: vi.fn().mockResolvedValue([]) }) }), + countDocuments: vi.fn().mockResolvedValue(0), + }), + }, +})) + +import { MainStateMachine } from '../../../app/services/cwmp/main_state_machine.js' +import { Device } from '../../../app/domain/device/device.js' +import { DeviceId } from '../../../app/domain/device/device_id.js' +import { DeviceProperty, DeviceParameter } from '../../../app/domain/device/device_parameter.js' +import { CWMPTaskQueue } from '../../../app/services/cwmp/task_queue.js' +import { ParameterType } from '../../../app/domain/shared/types.js' + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +function makeTr181Device(): Device { + const device = new Device(new DeviceId('Sagemcom', 'F8B7FF', 'Livebox 4', 'LB4-TEST-001')) + device.setDataModel('tr-181') + return device +} + +function makeTr098Device(): Device { + const device = new Device(new DeviceId('Huawei Technologies Co., Ltd.', '202BC1', 'BM632w', '000000')) + device.setDataModel('tr-098') + return device +} + +function makeUnknownDevice(): Device { + const device = new Device(new DeviceId('Unknown', '000000', 'Generic', 'SN001')) + device.setDataModel('unknown') + return device +} + +// --------------------------------------------------------------------------- +// MainStateMachine — Initial state +// --------------------------------------------------------------------------- + +describe('MainStateMachine — initial state', () => { + it('starts in idle state', () => { + const device = makeTr181Device() + const taskQueue = new CWMPTaskQueue() + const sm = new MainStateMachine(device, 'session-1', taskQueue) + + expect(sm.getCurrentStateName()).toBe('idle') + expect(sm.isCompleted()).toBe(false) + }) +}) + +// --------------------------------------------------------------------------- +// MainStateMachine — TR-181 device flow (GPN on Device.ManagementServer.) +// --------------------------------------------------------------------------- + +describe('MainStateMachine — TR-181 device', () => { + let device: Device + let taskQueue: CWMPTaskQueue + let sm: MainStateMachine + + beforeEach(() => { + device = makeTr181Device() + taskQueue = new CWMPTaskQueue() + sm = new MainStateMachine(device, 'session-1', taskQueue) + }) + + it('transitions to discovering-parameters and sends GPN for Device.ManagementServer.', async () => { + const response = await sm.start() + + expect(sm.getCurrentStateName()).toBe('discovering-parameters') + // Response should be a SOAP envelope with GetParameterNames + expect(response).toContain('GetParameterNames') + expect(response).toContain('Device.ManagementServer.') + }) + + it('advances past discovering-parameters after receiving a response', async () => { + await sm.start() // sends GPN, state = discovering-parameters + + // Simulate any response (success or failure) — state machine advances + await sm.handleResponse(true, {}) + + // Should have moved past discovering-parameters (no more paths in queue) + expect(sm.getCurrentStateName()).not.toBe('discovering-parameters') + expect(sm.getCurrentStateName()).not.toBe('idle') + }) + + it('GPN callback sets gpnStatus to unsupported on fault code 9000', async () => { + await sm.start() + + // Get the task from the queue and execute its callback manually + const tasks = Array.from((taskQueue as any).tasks.values()) + const gpnTask = tasks.find((t: any) => t.type === 'get_parameter_names') + expect(gpnTask).toBeDefined() + + // Execute the callback with error + await taskQueue.executeTaskCallback(gpnTask!.id, 'error' as any, { + FaultCode: 9000, + FaultString: 'Method not supported', + }) + + expect(device.gpnStatus).toBe('unsupported') + }) + + it('GPN callback sets gpnStatus to supported on success', async () => { + await sm.start() + + const tasks = Array.from((taskQueue as any).tasks.values()) + const gpnTask = tasks.find((t: any) => t.type === 'get_parameter_names') + expect(gpnTask).toBeDefined() + + // Execute the callback with success + await taskQueue.executeTaskCallback(gpnTask!.id, 'success' as any, { + ParameterList: { + ParameterInfoStruct: [ + { Name: 'Device.ManagementServer.URL', Writable: true }, + { Name: 'Device.ManagementServer.ParameterKey', Writable: false }, + ], + }, + }) + + expect(device.gpnStatus).toBe('supported') + }) + + it('GPN callback organizes parameters correctly on success', async () => { + device.setProperty(new DeviceProperty('Device.ManagementServer.URL', 'http://acs', ParameterType.STRING)) + device.setProperty(new DeviceProperty('Device.ManagementServer.ParameterKey', '', ParameterType.STRING)) + + await sm.start() + + const tasks = Array.from((taskQueue as any).tasks.values()) + const gpnTask = tasks.find((t: any) => t.type === 'get_parameter_names') + + await taskQueue.executeTaskCallback(gpnTask!.id, 'success' as any, { + ParameterList: { + ParameterInfoStruct: [ + { Name: 'Device.ManagementServer.URL', Writable: true }, + { Name: 'Device.ManagementServer.ParameterKey', Writable: false }, + ], + }, + }) + + // URL was property + writable in GPN → should be parameter now + expect(device.getParameter('Device.ManagementServer.URL')).not.toBeNull() + // ParameterKey was property + NOT writable → stays property + expect(device.getProperty('Device.ManagementServer.ParameterKey')).not.toBeNull() + }) +}) + +// --------------------------------------------------------------------------- +// MainStateMachine — TR-098 device flow (GPN on InternetGatewayDevice.ManagementServer.) +// --------------------------------------------------------------------------- + +describe('MainStateMachine — TR-098 device', () => { + let device: Device + let taskQueue: CWMPTaskQueue + let sm: MainStateMachine + + beforeEach(() => { + device = makeTr098Device() + taskQueue = new CWMPTaskQueue() + sm = new MainStateMachine(device, 'session-1', taskQueue) + }) + + it('sends GPN for InternetGatewayDevice.ManagementServer.', async () => { + const response = await sm.start() + + expect(sm.getCurrentStateName()).toBe('discovering-parameters') + expect(response).toContain('GetParameterNames') + expect(response).toContain('InternetGatewayDevice.ManagementServer.') + }) +}) + +// --------------------------------------------------------------------------- +// MainStateMachine — Unknown data model +// --------------------------------------------------------------------------- + +describe('MainStateMachine — unknown data model', () => { + it('skips GPN and goes directly to configuration-evaluation', async () => { + const device = makeUnknownDevice() + const taskQueue = new CWMPTaskQueue() + const sm = new MainStateMachine(device, 'session-1', taskQueue) + + await sm.start() + + // Should skip discovering-parameters entirely + expect(sm.getCurrentStateName()).not.toBe('discovering-parameters') + expect(sm.getCurrentStateName()).not.toBe('idle') + }) +}) + +// --------------------------------------------------------------------------- +// MainStateMachine — CompletedState reclassification +// --------------------------------------------------------------------------- + +describe('MainStateMachine — CompletedState reclassification', () => { + it('reclassifies writable properties on completion when GPN unsupported', async () => { + const device = makeTr181Device() + device.setGpnStatus('unsupported') + // Add writable property that should be reclassified + device.setProperty(new DeviceProperty('Device.ManagementServer.PeriodicInformInterval', '60', ParameterType.UNSIGNED_INT)) + // Add read-only property that should stay + device.setProperty(new DeviceProperty('Device.DeviceInfo.Manufacturer', 'Sagemcom', ParameterType.STRING)) + + const taskQueue = new CWMPTaskQueue() + const sm = new MainStateMachine(device, 'session-1', taskQueue) + + // Start → since dataModel is tr-181, it will try GPN + await sm.start() + + // Simulate GPN failure + await sm.handleResponse(true, {}) + + // The state machine should eventually complete + // Let it run through (no config match → completes) + let guard = 0 + while (!sm.isCompleted() && guard++ < 10) { + await sm.handleResponse(true, {}) + } + + // After completion, PeriodicInformInterval should be a parameter + expect(device.getParameter('Device.ManagementServer.PeriodicInformInterval')).not.toBeNull() + expect(device.getProperty('Device.ManagementServer.PeriodicInformInterval')).toBeNull() + // Manufacturer should stay as property + expect(device.getProperty('Device.DeviceInfo.Manufacturer')).not.toBeNull() + expect(device.getParameter('Device.DeviceInfo.Manufacturer')).toBeNull() + }) +}) + +// --------------------------------------------------------------------------- +// DeviceActionExecutor — reclassification after SPV (Livebox fallback scenario) +// --------------------------------------------------------------------------- + +describe('DeviceActionExecutor — reclassification after updateFromValues', () => { + it('reclassifies properties added via SPV callback when GPN unsupported', async () => { + const device = makeTr181Device() + device.setGpnStatus('unsupported') + // Device starts with only read-only properties (like after first Inform) + device.setProperty(new DeviceProperty('Device.DeviceInfo.Manufacturer', 'Sagemcom', ParameterType.STRING)) + + const taskQueue = new CWMPTaskQueue() + + // Import DeviceActionExecutor + const { DeviceActionExecutor } = await import('../../../app/services/cwmp/device_action_executor.js') + const executor = new DeviceActionExecutor(taskQueue) + + // Simulate SPV for ProvisioningCode (like ConfigurationEvaluationState does) + const spvXml = await executor.applySetParameterValues( + device, + [{ parameterName: 'Device.DeviceInfo.ProvisioningCode', value: 'test-code' }], + async () => '' + ) + + expect(spvXml).toContain('SetParameterValues') + + // Get the task and execute callback with success (simulating CPE response) + const tasks = Array.from((taskQueue as any).tasks.values()) + const spvTask = tasks.find((t: any) => t.type === 'set_parameter_values') + expect(spvTask).toBeDefined() + + await taskQueue.executeTaskCallback(spvTask!.id, 'success' as any, { Status: 0 }) + + // ProvisioningCode should now be a parameter (reclassified after updateFromValues) + expect(device.getParameter('Device.DeviceInfo.ProvisioningCode')).not.toBeNull() + expect(device.getParameter('Device.DeviceInfo.ProvisioningCode')!.value).toBe('test-code') + expect(device.getProperty('Device.DeviceInfo.ProvisioningCode')).toBeNull() + }) + + it('does NOT reclassify when GPN is supported (live data is authoritative)', async () => { + const device = makeTr181Device() + device.setGpnStatus('supported') + device.setProperty(new DeviceProperty('Device.DeviceInfo.Manufacturer', 'Sagemcom', ParameterType.STRING)) + + const taskQueue = new CWMPTaskQueue() + const { DeviceActionExecutor } = await import('../../../app/services/cwmp/device_action_executor.js') + const executor = new DeviceActionExecutor(taskQueue) + + // SPV for a writable param — but GPN is supported so no reclassification + await executor.applySetParameterValues( + device, + [{ parameterName: 'Device.ManagementServer.PeriodicInformInterval', value: '120' }], + async () => '' + ) + + const tasks = Array.from((taskQueue as any).tasks.values()) + const spvTask = tasks.find((t: any) => t.type === 'set_parameter_values') + await taskQueue.executeTaskCallback(spvTask!.id, 'success' as any, { Status: 0 }) + + // PeriodicInformInterval was added via updateFromValues as a property + // But since GPN is supported, reclassification is NOT called + // It stays as property (the GPN response is the authority) + expect(device.getProperty('Device.ManagementServer.PeriodicInformInterval')).not.toBeNull() + expect(device.getParameter('Device.ManagementServer.PeriodicInformInterval')).toBeNull() + }) +}) diff --git a/tests/unit/services/reclassify_device.spec.ts b/tests/unit/services/reclassify_device.spec.ts new file mode 100644 index 0000000..d77f8c3 --- /dev/null +++ b/tests/unit/services/reclassify_device.spec.ts @@ -0,0 +1,146 @@ +import { describe, it, expect } from 'vitest' +import { reclassifyDeviceFromCatalog } from '../../../app/services/parameter_writability.js' +import { Device } from '../../../app/domain/device/device.js' +import { DeviceId } from '../../../app/domain/device/device_id.js' +import { DeviceParameter, DeviceProperty } from '../../../app/domain/device/device_parameter.js' +import { ParameterType } from '../../../app/domain/shared/types.js' + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +function makeDevice(gpnStatus: 'supported' | 'unsupported' | 'unknown' = 'unsupported'): Device { + const device = new Device(new DeviceId('Sagemcom', 'F8B7FF', 'Livebox 4', 'LB4-TEST-001')) + device.setGpnStatus(gpnStatus) + device.setDataModel('tr-181') + return device +} + +function addProperty(device: Device, name: string, value: any = null): void { + device.setProperty(new DeviceProperty(name, value, ParameterType.STRING)) +} + +// --------------------------------------------------------------------------- +// reclassifyDeviceFromCatalog +// --------------------------------------------------------------------------- + +describe('reclassifyDeviceFromCatalog', () => { + it('moves writable properties to parameters (PeriodicInformInterval)', () => { + const device = makeDevice() + addProperty(device, 'Device.ManagementServer.PeriodicInformInterval', '30') + + const count = reclassifyDeviceFromCatalog(device) + + expect(count).toBe(1) + expect(device.getParameter('Device.ManagementServer.PeriodicInformInterval')).not.toBeNull() + expect(device.getProperty('Device.ManagementServer.PeriodicInformInterval')).toBeNull() + }) + + it('moves writable properties to parameters (ProvisioningCode)', () => { + const device = makeDevice() + addProperty(device, 'Device.DeviceInfo.ProvisioningCode', 'test-code') + + const count = reclassifyDeviceFromCatalog(device) + + expect(count).toBe(1) + expect(device.getParameter('Device.DeviceInfo.ProvisioningCode')).not.toBeNull() + expect(device.getParameter('Device.DeviceInfo.ProvisioningCode')!.value).toBe('test-code') + expect(device.getProperty('Device.DeviceInfo.ProvisioningCode')).toBeNull() + }) + + it('does NOT move read-only properties (Manufacturer)', () => { + const device = makeDevice() + addProperty(device, 'Device.DeviceInfo.Manufacturer', 'Sagemcom') + + const count = reclassifyDeviceFromCatalog(device) + + expect(count).toBe(0) + expect(device.getProperty('Device.DeviceInfo.Manufacturer')).not.toBeNull() + expect(device.getParameter('Device.DeviceInfo.Manufacturer')).toBeNull() + }) + + it('does NOT move properties not in catalog', () => { + const device = makeDevice() + addProperty(device, 'Device.Vendor.CustomParam.Unknown', 'value') + + const count = reclassifyDeviceFromCatalog(device) + + // Not in catalog → stays as property (default behavior for unknown paths) + expect(count).toBe(0) + expect(device.getProperty('Device.Vendor.CustomParam.Unknown')).not.toBeNull() + }) + + it('handles instance paths with numeric segments', () => { + const device = makeDevice() + // Device.IP.Interface.{i}.Enable is writable in catalog + addProperty(device, 'Device.IP.Interface.1.Enable', 'true') + + const count = reclassifyDeviceFromCatalog(device) + + expect(count).toBe(1) + expect(device.getParameter('Device.IP.Interface.1.Enable')).not.toBeNull() + expect(device.getProperty('Device.IP.Interface.1.Enable')).toBeNull() + }) + + it('reclassifies multiple properties in one call', () => { + const device = makeDevice() + addProperty(device, 'Device.ManagementServer.PeriodicInformInterval', '60') + addProperty(device, 'Device.DeviceInfo.ProvisioningCode', 'code') + addProperty(device, 'Device.DeviceInfo.Manufacturer', 'Sagemcom') // read-only, stays + addProperty(device, 'Device.DeviceInfo.SerialNumber', 'SN123') // read-only, stays + + const count = reclassifyDeviceFromCatalog(device) + + expect(count).toBe(2) + expect(device.getParameters()).toHaveLength(2) + expect(device.getProperties()).toHaveLength(2) + }) + + it('preserves value and type when moving property to parameter', () => { + const device = makeDevice() + device.setProperty( + new DeviceProperty('Device.ManagementServer.PeriodicInformInterval', '120', ParameterType.UNSIGNED_INT) + ) + + reclassifyDeviceFromCatalog(device) + + const param = device.getParameter('Device.ManagementServer.PeriodicInformInterval') + expect(param).not.toBeNull() + expect(param!.value).toBe('120') + expect(param!.type).toBe(ParameterType.UNSIGNED_INT) + }) + + it('returns 0 when device has no properties', () => { + const device = makeDevice() + + const count = reclassifyDeviceFromCatalog(device) + + expect(count).toBe(0) + }) + + it('does not touch existing parameters', () => { + const device = makeDevice() + device.setParameter(new DeviceParameter('Device.ManagementServer.URL', 'http://acs', ParameterType.STRING)) + addProperty(device, 'Device.DeviceInfo.Manufacturer', 'Sagemcom') + + reclassifyDeviceFromCatalog(device) + + expect(device.getParameter('Device.ManagementServer.URL')!.value).toBe('http://acs') + expect(device.getParameters()).toHaveLength(1) + }) + + it('works with InternetGatewayDevice paths (TR-098) — not in TR-181 catalog', () => { + const device = new Device(new DeviceId('Huawei', '202BC1', 'BM632w', '000000')) + device.setGpnStatus('unsupported') + device.setDataModel('tr-098') + addProperty(device, 'InternetGatewayDevice.DeviceInfo.ProvisioningCode', 'code') + addProperty(device, 'InternetGatewayDevice.ManagementServer.PeriodicInformInterval', '60') + + const count = reclassifyDeviceFromCatalog(device) + + // TR-098 paths are NOT in the TR-181 catalog → nothing reclassified + expect(count).toBe(0) + expect(device.getProperties()).toHaveLength(2) + expect(device.getParameters()).toHaveLength(0) + }) +}) diff --git a/tests/unit/services/tr069_trace_service.spec.ts b/tests/unit/services/tr069_trace_service.spec.ts index 4c33e0c..794d839 100644 --- a/tests/unit/services/tr069_trace_service.spec.ts +++ b/tests/unit/services/tr069_trace_service.spec.ts @@ -108,19 +108,19 @@ describe('Tr069TraceService', () => { expect(fsPromisesMocks.writeFile).toHaveBeenCalledTimes(2) expect(fsPromisesMocks.writeFile).toHaveBeenNthCalledWith( 1, - expect.stringContaining('config/tr069-trace.json.tmp'), + expect.stringContaining('tr069-trace.json.tmp'), expect.stringContaining(baseEntry.deviceId), 'utf-8' ) expect(fsPromisesMocks.writeFile).toHaveBeenNthCalledWith( 2, - expect.stringContaining('config/tr069-trace.json.tmp'), + expect.stringContaining('tr069-trace.json.tmp'), JSON.stringify({ entries: [] }, null, 2), 'utf-8' ) expect(fsMocks.renameSync).toHaveBeenCalledWith( - expect.stringContaining('config/tr069-trace.json.tmp'), - expect.stringContaining('config/tr069-trace.json') + expect.stringContaining('tr069-trace.json.tmp'), + expect.stringContaining('tr069-trace.json') ) })