UNPKG

matterbridge-roborock-vacuum-plugin

Version:
260 lines 12.3 kB
import { beforeEach, describe, expect, it, vi } from 'vitest'; import { DeviceModel } from '../../roborockCommunication/models/index.js'; import { updateFromHomeData } from '../../runtimes/handleHomeDataMessage.js'; import { homeData } from '../testData/mockData.js'; import { asPartial, asType } from '../testUtils.js'; // Mocks const mockUpdateAttribute = vi.fn(); const simpleUpdateAttribute = (...args) => mockUpdateAttribute(...args); const duid = 'test-duid'; const robot = asPartial({ updateAttribute: simpleUpdateAttribute, device: asPartial({ duid, name: 'TestVac', specs: asPartial({ model: DeviceModel.QREVO_EDGE_5V1 }), }), }); const robots = new Map([[duid, robot]]); const registry = asPartial({ get robotsMap() { return robots; }, getRobot: (id) => robots.get(id), hasDevices: () => robots.size > 0, registerRobot: vi.fn(), }); const platformRunner = asPartial({ updateRobotWithPayload: vi.fn(), }); const platform = asPartial({ registry: asPartial(registry), log: asType({ error: vi.fn(), debug: vi.fn(), notice: vi.fn(), fatal: vi.fn(), }), platformRunner, roborockService: asPartial({}), }); describe('updateFromHomeData', () => { beforeEach(() => { vi.clearAllMocks(); }); it('should update robot attributes when valid data is provided', async () => { await updateFromHomeData(homeData, asPartial(platform)); expect(platformRunner.updateRobotWithPayload).toHaveBeenCalled(); expect(platform.log.error).not.toHaveBeenCalled(); }); it('should log error if robot is not found', async () => { platform.registry.robotsMap.clear(); await updateFromHomeData(homeData, asPartial(platform)); expect(platform.log.error).not.toHaveBeenCalledWith(expect.stringContaining('Robot with DUID')); }); it('should log error if device data is undefined', async () => { platform.registry.robotsMap.clear(); // Simulate device missing data by removing the data property platform.registry.robotsMap.set('test-duid', asPartial({ ...robot, device: asPartial({}) })); await updateFromHomeData(homeData, asPartial(platform)); expect(platform.log.error).toHaveBeenCalledWith('Device not found in home data'); }); it('should return early if no state or matterState', async () => { const homeDataWithoutState = asPartial({ ...homeData, devices: [{ ...homeData.devices[0], deviceStatus: {} }], }); platform.registry.robotsMap.clear(); platform.registry.robotsMap.set('test-duid', robot); await updateFromHomeData(homeDataWithoutState, asPartial(platform)); expect(mockUpdateAttribute).not.toHaveBeenCalled(); }); it('should return early if platform has no robots', async () => { platform.registry.robotsMap.clear(); await updateFromHomeData(homeData, asPartial(platform)); // Should not throw, just return early expect(platform.log.error).not.toHaveBeenCalled(); }); it('should skip robot update when robot is found but continue to next device', async () => { const homeDataMultipleDevices = asPartial({ ...homeData, devices: [ { ...homeData.devices[0], duid: 'test-duid' }, // This one should succeed { ...homeData.devices[0], duid: 'test-duid-2' }, // Second device ], }); platform.registry.robotsMap.clear(); platform.registry.robotsMap.set('test-duid', robot); platform.registry.robotsMap.set('test-duid-2', asPartial({ ...robot, updateAttribute: vi.fn() })); await updateFromHomeData(homeDataMultipleDevices, asPartial(platform)); // Both devices should be processed expect(platformRunner.updateRobotWithPayload).toHaveBeenCalled(); }); it('should handle when device is filtered out before loop', async () => { const homeDataUnknownDevice = asPartial({ ...homeData, devices: [{ ...homeData.devices[0], duid: 'unknown-duid' }], }); platform.registry.robotsMap.clear(); platform.registry.robotsMap.set('test-duid', robot); // Robot exists but no matching device await updateFromHomeData(homeDataUnknownDevice, asPartial(platform)); // No devices should match the filter, so no updates expect(mockUpdateAttribute).not.toHaveBeenCalled(); }); it('should handle missing battery level', async () => { const homeDataNoBattery = asPartial({ ...homeData, devices: [{ ...homeData.devices[0], deviceStatus: asPartial({ state: 8 }) }], }); platform.registry.robotsMap.clear(); platform.registry.robotsMap.set('test-duid', robot); await updateFromHomeData(homeDataNoBattery, asPartial(platform)); // Battery payload not sent, but DeviceStatus payload still sent because state exists expect(platformRunner.updateRobotWithPayload).toHaveBeenCalledWith(expect.objectContaining({ type: 'DeviceStatusSimple' })); }); it('should handle state without matterState mapping', async () => { const homeDataInvalidState = asPartial({ ...homeData, devices: [{ ...homeData.devices[0], deviceStatus: asPartial({ state: 9999, battery: 100 }) }], // Truly unmapped state }); platform.registry.robotsMap.clear(); platform.registry.robotsMap.set('test-duid', robot); await updateFromHomeData(homeDataInvalidState, asPartial(platform)); // Should update status via platformRunner expect(platformRunner.updateRobotWithPayload).toHaveBeenCalled(); }); it('should return early when state is undefined', async () => { const homeDataNoState = asPartial({ ...homeData, devices: [{ ...homeData.devices[0], deviceStatus: {} }], }); platform.registry.robotsMap.clear(); platform.registry.robotsMap.set('test-duid', robot); await updateFromHomeData(homeDataNoState, asPartial(platform)); expect(mockUpdateAttribute).not.toHaveBeenCalled(); }); it('should handle missing operational state', async () => { const homeDataNoOpState = asPartial({ ...homeData, devices: [{ ...homeData.devices[0], deviceStatus: asPartial({ state: 9999, battery: 100 }) }], // Truly unmapped state }); platform.registry.robotsMap.clear(); platform.registry.robotsMap.set('test-duid', robot); await updateFromHomeData(homeDataNoOpState, asPartial(platform)); // Should update status via platformRunner expect(platformRunner.updateRobotWithPayload).toHaveBeenCalled(); }); it('should process device when it has docking station status', async () => { const robotWithDss = asPartial({ updateAttribute: simpleUpdateAttribute, device: asPartial({ duid, name: 'TestVac', specs: asPartial({ model: DeviceModel.QREVO_EDGE_5V1 }), }), dockStationStatus: asPartial({ cleanFluidStatus: 0, waterBoxFilterStatus: 0, dustBagStatus: 0, dirtyWaterBoxStatus: 0, clearWaterBoxStatus: 0, isUpdownWaterReady: 0, }), // No error }); platform.registry.robotsMap.clear(); platform.registry.robotsMap.set('test-duid', robotWithDss); await updateFromHomeData(homeData, asPartial(platform)); // Should process normally when no dss error expect(platformRunner.updateRobotWithPayload).toHaveBeenCalled(); }); it('should log error and continue when robot is removed during processing', async () => { // Create a custom platform that removes robot during get() const robots = new Map([['test-duid', robot]]); const customPlatform = asPartial({ log: platform.log, roborockService: asPartial({}), registry: asPartial({ get robotsMap() { return robots; }, getRobot: (id) => { if (id === 'test-duid') return undefined; // Simulate robot removed return robots.get(id); }, hasDevices: () => robots.size > 0, registerRobot: vi.fn(), }), }); await updateFromHomeData(homeData, asPartial(customPlatform)); expect(platform.log.error).toHaveBeenCalledWith(expect.stringContaining('Robot not found: test-duid')); }); it('should update batChargeState when batteryLevel exists', async () => { const homeDataWithBattery = asPartial({ ...homeData, devices: [{ ...homeData.devices[0], deviceStatus: asPartial({ state: 8, battery: 50 }) }], }); platform.registry.robotsMap.clear(); platform.registry.robotsMap.set('test-duid', robot); await updateFromHomeData(homeDataWithBattery, asPartial(platform)); expect(platformRunner.updateRobotWithPayload).toHaveBeenCalled(); }); it('should not update batChargeState when batteryLevel is missing', async () => { const homeDataNoBattery = asPartial({ ...homeData, devices: [{ ...homeData.devices[0], deviceStatus: asPartial({ state: 8 }) }], }); platform.registry.robotsMap.clear(); platform.registry.robotsMap.set('test-duid', robot); await updateFromHomeData(homeDataNoBattery, platform); // Battery not present so no BatteryUpdate payload sent, but DeviceStatus still sent expect(platformRunner.updateRobotWithPayload).toHaveBeenCalledWith(expect.objectContaining({ type: 'DeviceStatusSimple' })); }); it('should handle zero battery level', async () => { const homeDataZeroBattery = asPartial({ ...homeData, devices: [{ ...homeData.devices[0], deviceStatus: asPartial({ state: 8, battery: 0 }) }], }); platform.registry.robotsMap.clear(); platform.registry.robotsMap.set('test-duid', robot); await updateFromHomeData(homeDataZeroBattery, platform); // Battery is 0, which is falsy, so no BatteryUpdate payload, but DeviceStatus still sent expect(platformRunner.updateRobotWithPayload).toHaveBeenCalledWith(expect.objectContaining({ type: 'DeviceStatusSimple' })); }); it('should handle homeData without matching product schema', async () => { const homeDataNoSchema = asPartial({ ...homeData, products: [], // No products means schema won't be found devices: [{ ...homeData.devices[0], deviceStatus: asPartial({ state: 8, battery: 100 }) }], }); platform.registry.robotsMap.clear(); platform.registry.robotsMap.set('test-duid', robot); await updateFromHomeData(homeDataNoSchema, platform); // Should still process but schema will be empty array expect(platformRunner.updateRobotWithPayload).toHaveBeenCalled(); }); it('should use registered robot model to match product schema when home payload model differs', async () => { const homeDataMismatch = asPartial({ ...homeData, devices: [ { ...homeData.devices[0], deviceStatus: asPartial({ state: 8, battery: 100 }), specs: { ...homeData.devices[0].specs, model: DeviceModel.Q7_MAX }, }, ], products: [ asPartial({ id: homeData.products[0].id, model: robot.device.specs?.model, schema: homeData.products[0].schema, }), ], }); platform.registry.robotsMap.clear(); platform.registry.robotsMap.set('test-duid', robot); await updateFromHomeData(homeDataMismatch, platform); expect(platformRunner.updateRobotWithPayload).toHaveBeenCalled(); }); }); //# sourceMappingURL=handleHomeDataMessage.test.js.map