matterbridge-roborock-vacuum-plugin
Version:
Matterbridge Roborock Vacuum Plugin
375 lines • 18.2 kB
JavaScript
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { RoomIndexMap } from '../../core/application/models/index.js';
import { DeviceError } from '../../errors/index.js';
import { AreaManagementService } from '../../services/areaManagementService.js';
describe('AreaManagementService', () => {
let areaService;
let mockLogger;
let mockIotApi;
let mockMessageClient;
let mockMessageRoutingService;
const mockDeviceId = 'test-device-1';
const mockAreas = [
{ areaId: 0, mapId: 1 },
{ areaId: 1, mapId: 1 },
{ areaId: 2, mapId: 1 },
];
const mockRoutines = [
{ areaId: 100, mapId: 1 },
{ areaId: 101, mapId: 1 },
];
beforeEach(() => {
mockLogger = createMockLogger();
mockIotApi = createMockIotApi();
mockMessageClient = createMockMessageClient();
mockMessageRoutingService = {
getRoomMap: vi.fn(),
getMapInfo: vi.fn(),
};
areaService = new AreaManagementService(mockLogger, mockMessageRoutingService);
areaService.setIotApi(mockIotApi);
areaService.setMessageClient(mockMessageClient);
});
function createMockLogger() {
return {
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
notice: vi.fn(),
};
}
function createMockIotApi() {
return {
getScenes: vi.fn(),
startScene: vi.fn(),
};
}
function createMockMessageClient() {
return {
get: vi.fn(),
send: vi.fn(),
};
}
afterEach(() => {
vi.clearAllMocks();
});
describe('Initialization', () => {
it('should initialize with all dependencies', () => {
expect(areaService).toBeDefined();
});
it('should initialize with logger only', () => {
const serviceWithoutDeps = new AreaManagementService(mockLogger, undefined);
expect(serviceWithoutDeps).toBeDefined();
});
it('should initialize with logger and message routing service', () => {
const service = new AreaManagementService(mockLogger, mockMessageRoutingService);
expect(service).toBeDefined();
});
it('should set IoT API after initialization', () => {
const service = new AreaManagementService(mockLogger, undefined);
const newIotApi = {};
expect(() => {
service.setIotApi(newIotApi);
}).not.toThrow();
});
it('should set message client after initialization', () => {
const service = new AreaManagementService(mockLogger, undefined);
const newMessageClient = {};
expect(() => {
service.setMessageClient(newMessageClient);
}).not.toThrow();
});
});
describe('Supported Areas Management', () => {
it('should set and get supported areas', () => {
areaService.setSupportedAreas(mockDeviceId, mockAreas);
const areas = areaService.getSupportedAreas(mockDeviceId);
expect(areas).toBeDefined();
expect(areas).toHaveLength(3);
expect(areas).toEqual(mockAreas);
});
it('should return undefined for device without areas', () => {
const areas = areaService.getSupportedAreas('unknown-device');
expect(areas).toEqual([]);
});
it('should handle empty areas array', () => {
areaService.setSupportedAreas(mockDeviceId, []);
const areas = areaService.getSupportedAreas(mockDeviceId);
expect(areas).toBeDefined();
expect(areas).toHaveLength(0);
});
it('should support multiple devices', () => {
const device2 = 'test-device-2';
const areas2 = [{ areaId: 10, mapId: 2 }];
areaService.setSupportedAreas(mockDeviceId, mockAreas);
areaService.setSupportedAreas(device2, areas2);
expect(areaService.getSupportedAreas(mockDeviceId)).toEqual(mockAreas);
expect(areaService.getSupportedAreas(device2)).toEqual(areas2);
});
it('should update existing areas', () => {
areaService.setSupportedAreas(mockDeviceId, mockAreas);
const newAreas = [{ areaId: 99, mapId: 1 }];
areaService.setSupportedAreas(mockDeviceId, newAreas);
const areas = areaService.getSupportedAreas(mockDeviceId);
expect(areas).toEqual(newAreas);
});
});
describe('Area Index Map Management', () => {
it('should set and get area index map', () => {
const roomMapData = new Map([
[0, { roomId: 16, mapId: 1, roomName: 'rn-1' }],
[1, { roomId: 17, mapId: 1, roomName: 'rn-2' }],
]);
const roomInfo = new Map([]);
const indexMap = new RoomIndexMap(roomMapData, roomInfo);
areaService.setSupportedAreaIndexMap(mockDeviceId, indexMap);
const retrievedMap = areaService.getSupportedAreasIndexMap(mockDeviceId);
expect(retrievedMap).toBeDefined();
expect(retrievedMap).toBe(indexMap);
});
it('should return undefined for device without index map', () => {
const indexMap = areaService.getSupportedAreasIndexMap('unknown-device');
expect(indexMap).toBeUndefined();
});
});
describe('Selected Areas Management', () => {
it('should return empty array when no areas selected', () => {
const selectedAreas = areaService.getSelectedAreas(mockDeviceId);
expect(selectedAreas).toEqual([]);
});
it('should handle empty selection', () => {
const roomMapData = new Map([[0, { roomId: 16, mapId: 1, roomName: 'rn-1' }]]);
const roomInfo = new Map([]);
const indexMap = new RoomIndexMap(roomMapData, roomInfo);
areaService.setSupportedAreaIndexMap(mockDeviceId, indexMap);
areaService.setSelectedAreas(mockDeviceId, []);
const selectedAreas = areaService.getSelectedAreas(mockDeviceId);
expect(selectedAreas).toEqual([]);
});
it('should return empty array for unknown device', () => {
const selectedAreas = areaService.getSelectedAreas('unknown-device');
expect(selectedAreas).toEqual([]);
});
});
describe('Supported Routines Management', () => {
it('should set and get supported routines', () => {
areaService.setSupportedRoutines(mockDeviceId, mockRoutines);
const routines = areaService.getSupportedRoutines(mockDeviceId);
expect(routines).toBeDefined();
expect(routines).toHaveLength(2);
expect(routines).toEqual(mockRoutines);
});
it('should return undefined for device without routines', () => {
const routines = areaService.getSupportedRoutines('unknown-device');
expect(routines).toBeUndefined();
});
it('should handle empty routines array', () => {
areaService.setSupportedRoutines(mockDeviceId, []);
const routines = areaService.getSupportedRoutines(mockDeviceId);
expect(routines).toBeDefined();
expect(routines).toHaveLength(0);
});
});
describe('getMapInformation', () => {
const mockMultipleMaps = [
{
max_multi_map: 4,
max_bak_map: 1,
multi_map_count: 2,
map_info: [
{ mapFlag: 1, name: 'First Floor', rooms: [] },
{ mapFlag: 2, name: 'Second Floor', rooms: [] },
],
},
];
it('should retrieve map information successfully', async () => {
mockMessageRoutingService.getMapInfo.mockResolvedValue(mockMultipleMaps);
const mapInfo = await areaService.getMapInfo(mockDeviceId);
expect(mapInfo).toBeDefined();
expect(mockMessageRoutingService.getMapInfo).toHaveBeenCalled();
expect(mockLogger.debug).toHaveBeenCalledWith('AreaManagementService - getMapInfo', mockDeviceId);
});
it('should return undefined when no maps available', async () => {
mockMessageRoutingService.getMapInfo.mockResolvedValue(undefined);
const mapInfo = await areaService.getMapInfo(mockDeviceId);
expect(mapInfo).toBeUndefined();
});
it('should throw DeviceError when service routing not initialized', async () => {
const serviceWithoutClient = new AreaManagementService(mockLogger, undefined);
await expect(serviceWithoutClient.getMapInfo(mockDeviceId)).rejects.toThrow(DeviceError);
await expect(serviceWithoutClient.getMapInfo(mockDeviceId)).rejects.toThrow('Service routing not initialized');
});
});
describe('getRoomMappings', () => {
const mockRoomMappings = [
[16, 1],
[17, 2],
[18, 3],
];
it('should retrieve room mappings with non-secure request', async () => {
mockMessageRoutingService.getRoomMap.mockImplementation((duid, activeMap, rooms) => {
mockMessageClient.get(duid, { method: 'get_room_mapping', secure: false });
return Promise.resolve(mockRoomMappings);
});
const mappings = await areaService.getRoomMap(mockDeviceId, 1);
expect(mappings).toEqual(mockRoomMappings);
expect(mockMessageRoutingService.getRoomMap).toHaveBeenCalledWith(mockDeviceId, 1);
});
it('should return undefined when message client not initialized', async () => {
const serviceWithoutClient = new AreaManagementService(mockLogger, undefined);
await expect(serviceWithoutClient.getRoomMap(mockDeviceId, 1)).rejects.toThrow(DeviceError);
});
it('should handle empty room mappings', async () => {
mockMessageRoutingService.getRoomMap.mockResolvedValue([]);
const mappings = await areaService.getRoomMap(mockDeviceId, 1);
expect(mappings).toBeInstanceOf(Object);
expect(mappings.length).toEqual(0);
});
});
describe('getScenes', () => {
const mockHomeId = 12345;
const mockScenes = [{ id: 1, name: 'Morning Clean' }, { id: 2, name: 'Evening Clean' }];
it('should retrieve scenes successfully', async () => {
mockIotApi.getScenes.mockResolvedValue(mockScenes);
const scenes = await areaService.getScenes(mockHomeId);
expect(scenes).toEqual(mockScenes);
expect(mockIotApi.getScenes).toHaveBeenCalledWith(mockHomeId);
});
it('should throw DeviceError when IoT API not initialized', async () => {
const serviceWithoutApi = new AreaManagementService(mockLogger, undefined);
await expect(serviceWithoutApi.getScenes(mockHomeId)).rejects.toThrow(DeviceError);
await expect(serviceWithoutApi.getScenes(mockHomeId)).rejects.toThrow('IoT API not initialized');
});
it('should handle empty scenes array', async () => {
mockIotApi.getScenes.mockResolvedValue([]);
const scenes = await areaService.getScenes(mockHomeId);
expect(scenes).toEqual([]);
});
it('should handle undefined scenes', async () => {
mockIotApi.getScenes.mockResolvedValue(undefined);
const scenes = await areaService.getScenes(mockHomeId);
expect(scenes).toBeUndefined();
});
});
describe('startScene', () => {
const mockSceneId = 42;
it('should start scene successfully', async () => {
const mockResult = { success: true };
mockIotApi.startScene.mockResolvedValue(mockResult);
const result = await areaService.startScene(mockSceneId);
expect(result).toEqual(mockResult);
expect(mockIotApi.startScene).toHaveBeenCalledWith(mockSceneId);
});
it('should throw DeviceError when IoT API not initialized', async () => {
const serviceWithoutApi = new AreaManagementService(mockLogger, undefined);
await expect(serviceWithoutApi.startScene(mockSceneId)).rejects.toThrow(DeviceError);
await expect(serviceWithoutApi.startScene(mockSceneId)).rejects.toThrow('IoT API not initialized');
});
it('should propagate API errors', async () => {
const error = new Error('Scene execution failed');
mockIotApi.startScene.mockRejectedValue(error);
await expect(areaService.startScene(mockSceneId)).rejects.toThrow('Scene execution failed');
});
});
describe('clearAll', () => {
it('should clear all data', () => {
// Set up some data
areaService.setSupportedAreas(mockDeviceId, mockAreas);
areaService.setSupportedRoutines(mockDeviceId, mockRoutines);
const roomMapData = new Map([[0, { roomId: 16, mapId: 1, roomName: 'rn-1' }]]);
const roomInfo = new Map([]);
areaService.setSupportedAreaIndexMap(mockDeviceId, new RoomIndexMap(roomMapData, roomInfo));
areaService.setSelectedAreas(mockDeviceId, [0]);
// Clear all
areaService.clearAll();
// Verify everything is cleared
expect(areaService.getSupportedAreas(mockDeviceId)).toEqual([]);
expect(areaService.getSupportedRoutines(mockDeviceId)).toBeUndefined();
expect(areaService.getSupportedAreasIndexMap(mockDeviceId)).toBeUndefined();
expect(areaService.getSelectedAreas(mockDeviceId)).toEqual([]);
expect(mockLogger.debug).toHaveBeenCalledWith('AreaManagementService - All data cleared');
});
it('should be safe to call multiple times', () => {
areaService.clearAll();
areaService.clearAll();
expect(() => {
areaService.clearAll();
}).not.toThrow();
});
it('should clear empty service without errors', () => {
const newService = new AreaManagementService(mockLogger, undefined);
expect(() => {
newService.clearAll();
}).not.toThrow();
});
});
describe('Integration Scenarios', () => {
it('should handle complete area setup workflow', () => {
// Setup areas
areaService.setSupportedAreas(mockDeviceId, mockAreas);
// Setup index map
const roomMapData = new Map([
[0, { roomId: 16, mapId: 1, roomName: 'rn-1' }],
[1, { roomId: 17, mapId: 1, roomName: 'rn-2' }],
[2, { roomId: 18, mapId: 1, roomName: 'rn-3' }],
]);
const roomInfo = new Map([]);
areaService.setSupportedAreaIndexMap(mockDeviceId, new RoomIndexMap(roomMapData, roomInfo));
// Setup routines
areaService.setSupportedRoutines(mockDeviceId, mockRoutines);
// Select areas
areaService.setSelectedAreas(mockDeviceId, [0, 2]);
// Verify complete setup
expect(areaService.getSupportedAreas(mockDeviceId)).toEqual(mockAreas);
expect(areaService.getSupportedRoutines(mockDeviceId)).toEqual(mockRoutines);
expect(areaService.getSelectedAreas(mockDeviceId)).toEqual([0, 2]);
});
it('should handle multiple devices independently', () => {
const device1 = 'device-1';
const device2 = 'device-2';
const areas1 = [{ areaId: 1, mapId: 1 }];
const areas2 = [{ areaId: 2, mapId: 2 }];
areaService.setSupportedAreas(device1, areas1);
areaService.setSupportedAreas(device2, areas2);
expect(areaService.getSupportedAreas(device1)).toEqual(areas1);
expect(areaService.getSupportedAreas(device2)).toEqual(areas2);
});
it('should handle scene workflow with IoT API', async () => {
const homeId = 123;
const mockScenes = [{ id: 1, name: 'Test' }];
mockIotApi.getScenes.mockResolvedValue(mockScenes);
mockIotApi.startScene.mockResolvedValue({ success: true });
const scenes = await areaService.getScenes(homeId);
expect(scenes).toEqual(mockScenes);
const result = await areaService.startScene(1);
expect(result).toEqual({ success: true });
});
});
describe('Error Handling', () => {
it('should handle unknown device IDs gracefully', () => {
expect(areaService.getSupportedAreas('unknown-device')).toEqual([]);
expect(areaService.getSelectedAreas('unknown-device')).toEqual([]);
expect(areaService.getSupportedRoutines('unknown-device')).toBeUndefined();
});
it('should handle malformed area data', () => {
const malformedAreas = [{ areaId: 1 }, { areaId: 2 }];
expect(() => {
areaService.setSupportedAreas(mockDeviceId, malformedAreas);
}).not.toThrow();
const areas = areaService.getSupportedAreas(mockDeviceId);
expect(areas).toEqual(malformedAreas);
});
it('should propagate message client errors', async () => {
const error = new Error('Network error');
mockMessageRoutingService.getMapInfo.mockRejectedValue(error);
await expect(areaService.getMapInfo(mockDeviceId)).rejects.toThrow('Network error');
});
it('should propagate IoT API errors', async () => {
const error = new Error('API error');
mockIotApi.getScenes.mockRejectedValue(error);
await expect(areaService.getScenes(123)).rejects.toThrow('API error');
});
});
});
//# sourceMappingURL=areaManagementService.test.js.map