matterbridge-roborock-vacuum-plugin 1.1.0-rc13 → 1.1.0-rc14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/README.md +1 -1
  2. package/dist/platform.js +2 -2
  3. package/dist/roborockCommunication/broadcast/client/LocalNetworkClient.js +27 -27
  4. package/dist/roborockCommunication/broadcast/client/MQTTClient.js +1 -2
  5. package/dist/roborockCommunication/broadcast/clientRouter.js +1 -1
  6. package/dist/roborockCommunication/broadcast/listener/implementation/connectionStateListener.js +2 -2
  7. package/dist/roborockCommunication/broadcast/messageProcessor.js +9 -0
  8. package/dist/roborockCommunication/broadcast/model/protocol.js +9 -0
  9. package/dist/roborockService.js +20 -1
  10. package/matterbridge-roborock-vacuum-plugin.config.json +1 -1
  11. package/matterbridge-roborock-vacuum-plugin.schema.json +1 -1
  12. package/misc/status.md +119 -0
  13. package/package.json +1 -1
  14. package/src/platform.ts +2 -2
  15. package/src/roborockCommunication/broadcast/client/LocalNetworkClient.ts +35 -33
  16. package/src/roborockCommunication/broadcast/client/MQTTClient.ts +2 -2
  17. package/src/roborockCommunication/broadcast/clientRouter.ts +1 -1
  18. package/src/roborockCommunication/broadcast/listener/implementation/connectionStateListener.ts +2 -3
  19. package/src/roborockCommunication/broadcast/messageProcessor.ts +12 -5
  20. package/src/roborockCommunication/broadcast/model/protocol.ts +9 -0
  21. package/src/roborockService.ts +21 -1
  22. package/src/tests/roborockCommunication/RESTAPI/roborockAuthenticateApi.test.ts +136 -0
  23. package/src/tests/roborockCommunication/RESTAPI/roborockIoTApi.test.ts +106 -0
  24. package/src/tests/roborockCommunication/broadcast/client/LocalNetworkClient.test.ts +3 -5
  25. package/src/tests/roborockCommunication/broadcast/clientRouter.test.ts +168 -0
  26. package/src/tests/roborockCommunication/broadcast/messageProcessor.test.ts +131 -0
  27. package/src/tests/roborockService.test.ts +102 -2
  28. package/src/tests/roborockService3.test.ts +133 -0
  29. package/src/tests/roborockService4.test.ts +76 -0
  30. package/src/tests/roborockService5.test.ts +79 -0
@@ -0,0 +1,136 @@
1
+ import { RoborockAuthenticateApi } from '../../../roborockCommunication/RESTAPI/roborockAuthenticateApi';
2
+
3
+ describe('RoborockAuthenticateApi', () => {
4
+ let mockLogger: any;
5
+ let mockAxiosFactory: any;
6
+ let mockAxiosInstance: any;
7
+ let api: any;
8
+
9
+ beforeEach(() => {
10
+ mockLogger = { info: jest.fn(), error: jest.fn() };
11
+ mockAxiosInstance = {
12
+ post: jest.fn(),
13
+ get: jest.fn(),
14
+ };
15
+ mockAxiosFactory = {
16
+ create: jest.fn(() => mockAxiosInstance),
17
+ };
18
+ api = new RoborockAuthenticateApi(mockLogger, mockAxiosFactory);
19
+ });
20
+
21
+ it('should initialize deviceId, logger, axiosFactory', () => {
22
+ expect(api.logger).toBe(mockLogger);
23
+ expect(api.axiosFactory).toBe(mockAxiosFactory);
24
+ expect(typeof api['deviceId']).toBe('string');
25
+ });
26
+
27
+ it('loginWithUserData should call loginWithAuthToken and return userData', async () => {
28
+ const spy = jest.spyOn(api as any, 'loginWithAuthToken');
29
+ const userData = { token: 'abc', other: 'data' };
30
+ const result = await api.loginWithUserData('user', userData);
31
+ expect(spy).toHaveBeenCalledWith('user', 'abc');
32
+ expect(result).toBe(userData);
33
+ });
34
+
35
+ it('loginWithPassword should call auth and return userData', async () => {
36
+ const userData = { token: 'tok', other: 'data' };
37
+ const response = { data: { data: userData } };
38
+ jest.spyOn(api as any, 'getAPIFor').mockResolvedValue(mockAxiosInstance);
39
+ mockAxiosInstance.post.mockResolvedValue(response);
40
+ jest.spyOn(api as any, 'auth').mockReturnValue(userData);
41
+
42
+ const result = await api.loginWithPassword('user', 'pass');
43
+ expect(result).toBe(userData);
44
+ expect(mockAxiosInstance.post).toHaveBeenCalled();
45
+ expect(api['getAPIFor']).toHaveBeenCalledWith('user');
46
+ expect(api['auth']).toHaveBeenCalledWith('user', response.data);
47
+ });
48
+
49
+ it('loginWithPassword should throw error if token missing', async () => {
50
+ const response = { data: { data: null, msg: 'fail', code: 401 } };
51
+ jest.spyOn(api as any, 'getAPIFor').mockResolvedValue(mockAxiosInstance);
52
+ mockAxiosInstance.post.mockResolvedValue(response);
53
+ jest.spyOn(api as any, 'auth').mockImplementation(() => {
54
+ throw new Error('Authentication failed: fail code: 401');
55
+ });
56
+
57
+ await expect(api.loginWithPassword('user', 'pass')).rejects.toThrow('Authentication failed: fail code: 401');
58
+ });
59
+
60
+ it('getHomeDetails should return undefined if username/authToken missing', async () => {
61
+ api['username'] = undefined;
62
+ api['authToken'] = undefined;
63
+ const result = await api.getHomeDetails();
64
+ expect(result).toBeUndefined();
65
+ });
66
+
67
+ it('getHomeDetails should throw error if response.data missing', async () => {
68
+ api['username'] = 'user';
69
+ api['authToken'] = 'tok';
70
+ jest.spyOn(api as any, 'getAPIFor').mockResolvedValue(mockAxiosInstance);
71
+ mockAxiosInstance.get.mockResolvedValue({ data: { data: null } });
72
+
73
+ await expect(api.getHomeDetails()).rejects.toThrow('Failed to retrieve the home details');
74
+ });
75
+
76
+ it('getHomeDetails should return HomeInfo if present', async () => {
77
+ api['username'] = 'user';
78
+ api['authToken'] = 'tok';
79
+ const homeInfo = { home: 'info' };
80
+ jest.spyOn(api as any, 'getAPIFor').mockResolvedValue(mockAxiosInstance);
81
+ mockAxiosInstance.get.mockResolvedValue({ data: { data: homeInfo } });
82
+
83
+ const result = await api.getHomeDetails();
84
+ expect(result).toBe(homeInfo);
85
+ });
86
+
87
+ it('getBaseUrl should throw error if response.data missing', async () => {
88
+ jest.spyOn(api as any, 'apiForUser').mockResolvedValue(mockAxiosInstance);
89
+ mockAxiosInstance.post.mockResolvedValue({ data: { data: null, msg: 'fail' } });
90
+
91
+ await expect(api['getBaseUrl']('user')).rejects.toThrow('Failed to retrieve base URL: fail');
92
+ });
93
+
94
+ it('getBaseUrl should return url if present', async () => {
95
+ jest.spyOn(api as any, 'apiForUser').mockResolvedValue(mockAxiosInstance);
96
+ mockAxiosInstance.post.mockResolvedValue({ data: { data: { url: 'http://base.url' } } });
97
+
98
+ const result = await api['getBaseUrl']('user');
99
+ expect(result).toBe('http://base.url');
100
+ });
101
+
102
+ it('apiForUser should create AxiosInstance with correct headers', async () => {
103
+ const username = 'user';
104
+ const baseUrl = 'http://base.url';
105
+ const spy = jest.spyOn(mockAxiosFactory, 'create');
106
+ await api['apiForUser'](username, baseUrl);
107
+ expect(spy).toHaveBeenCalledWith(
108
+ expect.objectContaining({
109
+ baseURL: baseUrl,
110
+ headers: expect.objectContaining({
111
+ header_clientid: expect.any(String),
112
+ Authorization: undefined,
113
+ }),
114
+ }),
115
+ );
116
+ });
117
+
118
+ it('auth should call loginWithAuthToken and return userData', () => {
119
+ const spy = jest.spyOn(api as any, 'loginWithAuthToken');
120
+ const response = { data: { token: 'tok', other: 'data' }, msg: '', code: 0 };
121
+ const result = api['auth']('user', response);
122
+ expect(spy).toHaveBeenCalledWith('user', 'tok');
123
+ expect(result).toBe(response.data);
124
+ });
125
+
126
+ it('auth should throw error if token missing', () => {
127
+ const response = { data: null, msg: 'fail', code: 401 };
128
+ expect(() => api['auth']('user', response)).toThrow('Authentication failed: fail code: 401');
129
+ });
130
+
131
+ it('loginWithAuthToken should set username and authToken', () => {
132
+ api['loginWithAuthToken']('user', 'tok');
133
+ expect(api['username']).toBe('user');
134
+ expect(api['authToken']).toBe('tok');
135
+ });
136
+ });
@@ -0,0 +1,106 @@
1
+ import axios from 'axios';
2
+ import { RoborockIoTApi } from '../../../roborockCommunication/RESTAPI/roborockIoTApi';
3
+
4
+ describe('RoborockIoTApi', () => {
5
+ let mockLogger: any;
6
+ let mockAxiosInstance: any;
7
+ let mockUserData: any;
8
+ let api: RoborockIoTApi;
9
+
10
+ beforeEach(() => {
11
+ mockLogger = { error: jest.fn() };
12
+ mockAxiosInstance = {
13
+ get: jest.fn(),
14
+ post: jest.fn(),
15
+ interceptors: { request: { use: jest.fn() } },
16
+ getUri: jest.fn((config) => config.url),
17
+ };
18
+ mockUserData = {
19
+ rriot: {
20
+ r: { a: 'http://base.url' },
21
+ u: 'uid',
22
+ s: 'sid',
23
+ h: 'hkey',
24
+ },
25
+ };
26
+ jest.spyOn(axios, 'create').mockReturnValue(mockAxiosInstance);
27
+ api = new RoborockIoTApi(mockUserData, mockLogger);
28
+ });
29
+
30
+ afterEach(() => {
31
+ jest.restoreAllMocks();
32
+ });
33
+
34
+ it('should initialize logger and api', () => {
35
+ expect(api.logger).toBe(mockLogger);
36
+ expect(api['api']).toBe(mockAxiosInstance);
37
+ });
38
+
39
+ it('getHome should return home if result exists', async () => {
40
+ const home = { id: 1 };
41
+ mockAxiosInstance.get.mockResolvedValue({ data: { result: home } });
42
+ const result = await api.getHome(1);
43
+ expect(result).toBe(home);
44
+ });
45
+
46
+ it('getHome should log error and return undefined if result missing', async () => {
47
+ mockAxiosInstance.get.mockResolvedValue({ data: {} });
48
+ const result = await api.getHome(1);
49
+ expect(result).toBeUndefined();
50
+ expect(mockLogger.error).toHaveBeenCalledWith('Failed to retrieve the home data');
51
+ });
52
+
53
+ it('getHomev2 should return home if result exists', async () => {
54
+ const home = { id: 2 };
55
+ mockAxiosInstance.get.mockResolvedValue({ data: { result: home } });
56
+ const result = await api.getHomev2(2);
57
+ expect(result).toBe(home);
58
+ });
59
+
60
+ it('getHomev3 should return home if result exists', async () => {
61
+ const home = { id: 3 };
62
+ mockAxiosInstance.get.mockResolvedValue({ data: { result: home } });
63
+ const result = await api.getHomev3(3);
64
+ expect(result).toBe(home);
65
+ });
66
+
67
+ it('getScenes should return scenes if result exists', async () => {
68
+ const scenes = [{ id: 1 }, { id: 2 }];
69
+ mockAxiosInstance.get.mockResolvedValue({ data: { result: scenes } });
70
+ const result = await api.getScenes(1);
71
+ expect(result).toBe(scenes);
72
+ });
73
+
74
+ it('getScenes should log error and return undefined if result missing', async () => {
75
+ mockAxiosInstance.get.mockResolvedValue({ data: {} });
76
+ const result = await api.getScenes(1);
77
+ expect(result).toBeUndefined();
78
+ expect(mockLogger.error).toHaveBeenCalledWith('Failed to retrieve scene');
79
+ });
80
+
81
+ it('startScene should return result if present', async () => {
82
+ mockAxiosInstance.post.mockResolvedValue({ data: { result: 'started' } });
83
+ const result = await api.startScene(1);
84
+ expect(result).toBe('started');
85
+ });
86
+
87
+ it('startScene should log error and return undefined if result missing', async () => {
88
+ mockAxiosInstance.post.mockResolvedValue({ data: {} });
89
+ const result = await api.startScene(1);
90
+ expect(result).toBeUndefined();
91
+ expect(mockLogger.error).toHaveBeenCalledWith('Failed to execute scene');
92
+ });
93
+
94
+ it('getCustom should return result if present', async () => {
95
+ mockAxiosInstance.get.mockResolvedValue({ data: { result: 'custom' } });
96
+ const result = await api.getCustom('/custom/url');
97
+ expect(result).toBe('custom');
98
+ });
99
+
100
+ it('getCustom should log error and return undefined if result missing', async () => {
101
+ mockAxiosInstance.get.mockResolvedValue({ data: {} });
102
+ const result = await api.getCustom('/custom/url');
103
+ expect(result).toBeUndefined();
104
+ expect(mockLogger.error).toHaveBeenCalledWith('Failed to execute scene');
105
+ });
106
+ });
@@ -43,6 +43,7 @@ describe('LocalNetworkClient', () => {
43
43
  debug: jest.fn(),
44
44
  error: jest.fn(),
45
45
  notice: jest.fn(),
46
+ info: jest.fn(),
46
47
  };
47
48
  mockContext = {};
48
49
  mockSocket = {
@@ -134,7 +135,7 @@ describe('LocalNetworkClient', () => {
134
135
  jest.fn();
135
136
  }, 1000);
136
137
  await (client as any).onDisconnect();
137
- expect(mockLogger.error).toHaveBeenCalled();
138
+ expect(mockLogger.info).toHaveBeenCalled();
138
139
  expect(client['connected']).toBe(false);
139
140
  expect(mockSocket.destroy).toHaveBeenCalled();
140
141
  expect(client['socket']).toBeUndefined();
@@ -144,10 +145,7 @@ describe('LocalNetworkClient', () => {
144
145
  it('onError() should log, set connected false, destroy socket, call onError', async () => {
145
146
  client['socket'] = mockSocket;
146
147
  await (client as any).onError(new Error('fail'));
147
- expect(mockLogger.error).toHaveBeenCalledWith(expect.stringContaining('Socket connection error'));
148
- expect(client['connected']).toBe(false);
149
- expect(mockSocket.destroy).toHaveBeenCalled();
150
- expect(client['socket']).toBeUndefined();
148
+ expect(mockLogger.error).toHaveBeenCalledWith(expect.stringContaining(' [LocalNetworkClient]: Socket error for'));
151
149
  expect(client['connectionListeners'].onError).toHaveBeenCalledWith('duid1', expect.stringContaining('fail'));
152
150
  });
153
151
 
@@ -0,0 +1,168 @@
1
+ import { RequestMessage, UserData } from '../../../roborockCommunication';
2
+ import { ClientRouter } from '../../../roborockCommunication/broadcast/clientRouter';
3
+
4
+ describe('ClientRouter', () => {
5
+ let mockLogger: any;
6
+ let mockUserData: UserData;
7
+
8
+ let mockMQTTClient: any;
9
+ let mockLocalNetworkClient: any;
10
+
11
+ beforeEach(() => {
12
+ mockLogger = { debug: jest.fn(), notice: jest.fn() };
13
+ mockUserData = {
14
+ uid: '123',
15
+ token: '123:123/lfrZhw==:123',
16
+ rruid: '123',
17
+ region: 'eu',
18
+ countrycode: '33',
19
+ country: 'FR',
20
+ nickname: '123',
21
+ rriot: {
22
+ u: '123',
23
+ s: '123',
24
+ h: '123',
25
+ k: '123',
26
+ r: {
27
+ r: 'EU',
28
+ a: 'https://api-eu.roborock.com',
29
+ m: 'ssl://mqtt-eu-2.roborock.com:8883',
30
+ l: 'https://wood-eu.roborock.com',
31
+ },
32
+ },
33
+ };
34
+
35
+ mockMQTTClient = {
36
+ isConnected: jest.fn().mockReturnValue(true),
37
+ connect: jest.fn(),
38
+ disconnect: jest.fn(),
39
+ };
40
+
41
+ mockLocalNetworkClient = {
42
+ isConnected: jest.fn().mockReturnValue(true),
43
+ connect: jest.fn(),
44
+ disconnect: jest.fn(),
45
+ send: jest.fn(),
46
+ get: jest.fn(),
47
+ };
48
+ });
49
+
50
+ it('registerConnectionListener should call connectionListeners.register', () => {
51
+ const router = new ClientRouter(mockLogger, mockUserData);
52
+ const listener = {};
53
+ const spy = jest.spyOn(router['connectionListeners'], 'register');
54
+ router.registerConnectionListener(listener as any);
55
+ expect(spy).toHaveBeenCalledWith(listener);
56
+ });
57
+
58
+ it('registerMessageListener should call messageListeners.register', () => {
59
+ const router = new ClientRouter(mockLogger, mockUserData);
60
+ const listener = {};
61
+ const spy = jest.spyOn(router['messageListeners'], 'register');
62
+ router.registerMessageListener(listener as any);
63
+ expect(spy).toHaveBeenCalledWith(listener);
64
+ });
65
+
66
+ it('isConnected should return mqttClient.isConnected', () => {
67
+ const router = new ClientRouter(mockLogger, mockUserData);
68
+ mockMQTTClient = {
69
+ isConnected: jest.fn().mockReturnValue(true),
70
+ };
71
+ router['mqttClient'] = mockMQTTClient;
72
+ expect(router.isConnected()).toBe(true);
73
+ });
74
+
75
+ it('connect should call connect on mqttClient and all localClients', () => {
76
+ const router = new ClientRouter(mockLogger, mockUserData);
77
+ router['mqttClient'] = mockMQTTClient;
78
+
79
+ router.registerClient('duid', '127.0.0.1');
80
+ router['localClients'].set('duid', mockLocalNetworkClient);
81
+
82
+ router.connect();
83
+ expect(mockMQTTClient.connect).toHaveBeenCalled();
84
+ expect(mockLocalNetworkClient.connect).toHaveBeenCalled();
85
+ });
86
+
87
+ it('disconnect should call disconnect on mqttClient and all localClients', async () => {
88
+ const router = new ClientRouter(mockLogger, mockUserData);
89
+ router.registerClient('duid', '127.0.0.1');
90
+ mockMQTTClient = {
91
+ isConnected: jest.fn().mockReturnValue(false),
92
+ connect: jest.fn(),
93
+ disconnect: jest.fn(),
94
+ };
95
+ router['mqttClient'] = mockMQTTClient;
96
+
97
+ router['localClients'].set('duid', mockLocalNetworkClient);
98
+
99
+ await router.disconnect();
100
+ expect(mockMQTTClient.disconnect).toHaveBeenCalled();
101
+ expect(mockLocalNetworkClient.disconnect).toHaveBeenCalled();
102
+ });
103
+
104
+ it('send should use mqttClient for secure requests', async () => {
105
+ mockMQTTClient = {
106
+ isConnected: jest.fn().mockReturnValue(false),
107
+ connect: jest.fn(),
108
+ disconnect: jest.fn(),
109
+ send: jest.fn(),
110
+ };
111
+
112
+ const router = new ClientRouter(mockLogger, mockUserData);
113
+ const request: RequestMessage = { secure: true } as any;
114
+
115
+ router['mqttClient'] = mockMQTTClient;
116
+ await router.send('duid', request);
117
+ expect(mockMQTTClient.send).toHaveBeenCalledWith('duid', request);
118
+ });
119
+
120
+ it('send should use localClient for non-secure requests', async () => {
121
+ const router = new ClientRouter(mockLogger, mockUserData);
122
+ router.registerClient('duid', '127.0.0.1');
123
+ const request: RequestMessage = { secure: false } as any;
124
+ router['localClients'].set('duid', mockLocalNetworkClient);
125
+ await router.send('duid', request);
126
+ expect(mockLocalNetworkClient.send).toHaveBeenCalledWith('duid', request);
127
+ });
128
+
129
+ it('get should use mqttClient for secure requests', async () => {
130
+ const router = new ClientRouter(mockLogger, mockUserData);
131
+ const request: RequestMessage = { secure: true } as any;
132
+
133
+ mockMQTTClient = {
134
+ isConnected: jest.fn().mockReturnValue(false),
135
+ connect: jest.fn(),
136
+ disconnect: jest.fn(),
137
+ send: jest.fn(),
138
+ get: jest.fn(),
139
+ };
140
+ router['mqttClient'] = mockMQTTClient;
141
+ await router.get('duid', request);
142
+ expect(mockMQTTClient.get).toHaveBeenCalledWith('duid', request);
143
+ });
144
+
145
+ it('get should use localClient for non-secure requests', async () => {
146
+ const router = new ClientRouter(mockLogger, mockUserData);
147
+ router.registerClient('duid', '127.0.0.1');
148
+ const request: RequestMessage = { secure: false } as any;
149
+ router['localClients'].set('duid', mockLocalNetworkClient);
150
+ await router.get('duid', request);
151
+ expect(mockLocalNetworkClient.get).toHaveBeenCalledWith('duid', request);
152
+ });
153
+
154
+ it('getClient should return localClient if connected', () => {
155
+ const router = new ClientRouter(mockLogger, mockUserData);
156
+ router.registerClient('duid', '127.0.0.1');
157
+ router['localClients'].set('duid', mockLocalNetworkClient);
158
+ expect(router['getClient']('duid')).toBe(mockLocalNetworkClient);
159
+ });
160
+
161
+ it('getClient should return mqttClient if localClient not connected', () => {
162
+ mockLocalNetworkClient.isConnected.mockReturnValue(false);
163
+ const router = new ClientRouter(mockLogger, mockUserData);
164
+ router.registerClient('duid', '127.0.0.1');
165
+ router['mqttClient'] = mockMQTTClient;
166
+ expect(router['getClient']('duid')).toBe(mockMQTTClient);
167
+ });
168
+ });
@@ -0,0 +1,131 @@
1
+ import { MessageProcessor } from '../../../roborockCommunication/broadcast/messageProcessor';
2
+ import { DeviceStatus } from '../../../roborockCommunication/Zmodel/deviceStatus';
3
+ import { RoomInfo } from '../../../roborockCommunication/Zmodel/roomInfo';
4
+
5
+ describe('MessageProcessor', () => {
6
+ let mockClient: any;
7
+ let processor: MessageProcessor;
8
+ let mockLogger: any;
9
+
10
+ beforeEach(() => {
11
+ mockClient = {
12
+ registerMessageListener: jest.fn().mockImplementation(() => {
13
+ void 0;
14
+ }),
15
+ get: jest.fn(),
16
+ send: jest.fn(),
17
+ };
18
+ processor = new MessageProcessor(mockClient);
19
+ mockLogger = { debug: jest.fn(), notice: jest.fn() };
20
+ processor.injectLogger(mockLogger);
21
+ });
22
+
23
+ afterEach(() => {
24
+ jest.restoreAllMocks();
25
+ });
26
+
27
+ it('should inject logger', () => {
28
+ processor.injectLogger(mockLogger);
29
+ expect(processor.logger).toBe(mockLogger);
30
+ });
31
+
32
+ it('getNetworkInfo should call client.get with correct params', async () => {
33
+ mockClient.get.mockResolvedValue('networkInfo');
34
+ const result = await processor.getNetworkInfo('duid');
35
+ expect(mockClient.get).toHaveBeenCalledWith('duid', expect.any(Object));
36
+ expect(result).toBe('networkInfo');
37
+ });
38
+
39
+ it('getDeviceStatus should return DeviceStatus if response exists', async () => {
40
+ const mockDeviceStatus = { status: 'ok' };
41
+ mockClient.get.mockResolvedValue([mockDeviceStatus]);
42
+ const result = await processor.getDeviceStatus('duid');
43
+ expect(mockLogger.debug).toHaveBeenCalled();
44
+ expect(result).toBeInstanceOf(DeviceStatus);
45
+ });
46
+
47
+ it('getDeviceStatus should return undefined if response is falsy', async () => {
48
+ mockClient.get.mockResolvedValue(undefined);
49
+ const result = await processor.getDeviceStatus('duid');
50
+ expect(result).toBeUndefined();
51
+ });
52
+
53
+ it('getRooms should return RoomInfo', async () => {
54
+ const rooms = [
55
+ { id: 1, name: 'Room1' },
56
+ { id: 2, name: 'Room2' },
57
+ ];
58
+ mockClient.get.mockResolvedValue([[1, 2]]);
59
+ const result = await processor.getRooms('duid', rooms);
60
+ expect(result).toBeInstanceOf(RoomInfo);
61
+ });
62
+
63
+ it('gotoDock should call client.send', async () => {
64
+ await processor.gotoDock('duid');
65
+ expect(mockClient.send).toHaveBeenCalledWith('duid', expect.any(Object));
66
+ });
67
+
68
+ it('startClean should call client.send', async () => {
69
+ await processor.startClean('duid');
70
+ expect(mockClient.send).toHaveBeenCalledWith('duid', expect.any(Object));
71
+ });
72
+
73
+ it('startRoomClean should call client.send with correct params', async () => {
74
+ await processor.startRoomClean('duid', [1, 2], 3);
75
+ expect(mockClient.send).toHaveBeenCalledWith('duid', expect.any(Object));
76
+ });
77
+
78
+ it('pauseClean should call client.send', async () => {
79
+ await processor.pauseClean('duid');
80
+ expect(mockClient.send).toHaveBeenCalledWith('duid', expect.any(Object));
81
+ });
82
+
83
+ it('resumeClean should call client.send', async () => {
84
+ await processor.resumeClean('duid');
85
+ expect(mockClient.send).toHaveBeenCalledWith('duid', expect.any(Object));
86
+ });
87
+
88
+ it('stopClean should call client.send', async () => {
89
+ await processor.stopClean('duid');
90
+ expect(mockClient.send).toHaveBeenCalledWith('duid', expect.any(Object));
91
+ });
92
+
93
+ it('sendCustomMessage should call client.send', async () => {
94
+ const def = { method: 'custom' };
95
+ await processor.sendCustomMessage('duid', def as any);
96
+ expect(mockClient.send).toHaveBeenCalledWith('duid', expect.any(Object));
97
+ });
98
+
99
+ it('getCustomMessage should call client.get', async () => {
100
+ const def = { method: 'custom' };
101
+ await processor.getCustomMessage('duid', def as any);
102
+ expect(mockClient.get).toHaveBeenCalledWith('duid', def);
103
+ });
104
+
105
+ it('findMyRobot should call client.send', async () => {
106
+ await processor.findMyRobot('duid');
107
+ expect(mockClient.send).toHaveBeenCalledWith('duid', expect.any(Object));
108
+ });
109
+
110
+ it('getCleanModeData should parse and return correct values', async () => {
111
+ mockClient.get
112
+ .mockResolvedValueOnce([306]) // get_mop_mode
113
+ .mockResolvedValueOnce([101]) // get_custom_mode
114
+ .mockResolvedValueOnce({ water_box_mode: 207, distance_off: 5 }); // get_water_box_custom_mode
115
+
116
+ const result = await processor.getCleanModeData('duid');
117
+ expect(result).toEqual({
118
+ suctionPower: 101,
119
+ waterFlow: 207,
120
+ distance_off: 5,
121
+ mopRoute: 306,
122
+ });
123
+ });
124
+
125
+ it('changeCleanMode should call logger.notice and client.send as needed', async () => {
126
+ mockLogger.notice = jest.fn();
127
+ mockClient.get.mockResolvedValueOnce(110); // currentMopMode
128
+ await processor.changeCleanMode('duid', 101, 207, 306, 5);
129
+ expect(mockLogger.notice).toHaveBeenCalled();
130
+ });
131
+ });