matterbridge-roborock-vacuum-plugin
Version:
Matterbridge Roborock Vacuum Plugin
206 lines • 9.99 kB
JavaScript
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { ClientRouter } from '../../../roborockCommunication/routing/clientRouter.js';
import { asPartial, asType } from '../../testUtils.js';
describe('ClientRouter', () => {
let mockLogger;
let mockUserData;
afterEach(() => {
vi.useRealTimers();
});
let mockMQTTClient;
let mockLocalNetworkClient;
beforeEach(() => {
mockLogger = { debug: vi.fn(), notice: vi.fn(), error: vi.fn(), warn: vi.fn() };
mockUserData = {
username: 'testuser',
uid: '123',
token: '123:123/lfrZhw==:123',
tokentype: 'Bearer',
rruid: '123',
region: 'eu',
countrycode: '33',
country: 'FR',
nickname: '123',
rriot: {
u: '123',
s: '123',
h: '123',
k: '123',
r: {
r: 'EU',
a: 'https://api-eu.roborock.com',
m: 'ssl://mqtt-eu-2.roborock.com:8883',
l: 'https://wood-eu.roborock.com',
},
},
};
mockMQTTClient = {
isConnected: vi.fn().mockReturnValue(true),
connect: vi.fn(),
disconnect: vi.fn(),
};
mockLocalNetworkClient = {
isConnected: vi.fn().mockReturnValue(true),
isReady: vi.fn().mockReturnValue(true),
connect: vi.fn(),
disconnect: vi.fn(),
send: vi.fn(),
get: vi.fn(),
};
});
it('registerConnectionListener should call connectionListener.register', () => {
const router = new ClientRouter(mockLogger, mockUserData);
const listener = asType({});
const spy = vi.spyOn(router['connectionBroadcaster'], 'register');
router.registerConnectionListener(listener);
expect(spy).toHaveBeenCalledWith(listener);
});
it('registerMessageListener should call responseBroadcaster.register', () => {
const router = new ClientRouter(mockLogger, mockUserData);
const listener = asType({});
const spy = vi.spyOn(router['broadcasterFactory'], 'register');
router.registerMessageListener(listener);
expect(spy).toHaveBeenCalledWith(listener);
});
it('isConnected should return mqttClient.isConnected', () => {
const router = new ClientRouter(mockLogger, mockUserData);
mockMQTTClient = {
isConnected: vi.fn().mockReturnValue(true),
};
router['mqttClient'] = mockMQTTClient;
expect(router.isConnected()).toBe(true);
});
it('connect should call connect on mqttClient and all localClients', () => {
const router = new ClientRouter(mockLogger, mockUserData);
router['mqttClient'] = mockMQTTClient;
router.registerClient('duid', '127.0.0.1');
router['localClients'].set('duid', mockLocalNetworkClient);
router.connect();
expect(mockMQTTClient.connect).toHaveBeenCalled();
expect(mockLocalNetworkClient.connect).toHaveBeenCalled();
});
it('disconnect should call disconnect on mqttClient and all localClients', async () => {
const router = new ClientRouter(mockLogger, mockUserData);
router.registerClient('duid', '127.0.0.1');
mockMQTTClient = {
isConnected: vi.fn().mockReturnValue(false),
connect: vi.fn(),
disconnect: vi.fn(),
};
router['mqttClient'] = mockMQTTClient;
router['localClients'].set('duid', mockLocalNetworkClient);
await router.disconnect();
expect(mockMQTTClient.disconnect).toHaveBeenCalled();
expect(mockLocalNetworkClient.disconnect).toHaveBeenCalled();
});
it('send should use mqttClient for secure requests', async () => {
mockMQTTClient = {
isConnected: vi.fn().mockReturnValue(false),
connect: vi.fn(),
disconnect: vi.fn(),
send: vi.fn(),
};
const router = new ClientRouter(mockLogger, mockUserData);
const request = asPartial({ secure: true });
router['mqttClient'] = mockMQTTClient;
await router.send('duid', request);
expect(mockMQTTClient.send).toHaveBeenCalledWith('duid', request);
});
it('send should use localClient for non-secure requests', async () => {
const router = new ClientRouter(mockLogger, mockUserData);
router.registerClient('duid', '127.0.0.1');
const request = asPartial({ secure: false });
router['localClients'].set('duid', mockLocalNetworkClient);
await router.send('duid', request);
expect(mockLocalNetworkClient.send).toHaveBeenCalledWith('duid', request);
});
it('getLocalClient should return localClient if connected', () => {
const router = new ClientRouter(mockLogger, mockUserData);
router.registerClient('duid', '127.0.0.1');
router['localClients'].set('duid', mockLocalNetworkClient);
expect(router['getLocalClient']('duid')).toBe(mockLocalNetworkClient);
});
it('getLocalClient should return mqttClient if localClient not connected', () => {
mockLocalNetworkClient.isConnected.mockReturnValue(false);
const router = new ClientRouter(mockLogger, mockUserData);
router.registerClient('duid', '127.0.0.1');
router['mqttClient'] = mockMQTTClient;
expect(router['getLocalClient']('duid')).toBe(mockMQTTClient);
});
it('getLocalClient should return mqttClient when localClient isReady returns false', () => {
mockLocalNetworkClient.isReady = vi.fn().mockReturnValue(false);
const router = new ClientRouter(mockLogger, mockUserData);
router['mqttClient'] = mockMQTTClient;
router['localClients'].set('duid', mockLocalNetworkClient);
expect(router['getLocalClient']('duid')).toBe(mockMQTTClient);
});
it('getLocalClient should return mqttClient when no localClient is registered for duid', () => {
const router = new ClientRouter(mockLogger, mockUserData);
router['mqttClient'] = mockMQTTClient;
expect(router['getLocalClient']('unknown-duid')).toBe(mockMQTTClient);
});
it('registerDevice should delegate to context', () => {
const router = new ClientRouter(mockLogger, mockUserData);
const spy = vi.spyOn(router['context'], 'registerDevice');
router.registerDevice('duid', 'localKey', '1.0', 12345);
expect(spy).toHaveBeenCalledWith('duid', 'localKey', '1.0', 12345);
});
it('updateNonce should delegate to context', () => {
const router = new ClientRouter(mockLogger, mockUserData);
const spy = vi.spyOn(router['context'], 'updateNonce');
router.updateNonce('duid', 99);
expect(spy).toHaveBeenCalledWith('duid', 99);
});
it('isReady should return mqttClient.isReady', () => {
const router = new ClientRouter(mockLogger, mockUserData);
mockMQTTClient = { isReady: vi.fn().mockReturnValue(true) };
router['mqttClient'] = mockMQTTClient;
expect(router.isReady()).toBe(true);
expect(mockMQTTClient.isReady).toHaveBeenCalled();
});
it('unregisterClient should remove client from localClients', () => {
const router = new ClientRouter(mockLogger, mockUserData);
router.registerClient('duid', '127.0.0.1');
expect(router['localClients'].has('duid')).toBe(true);
router.unregisterClient('duid');
expect(router['localClients'].has('duid')).toBe(false);
});
it('query should return undefined and log on timeout', async () => {
const router = new ClientRouter(mockLogger, mockUserData);
router['mqttClient'] = { ...mockMQTTClient, isReady: vi.fn().mockReturnValue(false), send: vi.fn() };
const request = asPartial({ secure: true, messageId: 1 });
const result = await router.query('duid', request, () => undefined, 1);
expect(result).toBeUndefined();
expect(mockLogger.error).toHaveBeenCalled();
});
it('query should resolve via responseBroadcaster', async () => {
vi.useFakeTimers();
const router = new ClientRouter(mockLogger, mockUserData);
router['mqttClient'] = {
...mockMQTTClient,
isReady: vi.fn().mockReturnValue(false),
send: vi.fn(),
};
const request = asPartial({ secure: true, messageId: 42 });
const parseFn = vi.fn().mockReturnValue('parsed');
const queryPromise = router.query('duid', request, parseFn, 5000);
// Simulate a matching message arriving on the broadcaster
const { OneShotResponseListener } = await import('../../../roborockCommunication/routing/listeners/oneShotResponseListener.js');
const listeners = router['broadcasterFactory']['v1Broadcaster']['listeners'];
const listener = listeners.find((l) => l.duid === 'duid');
if (!listener)
throw new Error('listener not registered');
const { ResponseMessage, HeaderMessage, ResponseBody } = await import('../../../roborockCommunication/models/index.js');
const msg = new ResponseMessage('duid', new HeaderMessage('1.0', 1, 0, 101, 102), new ResponseBody({ '102': { id: 42, result: ['ok'] } }));
await listener.onMessage(msg);
await expect(queryPromise).resolves.toBe('parsed');
});
it('getLocalClient should return mqttClient when LocalNetworkClient isReconnecting', () => {
const router = new ClientRouter(mockLogger, mockUserData);
router['mqttClient'] = mockMQTTClient;
const localClient = router.registerClient('duid', '127.0.0.1');
localClient.intentionalDisconnect = true;
expect(router['getLocalClient']('duid')).toBe(mockMQTTClient);
});
});
//# sourceMappingURL=clientRouter.test.js.map