UNPKG

matterbridge-roborock-vacuum-plugin

Version:
483 lines 21.8 kB
import { beforeEach, describe, expect, it, vi } from 'vitest'; import { RoborockMatterbridgePlatform } from '../module.js'; import { DeviceCategory } from '../roborockCommunication/models/deviceCategory.js'; import { DeviceModel } from '../roborockCommunication/models/deviceModel.js'; import { asPartial } from './helpers/testUtils.js'; vi.mock('../share/behaviorFactory.js', () => ({ configureBehavior: vi.fn().mockReturnValue({ handler: vi.fn() }), })); vi.mock('../initialData/index.js', async (importOriginal) => { const original = await importOriginal(); return { ...original, getSupportedAreas: vi.fn().mockReturnValue({ supportedAreas: [], roomIndexMap: new Map() }), getSupportedScenes: vi.fn().mockReturnValue([]), }; }); vi.mock('../types/roborockVacuumCleaner.js', () => { class MockVacuum { username; device; serialNumber; deviceName; vendorId = 1; vendorName = 'Roborock'; productName; deviceTypes = new Map(); mode = undefined; configureHandler = vi.fn(); getClusterServerOptions = vi.fn().mockReturnValue({ deviceTypeList: [] }); createDefaultBridgedDeviceBasicInformationClusterServer = vi.fn(); createDefaultIdentifyClusterServer = vi.fn(); constructor(username, device) { this.username = username; this.device = device; this.serialNumber = device.duid; this.deviceName = device.name; this.productName = device.name; } } return { RoborockVacuumCleaner: MockVacuum }; }); function createMockLogger() { return asPartial({ debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn(), notice: vi.fn(), log: vi.fn(), logLevel: "debug" /* LogLevel.DEBUG */, }); } function createMockMatterbridge() { return { matterbridgeVersion: '3.5.5', matterbridgePluginDirectory: '/tmp', matterbridgeDirectory: '/tmp', }; } function createMockConfig(overrides = {}) { return { name: 'TestPlatform', authentication: { username: 'test', region: 'US', forceAuthentication: false, password: 'test', authenticationMethod: 'Password', }, pluginConfiguration: { whiteList: [], sanitizeSensitiveLogs: false, enableMultipleMap: false, unregisterOnShutdown: false, refreshInterval: 60, debug: false, enableServerMode: false, }, advancedFeature: { enableAdvancedFeature: true, settings: { clearStorageOnStartup: false, showRoutinesAsRoom: false, includeDockStationStatus: false, includeVacuumErrorStatus: false, forceRunAtDefault: false, useVacationModeToSendVacuumToDock: false, enableCleanModeMapping: true, cleanModeSettings: { vacuuming: { fanMode: 'Silent', mopRouteMode: 'Standard' }, mopping: { waterFlowMode: 'Low', mopRouteMode: 'Standard', distanceOff: 0 }, vacmop: { fanMode: 'Silent', waterFlowMode: 'Low', mopRouteMode: 'Standard', distanceOff: 0 }, }, overrideMatterConfiguration: false, matterOverrideSettings: { matterVendorName: 'xxx', matterVendorId: 123, matterProductName: 'yy', matterProductId: 456, }, enableEmailNotification: false, emailNotificationSettings: {}, }, }, persistDirectory: '/tmp', ...overrides, }; } describe('module.ts - complete coverage', () => { let mockLogger; let mockMatterbridge; let mockPersist; beforeEach(() => { mockLogger = createMockLogger(); mockMatterbridge = createMockMatterbridge(); mockPersist = asPartial({ init: vi.fn().mockResolvedValue(undefined), getItem: vi.fn().mockResolvedValue(undefined), setItem: vi.fn().mockResolvedValue(undefined), }); vi.clearAllMocks(); }); describe('onConfigureDevice complete flow', () => { it('should execute full onConfigureDevice including setDeviceNotify and requestHomeData', async () => { const mockDevice = asPartial({ duid: 'device1', serialNumber: 'device1', name: 'Vacuum 1', specs: { id: 'device1', firmwareVersion: '1.0', serialNumber: 'device1', model: DeviceModel.QREVO_EDGE_5V1, category: DeviceCategory.VacuumCleaner, batteryLevel: 100, hasRealTimeConnection: true, }, rrHomeId: 123, store: asPartial({ homeData: { id: 123, name: 'Test Home', products: [], devices: [], receivedDevices: [], rooms: [], }, }), }); const platform = new RoborockMatterbridgePlatform(mockMatterbridge, mockLogger, createMockConfig()); platform.persist = mockPersist; platform.version = '1.0.0'; let capturedCallback = () => { }; const roomData = [ [1, '11100845', 14], [2, '11100849', 9], [3, '11100842', 6], [4, '11100847', 1], ]; const mockRoborockService = asPartial({ initializeMessageClientForLocal: vi.fn().mockResolvedValue(true), getMapInfo: vi.fn().mockResolvedValue({ allRooms: [] }), setSupportedAreas: vi.fn(), setSupportedAreaIndexMap: vi.fn(), setDeviceNotify: vi.fn().mockImplementation((cb) => { capturedCallback = cb; }), activateDeviceNotify: vi.fn(), getRoomMap: vi.fn().mockResolvedValue(roomData), }); platform.roborockService = mockRoborockService; const mockPlatformRunner = asPartial({ updateRobotWithPayload: vi.fn().mockResolvedValue(undefined), requestHomeData: vi.fn().mockResolvedValue(undefined), activateHandlerFunctions: vi.fn(), }); platform.platformRunner = mockPlatformRunner; platform.registry.registerDevice(mockDevice); platform.validateDevice = vi.fn().mockReturnValue(true); platform.registerDevice = vi.fn().mockResolvedValue(undefined); platform.setSelectDevice = vi.fn(); await platform.configurator['configureDevice'](mockDevice, platform.roborockService); const mockRobot = asPartial({ serialNumber: 'device1', device: mockDevice }); platform.registry.registerRobot(mockRobot); await platform.configurator['onConfigureDevice'](platform.roborockService); expect(mockRoborockService.setDeviceNotify).toHaveBeenCalled(); expect(capturedCallback).toBeDefined(); const testPayload = { type: 'status', data: { duid: 'device1', battery: 90 }, duid: 'device1', }; capturedCallback(testPayload); expect(mockPlatformRunner.updateRobotWithPayload).toHaveBeenCalledWith(testPayload); expect(mockRoborockService.activateDeviceNotify).toHaveBeenCalledWith(mockDevice); expect(mockPlatformRunner.requestHomeData).toHaveBeenCalled(); expect(mockLogger.log).toHaveBeenCalledWith('info', 'onConfigureDevice finished'); }); it('should skip activating notify for failed device configurations', async () => { const mockDevice1 = asPartial({ duid: 'device1', serialNumber: 'device1', name: 'Vacuum 1', specs: asPartial({ model: DeviceModel.S7 }), rrHomeId: 123, store: asPartial({ homeData: { id: 123, name: 'Test Home', products: [], devices: [], receivedDevices: [], rooms: [], }, }), }); const mockDevice2 = asPartial({ duid: 'device2', serialNumber: 'device2', name: 'Vacuum 2', specs: asPartial({ model: DeviceModel.S6 }), rrHomeId: 456, store: asPartial({ homeData: { id: 456, name: 'Test Home 2', products: [], devices: [], receivedDevices: [], rooms: [], }, }), }); const platform = new RoborockMatterbridgePlatform(mockMatterbridge, mockLogger, createMockConfig()); platform.persist = mockPersist; platform.version = '1.0.0'; const roomData = [ [1, '11100845', 14], [2, '11100849', 9], [3, '11100842', 6], [4, '11100847', 1], ]; const mockRoborockService = asPartial({ initializeMessageClientForLocal: vi .fn() .mockImplementation((d) => Promise.resolve(d.duid === 'device1')), getMapInfo: vi.fn().mockResolvedValue({ allRooms: [] }), setSupportedAreas: vi.fn(), setSupportedAreaIndexMap: vi.fn(), setDeviceNotify: vi.fn(), activateDeviceNotify: vi.fn(), getRoomMap: vi.fn().mockResolvedValue(roomData), }); platform.roborockService = mockRoborockService; const mockPlatformRunner = asPartial({ updateRobotWithPayload: vi.fn().mockResolvedValue(undefined), requestHomeData: vi.fn().mockResolvedValue(undefined), activateHandlerFunctions: vi.fn(), }); platform.platformRunner = mockPlatformRunner; platform.registry.registerDevice(mockDevice1); platform.registry.registerDevice(mockDevice2); platform.validateDevice = vi.fn().mockReturnValue(true); platform.registerDevice = vi.fn().mockResolvedValue(undefined); platform.setSelectDevice = vi.fn(); await platform.configurator['configureDevice'](mockDevice1, platform.roborockService); await platform.configurator['configureDevice'](mockDevice2, platform.roborockService); const mockRobot1 = { serialNumber: 'device1', device: mockDevice1 }; const mockRobot2 = { serialNumber: 'device2', device: mockDevice2 }; platform.registry.registerRobot(asPartial(mockRobot1)); platform.registry.registerRobot(asPartial(mockRobot2)); await platform.configurator['onConfigureDevice'](platform.roborockService); expect(mockRoborockService.activateDeviceNotify).toHaveBeenCalledTimes(2); expect(mockRoborockService.activateDeviceNotify).toHaveBeenCalledWith(mockDevice1); expect(mockRoborockService.activateDeviceNotify).toHaveBeenCalledWith(mockDevice2); }); it('should handle requestHomeData Error exception', async () => { const mockDevice = asPartial({ duid: 'device1', serialNumber: 'device1', name: 'Vacuum 1', specs: asPartial({ model: DeviceModel.S7 }), rrHomeId: 123, store: asPartial({ homeData: { id: 123, name: 'Test Home', products: [], devices: [], receivedDevices: [], rooms: [], }, }), }); const platform = new RoborockMatterbridgePlatform(mockMatterbridge, mockLogger, createMockConfig()); platform.persist = mockPersist; platform.version = '1.0.0'; const roomData = [ [1, '11100845', 14], [2, '11100849', 9], [3, '11100842', 6], [4, '11100847', 1], ]; const mockRoborockService = asPartial({ initializeMessageClientForLocal: vi.fn().mockResolvedValue(true), getMapInfo: vi.fn().mockResolvedValue({ allRooms: [] }), setSupportedAreas: vi.fn(), setSupportedAreaIndexMap: vi.fn(), setDeviceNotify: vi.fn(), activateDeviceNotify: vi.fn(), getRoomMap: vi.fn().mockResolvedValue(roomData), }); platform.roborockService = mockRoborockService; const mockPlatformRunner = { updateRobotWithPayload: vi.fn().mockResolvedValue(undefined), requestHomeData: vi.fn().mockRejectedValue(new Error('Network timeout')), activateHandlerFunctions: vi.fn(), }; platform.platformRunner = asPartial(mockPlatformRunner); platform.registry.registerDevice(mockDevice); platform.validateDevice = vi.fn().mockReturnValue(true); platform.registerDevice = vi.fn().mockResolvedValue(undefined); platform.setSelectDevice = vi.fn(); await platform.configurator['configureDevice'](mockDevice, platform.roborockService); const mockRobot = asPartial({ serialNumber: 'device1', device: mockDevice }); platform.registry.registerRobot(mockRobot); await platform.configurator['onConfigureDevice'](platform.roborockService); expect(mockLogger.log).toHaveBeenCalledWith('error', 'requestHomeData (initial) failed: Network timeout'); }); it('should handle requestHomeData string exception', async () => { const mockDevice = asPartial({ duid: 'device1', serialNumber: 'device1', name: 'Vacuum 1', specs: asPartial({ model: DeviceModel.S7 }), rrHomeId: 123, store: asPartial({ homeData: { id: 123, name: 'Test Home', products: [], devices: [], receivedDevices: [], rooms: [], }, }), }); const platform = new RoborockMatterbridgePlatform(mockMatterbridge, mockLogger, createMockConfig()); platform.persist = mockPersist; platform.version = '1.0.0'; const roomData = [ [1, '11100845', 14], [2, '11100849', 9], [3, '11100842', 6], [4, '11100847', 1], ]; const mockRoborockService = asPartial({ initializeMessageClientForLocal: vi.fn().mockResolvedValue(true), getMapInfo: vi.fn().mockResolvedValue({ allRooms: [] }), setSupportedAreas: vi.fn(), setSupportedAreaIndexMap: vi.fn(), setDeviceNotify: vi.fn(), activateDeviceNotify: vi.fn(), getRoomMap: vi.fn().mockResolvedValue(roomData), }); platform.roborockService = mockRoborockService; const mockPlatformRunner = { updateRobotWithPayload: vi.fn().mockResolvedValue(undefined), requestHomeData: vi.fn().mockRejectedValue('Connection failed'), activateHandlerFunctions: vi.fn(), }; platform.platformRunner = asPartial(mockPlatformRunner); platform.registry.registerDevice(mockDevice); platform.validateDevice = vi.fn().mockReturnValue(true); platform.registerDevice = vi.fn().mockResolvedValue(undefined); platform.setSelectDevice = vi.fn(); await platform.configurator['configureDevice'](mockDevice, platform.roborockService); const mockRobot = asPartial({ serialNumber: 'device1', device: mockDevice }); platform.registry.registerRobot(mockRobot); await platform.configurator['onConfigureDevice'](platform.roborockService); expect(mockLogger.log).toHaveBeenCalledWith('error', 'requestHomeData (initial) failed: Connection failed'); }); }); describe('configureDevice room fetching', () => { it('should fetch rooms when undefined', async () => { const mockDevice = asPartial({ duid: 'device1', serialNumber: 'device1', name: 'Vacuum 1', specs: asPartial({ model: DeviceModel.S7 }), rrHomeId: 123, store: asPartial({ homeData: { id: 123, name: 'Test Home', products: [], devices: [], receivedDevices: [], rooms: [], }, }), }); const platform = new RoborockMatterbridgePlatform(mockMatterbridge, mockLogger, createMockConfig()); platform.persist = mockPersist; platform.version = '1.0.0'; const mockMapInfo = { maps: [], allRooms: [ { id: 1, iot_name: 'Living Room', tag: 0, iot_name_id: 'room1' }, { id: 2, iot_name: 'Kitchen', tag: 0, iot_name_id: 'room2' }, ], hasRooms: true, getActiveMapId: vi.fn().mockReturnValue(0), }; const mockRoborockService = asPartial({ initializeMessageClientForLocal: vi.fn().mockResolvedValue(true), getMapInfo: vi.fn().mockResolvedValue(mockMapInfo), getRoomMap: vi.fn().mockResolvedValue([]), setSupportedAreas: vi.fn(), setSupportedAreaIndexMap: vi.fn(), }); platform.roborockService = asPartial(mockRoborockService); platform.validateDevice = vi.fn().mockReturnValue(true); platform.registerDevice = vi.fn().mockResolvedValue(undefined); platform.setSelectDevice = vi.fn(); const mockRobot = asPartial({ serialNumber: 'device1', device: mockDevice, }); platform.registry.registerRobot(mockRobot); const result = await platform.configurator['configureDevice'](mockDevice, platform.roborockService); expect(result).toBe(true); expect(mockRoborockService.getMapInfo).toHaveBeenCalledWith('device1'); }); it('should fetch rooms when array is empty', async () => { const mockDevice = asPartial({ duid: 'device1', serialNumber: 'device1', name: 'Vacuum 1', specs: asPartial({ model: DeviceModel.S7 }), rrHomeId: 123, store: asPartial({ homeData: { id: 123, name: 'Test Home', products: [], devices: [], receivedDevices: [], rooms: [], }, }), }); const platform = new RoborockMatterbridgePlatform(mockMatterbridge, mockLogger, createMockConfig()); platform.persist = mockPersist; platform.version = '1.0.0'; const mockMapInfo = { maps: [], allRooms: [{ id: 5, iot_name: 'Bedroom', tag: 0, iot_name_id: 'room5' }], hasRooms: true, getActiveMapId: vi.fn().mockReturnValue(0), }; const mockRoborockService = { initializeMessageClientForLocal: vi.fn().mockResolvedValue(true), getMapInfo: vi.fn().mockResolvedValue(mockMapInfo), getRoomMap: vi.fn().mockResolvedValue([]), setSupportedAreas: vi.fn(), setSupportedAreaIndexMap: vi.fn(), }; platform.roborockService = asPartial(mockRoborockService); platform.validateDevice = vi.fn().mockReturnValue(true); platform.registerDevice = vi.fn().mockResolvedValue(undefined); platform.setSelectDevice = vi.fn(); const mockRobot = asPartial({ serialNumber: 'device1', device: mockDevice, }); platform.registry.registerRobot(mockRobot); const result = await platform.configurator['configureDevice'](mockDevice, platform.roborockService); expect(result).toBe(true); }); }); }); //# sourceMappingURL=module.complete.coverage.test.js.map