homebridge-tsvesync
Version:
Homebridge plugin for VeSync devices including Levoit air purifiers, humidifiers, and Etekcity smart outlets
302 lines (264 loc) • 10.3 kB
text/typescript
// Set up mocks before imports
jest.mock('../../utils/device-factory');
jest.mock('tsvesync');
jest.mock('../../utils/retry');
import { API, Logger, PlatformAccessory, Service as ServiceType, Characteristic as CharacteristicType, CharacteristicValue } from 'homebridge';
import { VeSync } from 'tsvesync';
import { TSVESyncPlatform } from '../../platform';
import { TEST_CONFIG } from '../setup';
import { createMockLogger, createMockSwitch } from '../utils/test-helpers';
import { DeviceFactory } from '../../utils/device-factory';
import { BaseAccessory } from '../../accessories/base.accessory';
import { VeSyncSwitch } from '../../types/device.types';
import { RetryManager } from '../../utils/retry';
const mockDeviceFactory = jest.mocked(DeviceFactory);
const mockRetryManager = jest.mocked(RetryManager);
describe('Switch Device Tests', () => {
let platform: TSVESyncPlatform;
let mockLogger: jest.Mocked<Logger>;
let mockAPI: jest.Mocked<API>;
let mockVeSync: jest.Mocked<VeSync>;
let mockSetHandler: jest.Mock;
let mockGetHandler: jest.Mock;
const defaultConfig = {
platform: 'TSVESync',
name: 'TSVESync',
username: TEST_CONFIG.username || 'test@example.com',
password: TEST_CONFIG.password || 'password123',
apiUrl: TEST_CONFIG.apiUrl,
debug: true,
};
beforeEach(() => {
jest.clearAllMocks();
// Mock RetryManager to execute operations immediately
mockRetryManager.prototype.execute.mockImplementation(async (operation) => {
return operation();
});
// Setup handlers
mockSetHandler = jest.fn();
mockGetHandler = jest.fn();
// Setup logger mock
mockLogger = createMockLogger();
// Setup API mock
mockAPI = {
version: 2.0,
serverVersion: '1.0.0',
user: {
configPath: jest.fn(),
storagePath: jest.fn(),
persistPath: jest.fn(),
},
hapLegacyTypes: {},
platformAccessory: jest.fn().mockImplementation((name, uuid) => ({
UUID: uuid,
displayName: name,
context: {},
services: new Map(),
addService: jest.fn(),
removeService: jest.fn(),
getService: jest.fn().mockReturnValue({
getCharacteristic: jest.fn().mockReturnValue({
onSet: jest.fn().mockImplementation((fn) => {
mockSetHandler = fn;
return {
onGet: jest.fn()
};
}),
onGet: jest.fn().mockImplementation((fn) => {
mockGetHandler = fn;
return {
updateValue: jest.fn()
};
}),
updateValue: jest.fn(),
}),
setCharacteristic: jest.fn().mockReturnThis(),
}),
getServiceById: jest.fn(),
})),
versionGreaterOrEqual: jest.fn(),
registerAccessory: jest.fn(),
registerPlatform: jest.fn(),
publishCameraAccessories: jest.fn(),
registerPlatformAccessories: jest.fn(),
unregisterPlatformAccessories: jest.fn(),
publishExternalAccessories: jest.fn(),
updatePlatformAccessories: jest.fn(),
registerPlatformAccessory: jest.fn(),
on: jest.fn(),
emit: jest.fn(),
hap: {
Service: {
Switch: jest.fn().mockImplementation(() => ({
getCharacteristic: jest.fn().mockReturnValue({
onSet: jest.fn().mockImplementation((fn) => {
mockSetHandler = fn;
return {
onGet: jest.fn()
};
}),
onGet: jest.fn().mockImplementation((fn) => {
mockGetHandler = fn;
return {
updateValue: jest.fn()
};
}),
updateValue: jest.fn(),
}),
setCharacteristic: jest.fn().mockReturnThis(),
})),
AccessoryInformation: jest.fn(),
} as unknown as typeof ServiceType,
Characteristic: {
On: 'On',
Name: 'Name',
Model: 'Model',
Manufacturer: 'Manufacturer',
SerialNumber: 'SerialNumber',
FirmwareRevision: 'FirmwareRevision',
} as unknown as typeof CharacteristicType,
uuid: {
generate: jest.fn().mockImplementation((id) => `test-uuid-${id}`),
},
},
} as unknown as jest.Mocked<API>;
// Create platform instance
platform = new TSVESyncPlatform(mockLogger, defaultConfig, mockAPI);
platform.isReady = jest.fn().mockResolvedValue(true);
platform.discoverDevices = jest.fn().mockResolvedValue(undefined);
// Setup VeSync mock
mockVeSync = {
login: jest.fn().mockResolvedValue(true),
getDevices: jest.fn().mockResolvedValue(true),
switches: [],
} as unknown as jest.Mocked<VeSync>;
// Replace VeSync client
(platform as any).client = mockVeSync;
// Mock DeviceFactory
mockDeviceFactory.getAccessoryCategory.mockReturnValue(8); // 8 is the category for switches
mockDeviceFactory.createAccessory.mockImplementation((platform, accessory, device) => {
const baseAccessory = {
service: new mockAPI.hap.Service.Switch(),
platform,
accessory,
device,
initialize: jest.fn().mockResolvedValue(undefined),
updateCharacteristicValue: jest.fn(),
} as unknown as BaseAccessory;
return baseAccessory;
});
});
afterEach(() => {
jest.clearAllMocks();
});
describe('switch state management', () => {
let mockSwitch: jest.Mocked<VeSyncSwitch>;
let switchAccessory: BaseAccessory;
let onCharacteristic: any;
beforeEach(async () => {
// Create a mock switch with immediate responses
mockSwitch = createMockSwitch({
deviceName: 'Test Switch',
deviceType: 'ESW01-EU',
cid: 'test-cid-123',
uuid: 'test-uuid-123',
power: false,
});
// Mock successful details retrieval
mockSwitch.getDetails.mockResolvedValue(true);
mockSwitch.deviceStatus = 'off';
// Create a mock accessory
const mockAccessory = new mockAPI.platformAccessory(mockSwitch.deviceName, mockAPI.hap.uuid.generate(mockSwitch.cid));
mockAccessory.context.device = mockSwitch;
// Create the switch accessory
switchAccessory = mockDeviceFactory.createAccessory(platform, mockAccessory, mockSwitch);
// Set up the characteristic handlers
onCharacteristic = {
onSet: jest.fn().mockImplementation(async (value: boolean) => {
if (value) {
await mockSwitch.turnOn();
} else {
await mockSwitch.turnOff();
}
}),
onGet: jest.fn().mockImplementation(async () => {
return mockSwitch.deviceStatus === 'on';
}),
};
// Mock the service to use our handlers
const mockService = {
getCharacteristic: jest.fn().mockReturnValue(onCharacteristic),
setCharacteristic: jest.fn().mockReturnThis(),
};
(mockAccessory.getService as jest.Mock).mockReturnValue(mockService);
await switchAccessory.initialize();
});
it('should handle power state changes', async () => {
// Test turning on
await onCharacteristic.onSet(true);
expect(mockSwitch.turnOn).toHaveBeenCalled();
// Test turning off
await onCharacteristic.onSet(false);
expect(mockSwitch.turnOff).toHaveBeenCalled();
});
it('should handle device errors', async () => {
// Mock error responses
mockSwitch.turnOn.mockRejectedValueOnce(new Error('Failed to turn on'));
mockSwitch.turnOff.mockRejectedValueOnce(new Error('Failed to turn off'));
// Test error handling for turn on
await expect(onCharacteristic.onSet(true)).rejects.toThrow('Failed to turn on');
// Test error handling for turn off
await expect(onCharacteristic.onSet(false)).rejects.toThrow('Failed to turn off');
});
});
describe('switch device types', () => {
beforeEach(async () => {
// Ensure platform is ready before each test
await platform.isReady();
});
it('should support ESW01-EU devices', async () => {
// Create a mock switch with immediate responses
const mockSwitch = createMockSwitch({
deviceName: 'Test Switch',
deviceType: 'ESW01-EU',
cid: 'test-cid-123',
uuid: 'test-uuid-123',
});
// Mock successful details retrieval
mockSwitch.getDetails.mockResolvedValue(true);
mockSwitch.deviceStatus = 'off';
// Create a mock accessory
const mockAccessory = new mockAPI.platformAccessory(mockSwitch.deviceName, mockAPI.hap.uuid.generate(mockSwitch.cid));
mockAccessory.context.device = mockSwitch;
// Create the switch accessory
const switchAccessory = mockDeviceFactory.createAccessory(platform, mockAccessory, mockSwitch);
await switchAccessory.initialize();
// Get the switch service
const switchService = mockAccessory.getService(platform.Service.Switch);
expect(switchService).toBeDefined();
expect(mockSwitch.deviceType).toBe('ESW01-EU');
});
it('should support other switch device types', async () => {
// Create a mock switch with immediate responses
const mockSwitch = createMockSwitch({
deviceName: 'Test Switch',
deviceType: 'other-switch-type',
cid: 'test-cid-123',
uuid: 'test-uuid-123',
});
// Mock successful details retrieval
mockSwitch.getDetails.mockResolvedValue(true);
mockSwitch.deviceStatus = 'off';
// Create a mock accessory
const mockAccessory = new mockAPI.platformAccessory(mockSwitch.deviceName, mockAPI.hap.uuid.generate(mockSwitch.cid));
mockAccessory.context.device = mockSwitch;
// Create the switch accessory
const switchAccessory = mockDeviceFactory.createAccessory(platform, mockAccessory, mockSwitch);
await switchAccessory.initialize();
// Get the switch service
const switchService = mockAccessory.getService(platform.Service.Switch);
expect(switchService).toBeDefined();
expect(mockSwitch.deviceType).toBe('other-switch-type');
});
});
});