matterbridge-roborock-vacuum-plugin
Version:
Matterbridge Roborock Vacuum Plugin
483 lines • 21.8 kB
JavaScript
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