matterbridge-roborock-vacuum-plugin
Version:
Matterbridge Roborock Vacuum Plugin
143 lines • 7.33 kB
JavaScript
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { createMockLogger } from '../helpers/testUtils.js';
// Mock all module boundaries cmdB01PoseInfo touches, following this project's convention of
// vi.mock at module boundaries for CLI command isolation testing (see mapListHelpers.test.ts /
// broadcast client tests for the vi.mock pattern used elsewhere in this suite).
const mockConnectDevice = vi.fn();
const mockWaitForPush = vi.fn();
const mockParseRoomsFromEncryptedBinary = vi.fn();
const mockResolveRoomFromPose = vi.fn();
vi.mock('../../cli/connection.js', () => ({
connectDevice: (...args) => mockConnectDevice(...args),
}));
vi.mock('../../cli/waitForPush.js', () => ({
waitForPush: (...args) => mockWaitForPush(...args),
}));
vi.mock('../../roborockCommunication/map/b01/b01MapParser.js', () => ({
B01MapParser: vi.fn().mockImplementation(function B01MapParser() {
this.parseRoomsFromEncryptedBinary = (...args) => mockParseRoomsFromEncryptedBinary(...args);
}),
}));
vi.mock('../../roborockCommunication/map/b01/roomMatrixResolver.js', () => ({
resolveRoomFromPose: (...args) => mockResolveRoomFromPose(...args),
}));
import { cmdB01PoseInfo } from '../../cli/commands/b01PoseInfo.js';
function createSession(overrides = {}) {
return {
email: 'test@example.com',
userData: {},
devices: overrides.devices ??
[
{
duid: 'duid-1',
serialNumber: 'SN001',
specs: { model: 'roborock.vacuum.a75', protocol: 202, serialNumber: 'SN001' },
},
],
};
}
function createClientRouterStub() {
return { disconnect: vi.fn().mockResolvedValue(undefined) };
}
function createDispatcherStub() {
return { getMapInfo: vi.fn().mockResolvedValue(undefined) };
}
describe('cmdB01PoseInfo', () => {
let logger;
let consoleLogSpy;
let consoleErrorSpy;
beforeEach(() => {
vi.clearAllMocks();
logger = createMockLogger();
consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => undefined);
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined);
});
afterEach(() => {
vi.clearAllMocks();
consoleLogSpy.mockRestore();
consoleErrorSpy.mockRestore();
});
it('should print a timeout message and return without throwing when no map buffer is received', async () => {
const clientRouter = createClientRouterStub();
const dispatcher = createDispatcherStub();
mockConnectDevice.mockResolvedValue({ clientRouter, dispatcher });
mockWaitForPush.mockResolvedValue(undefined);
const session = createSession();
await expect(cmdB01PoseInfo('duid-1', session, logger)).resolves.toBeUndefined();
expect(consoleLogSpy).toHaveBeenCalledWith('No map binary received within timeout.');
expect(mockParseRoomsFromEncryptedBinary).not.toHaveBeenCalled();
expect(clientRouter.disconnect).toHaveBeenCalledTimes(1);
});
it('should print "could not determine" messaging (not crash, not fabricate a room ID) when resolver returns undefined', async () => {
const clientRouter = createClientRouterStub();
const dispatcher = createDispatcherStub();
mockConnectDevice.mockResolvedValue({ clientRouter, dispatcher });
mockWaitForPush.mockResolvedValue(Buffer.from([1, 2, 3]));
mockParseRoomsFromEncryptedBinary.mockReturnValue({
rooms: [{ roomId: 1, roomName: 'Kitchen' }],
currentPose: { x: 1, y: 2 },
roomMatrix: { data: Buffer.from([1, 2, 3]) },
});
mockResolveRoomFromPose.mockReturnValue(undefined);
const session = createSession();
await expect(cmdB01PoseInfo('duid-1', session, logger)).resolves.toBeUndefined();
expect(consoleLogSpy).toHaveBeenCalledWith('Resolved room:', 'could not determine — roomMatrix decoding not yet implemented, pending real-device capture');
expect(clientRouter.disconnect).toHaveBeenCalledTimes(1);
});
it('should handle decode failure gracefully (no throw) via try/catch around parseRoomsFromEncryptedBinary', async () => {
const clientRouter = createClientRouterStub();
const dispatcher = createDispatcherStub();
mockConnectDevice.mockResolvedValue({ clientRouter, dispatcher });
mockWaitForPush.mockResolvedValue(Buffer.from([1, 2, 3]));
mockParseRoomsFromEncryptedBinary.mockImplementation(() => {
throw new Error('corrupt buffer');
});
const session = createSession();
await expect(cmdB01PoseInfo('duid-1', session, logger)).resolves.toBeUndefined();
expect(consoleErrorSpy).toHaveBeenCalledWith('Failed to decode B01 map binary:', 'corrupt buffer');
expect(mockResolveRoomFromPose).not.toHaveBeenCalled();
expect(clientRouter.disconnect).toHaveBeenCalledTimes(1);
});
it('should throw a clear "Device not found" error when duid is not found in session.devices', async () => {
const session = createSession({ devices: [] });
await expect(cmdB01PoseInfo('missing-duid', session, logger)).rejects.toThrow('Device not found: missing-duid');
expect(mockConnectDevice).not.toHaveBeenCalled();
});
it('should call clientRouter.disconnect() in the finally block on the success path', async () => {
const clientRouter = createClientRouterStub();
const dispatcher = createDispatcherStub();
mockConnectDevice.mockResolvedValue({ clientRouter, dispatcher });
mockWaitForPush.mockResolvedValue(Buffer.from([1, 2, 3]));
mockParseRoomsFromEncryptedBinary.mockReturnValue({
rooms: [],
currentPose: undefined,
roomMatrix: undefined,
});
mockResolveRoomFromPose.mockReturnValue(undefined);
const session = createSession();
await cmdB01PoseInfo('duid-1', session, logger);
expect(clientRouter.disconnect).toHaveBeenCalledTimes(1);
});
it('should call clientRouter.disconnect() in the finally block on the decode-error path', async () => {
const clientRouter = createClientRouterStub();
const dispatcher = createDispatcherStub();
mockConnectDevice.mockResolvedValue({ clientRouter, dispatcher });
mockWaitForPush.mockResolvedValue(Buffer.from([1, 2, 3]));
mockParseRoomsFromEncryptedBinary.mockImplementation(() => {
throw new Error('bad data');
});
const session = createSession();
await cmdB01PoseInfo('duid-1', session, logger);
expect(clientRouter.disconnect).toHaveBeenCalledTimes(1);
});
it('should call clientRouter.disconnect() in the finally block on the timeout path', async () => {
const clientRouter = createClientRouterStub();
const dispatcher = createDispatcherStub();
mockConnectDevice.mockResolvedValue({ clientRouter, dispatcher });
mockWaitForPush.mockResolvedValue(undefined);
const session = createSession();
await cmdB01PoseInfo('duid-1', session, logger);
expect(clientRouter.disconnect).toHaveBeenCalledTimes(1);
});
});
//# sourceMappingURL=b01PoseInfo.test.js.map