homebridge-rabbitair
Version:
Homebridge plugin for RabbitAir air purifiers
862 lines (697 loc) • 26.9 kB
Markdown
# Specification: Integrating hap-fluent & hap-test into homebridge-rabbitair
**Repository:** https://github.com/pradeepmouli/homebridge-rabbitair
**Date:** December 30, 2025
**Status:** Draft
## Executive Summary
This spec outlines the integration of `hap-fluent` (fluent interface for HAP characteristics) and `hap-test` (comprehensive testing harness) into the existing homebridge-rabbitair plugin. The integration will modernize the codebase, improve type safety, enhance testability, and establish a robust testing foundation.
## Background
### Current State
The homebridge-rabbitair plugin:
- **Technology:** TypeScript, based on Homebridge Plugin Template
- **Testing:** Mocha-based test framework (test/ directory exists)
- **Coverage:** Uses c8 for coverage reporting
- **Services Implemented:**
- Air Purifier Service (Active, Current/Target State, Rotation Speed, Filter indicators)
- Air Quality Sensor Service
- **Network Protocol:** UDP communication on port 9009 with 32-char token authentication
### Target Libraries
- **hap-fluent:** Fluent interface for HomeKit accessories with type-safe characteristic operations
- **hap-test:** Test harness with MockHomeKit, TimeController, NetworkSimulator, custom matchers
## Goals
1. **Refactor Platform Code with hap-fluent**
- Replace imperative service/characteristic setup with fluent API
- Improve type safety and code readability
- Reduce boilerplate in RabbitAirAccessory implementation
2. **Establish Comprehensive Testing with hap-test**
- Unit tests for RabbitAirClient protocol logic
- Integration tests for platform lifecycle and accessory registration
- End-to-end tests simulating network conditions and device states
- Replace or complement existing Mocha tests
3. **Maintain Backward Compatibility**
- Preserve existing configuration schema
- No breaking changes to plugin behavior
- Gradual migration path
## Architecture
### Package Structure
```
homebridge-rabbitair/
├── src/
│ ├── platform.ts # RabbitAirPlatform (refactor with hap-fluent)
│ ├── platformAccessory.ts # RabbitAirAccessory (refactor with hap-fluent)
│ ├── rabbitAirClient.ts # UDP protocol client (keep, add tests)
│ └── settings.ts # Configuration constants
├── test/
│ ├── unit/
│ │ ├── platform.test.ts # Platform initialization tests
│ │ ├── platformAccessory.test.ts # Accessory setup and state management
│ │ └── rabbitAirClient.test.ts # Protocol and network logic
│ ├── integration/
│ │ ├── platform-lifecycle.test.ts # Full platform lifecycle
│ │ ├── accessory-updates.test.ts # Device state synchronization
│ │ └── network-resilience.test.ts # Network failure scenarios
│ └── e2e/
│ └── rabbitair-plugin.test.ts # Complete plugin behavior
└── package.json # Add hap-fluent, hap-test dependencies
```
### Dependency Integration
#### package.json Updates
```json
{
"dependencies": {
"hap-fluent": "workspace:*", // or published version
"homebridge": "^1.8.0"
},
"devDependencies": {
"hap-test": "workspace:*", // or published version
"vitest": "^4.0.0",
"@types/node": "^20.0.0"
},
"scripts": {
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage",
"test:unit": "vitest run test/unit",
"test:integration": "vitest run test/integration",
"test:e2e": "vitest run test/e2e"
}
}
```
#### Testing Framework Migration
- **Phase 1:** Keep Mocha for existing tests, add Vitest for new hap-test tests
- **Phase 2:** Gradually migrate Mocha tests to Vitest
- **Phase 3:** Remove Mocha dependency once migration complete
## Implementation Plan
### Phase 1: Setup & Dependencies (2-3 hours)
#### Task 1.1: Install Dependencies
```bash
cd homebridge-rabbitair
pnpm add hap-fluent homebridge
pnpm add -D hap-test vitest /coverage-istanbul
```
#### Task 1.2: Configure Vitest
Create `vitest.config.ts`:
```typescript
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
environment: 'node',
coverage: {
enabled: true,
provider: 'istanbul',
reporter: ['text', 'json', 'html', 'lcov'],
reportsDirectory: './coverage',
include: ['src/**/*.ts'],
exclude: ['**/*.d.ts', 'test/**', 'dist/**'],
},
include: ['test/**/*.test.ts'],
testTimeout: 5000,
},
});
```
#### Task 1.3: Update TypeScript Config
Ensure `tsconfig.json` includes test files:
```json
{
"compilerOptions": {
"types": ["node", "vitest/globals"]
},
"include": ["src/**/*", "test/**/*"]
}
```
### Phase 2: Refactor with hap-fluent (4-6 hours)
#### Task 2.1: Refactor RabbitAirAccessory Service Setup
**Before (Current):**
```typescript
// Imperative service setup
const service = this.accessory.getService(this.platform.Service.AirPurifier)
|| this.accessory.addService(this.platform.Service.AirPurifier);
service.setCharacteristic(this.platform.Characteristic.Name, accessory.context.device.name);
service.getCharacteristic(this.platform.Characteristic.Active)
.onSet(this.setActive.bind(this))
.onGet(this.getActive.bind(this));
service.getCharacteristic(this.platform.Characteristic.RotationSpeed)
.onSet(this.setRotationSpeed.bind(this))
.onGet(this.getRotationSpeed.bind(this));
```
**After (with hap-fluent):**
```typescript
import { AccessoryHandler } from 'hap-fluent';
export class RabbitAirAccessory {
private handler: AccessoryHandler;
constructor(
private readonly platform: RabbitAirPlatform,
private readonly accessory: PlatformAccessory,
) {
// Initialize fluent handler
this.handler = new AccessoryHandler(this, accessory);
// Fluent service configuration
const { airPurifier, airQualitySensor } = this.handler
.service('AirPurifier', 'Air Purifier')
.characteristic('active')
.onGet(async () => this.getActive())
.onSet(async (value) => this.setActive(value))
.parent
.characteristic('currentAirPurifierState')
.onGet(async () => this.getCurrentState())
.parent
.characteristic('targetAirPurifierState')
.onSet(async (value) => this.setTargetState(value))
.onGet(async () => this.getTargetState())
.parent
.characteristic('rotationSpeed')
.onSet(async (value) => this.setRotationSpeed(value))
.onGet(async () => this.getRotationSpeed())
.parent
.characteristic('filterLifeLevel')
.onGet(async () => this.getFilterLife())
.parent
.characteristic('filterChangeIndication')
.onGet(async () => this.getFilterChangeIndication())
.parent
.service('AirQualitySensor', 'Air Quality')
.characteristic('airQuality')
.onGet(async () => this.getAirQuality())
.parent
.build();
}
}
```
#### Task 2.2: Implement Type-Safe State Management
```typescript
import type { InterfaceFor } from 'hap-fluent';
// Get typed interfaces for services
type AirPurifierInterface = InterfaceFor<typeof Service.AirPurifier>;
type AirQualitySensorInterface = InterfaceFor<typeof Service.AirQualitySensor>;
// Use in methods for type safety
private async updateAirPurifierState(state: AirPurifierInterface['currentAirPurifierState']) {
// Type-safe characteristic updates
}
```
#### Task 2.3: Add Interceptors for Device State Mapping
```typescript
import { debounce, mapValues } from 'hap-fluent/interceptors';
// Apply debouncing to rotation speed to reduce UDP traffic
.characteristic('rotationSpeed')
.interceptor(debounce(500)) // Wait 500ms before sending
.interceptor(mapValues({
// Map HomeKit 0-100 to RabbitAir speed levels
0: 'silent',
25: 'low',
50: 'medium',
75: 'high',
100: 'turbo',
}))
.onSet(async (value) => this.setRotationSpeed(value))
```
### Phase 3: Unit Testing with hap-test (6-8 hours)
#### Task 3.1: Test RabbitAirClient Protocol Logic
Create `test/unit/rabbitAirClient.test.ts`:
```typescript
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { NetworkSimulator } from 'hap-test';
import { RabbitAirClient } from '../../src/rabbitAirClient';
describe('RabbitAirClient', () => {
let client: RabbitAirClient;
let mockSocket: any;
beforeEach(() => {
mockSocket = {
send: vi.fn(),
on: vi.fn(),
close: vi.fn(),
};
client = new RabbitAirClient('192.168.1.100', 9009, 'test-token', mockSocket);
});
describe('Protocol Commands', () => {
it('should send properly formatted power on command', async () => {
await client.setPower(true);
expect(mockSocket.send).toHaveBeenCalledWith(
expect.any(Buffer),
9009,
'192.168.1.100',
expect.any(Function)
);
// Verify command structure
const sentBuffer = mockSocket.send.mock.calls[0][0];
expect(sentBuffer).toMatchSnapshot();
});
it('should parse device state response correctly', async () => {
const mockResponse = Buffer.from(/* ... */);
const state = await client.parseState(mockResponse);
expect(state).toEqual({
power: true,
mode: 'auto',
fanSpeed: 'medium',
airQuality: 2,
filterLife: 85,
});
});
});
describe('Network Resilience', () => {
it('should retry failed commands', async () => {
mockSocket.send
.mockImplementationOnce((buf, port, host, cb) => cb(new Error('Network error')))
.mockImplementationOnce((buf, port, host, cb) => cb(null));
await expect(client.setPower(true)).resolves.not.toThrow();
expect(mockSocket.send).toHaveBeenCalledTimes(2);
});
it('should timeout after max retries', async () => {
mockSocket.send.mockImplementation((buf, port, host, cb) =>
cb(new Error('Network error'))
);
await expect(client.setPower(true)).rejects.toThrow('Max retries exceeded');
});
});
});
```
#### Task 3.2: Test RabbitAirAccessory Setup
Create `test/unit/platformAccessory.test.ts`:
```typescript
import { describe, it, expect, beforeEach } from 'vitest';
import { TestHarness, toHaveService, toHaveCharacteristic } from 'hap-test';
import { RabbitAirAccessory } from '../../src/platformAccessory';
describe('RabbitAirAccessory', () => {
let harness: TestHarness;
beforeEach(async () => {
harness = await TestHarness.create({
platformConstructor: undefined as any,
platformConfig: {
platform: 'RabbitAir',
name: 'RabbitAir',
devices: [{
name: 'Test Purifier',
host: '192.168.1.100',
token: '0123456789ABCDEF0123456789ABCDEF',
}],
},
});
});
it('should register air purifier service with all characteristics', () => {
const accessory = new RabbitAirAccessory(
harness.platform as any,
harness.homeKit.accessory('test-uuid')!
);
expect(toHaveService(harness.homeKit, 'AirPurifier').pass).toBe(true);
const service = harness.homeKit.service('test-uuid', 'AirPurifier')!;
expect(toHaveCharacteristic(service, 'Active').pass).toBe(true);
expect(toHaveCharacteristic(service, 'CurrentAirPurifierState').pass).toBe(true);
expect(toHaveCharacteristic(service, 'TargetAirPurifierState').pass).toBe(true);
expect(toHaveCharacteristic(service, 'RotationSpeed').pass).toBe(true);
expect(toHaveCharacteristic(service, 'FilterLifeLevel').pass).toBe(true);
});
it('should update characteristics when device state changes', async () => {
const accessory = new RabbitAirAccessory(/* ... */);
// Simulate device state update
await accessory.updateFromDevice({
power: true,
mode: 'auto',
fanSpeed: 'medium',
airQuality: 2,
filterLife: 85,
});
const active = harness.homeKit.characteristic('test-uuid', 'AirPurifier', 'Active')!;
const speed = harness.homeKit.characteristic('test-uuid', 'AirPurifier', 'RotationSpeed')!;
expect(active.value).toBe(1); // Active
expect(speed.value).toBe(50); // Medium = 50%
});
});
```
#### Task 3.3: Test Platform Lifecycle
Create `test/unit/platform.test.ts`:
```typescript
import { describe, it, expect, beforeEach } from 'vitest';
import { TestHarness } from 'hap-test';
import { RabbitAirPlatform } from '../../src/platform';
describe('RabbitAirPlatform', () => {
describe('Configuration', () => {
it('should load platform with valid config', async () => {
const harness = await TestHarness.create({
platformConstructor: RabbitAirPlatform,
platformConfig: {
platform: 'RabbitAir',
name: 'RabbitAir',
devices: [
{ name: 'Purifier 1', host: '192.168.1.100', token: 'A'.repeat(32) },
{ name: 'Purifier 2', host: '192.168.1.101', token: 'B'.repeat(32) },
],
},
});
expect(harness.platform).toBeDefined();
expect(harness.homeKit.accessories()).toHaveLength(2);
});
it('should reject invalid token length', async () => {
await expect(TestHarness.create({
platformConstructor: RabbitAirPlatform,
platformConfig: {
platform: 'RabbitAir',
devices: [
{ name: 'Test', host: '192.168.1.100', token: 'invalid' },
],
},
})).rejects.toThrow('Token must be 32 characters');
});
});
describe('Cached Accessories', () => {
it('should restore accessories from cache', async () => {
const harness = await TestHarness.create({
platformConstructor: RabbitAirPlatform,
platformConfig: { /* ... */ },
});
// Provide cached accessories
const cachedAccessory = /* create mock accessory */;
harness.api.provideCachedAccessories([cachedAccessory]);
await harness.platform.configureAccessory(cachedAccessory);
expect(harness.homeKit.accessory('cached-uuid')).toBeDefined();
});
});
});
```
### Phase 4: Integration Testing (4-6 hours)
#### Task 4.1: Test Complete Platform Lifecycle
Create `test/integration/platform-lifecycle.test.ts`:
```typescript
import { describe, it, expect, beforeEach } from 'vitest';
import { TestHarness } from 'hap-test';
import { RabbitAirPlatform } from '../../src/platform';
describe('RabbitAir Platform Lifecycle', () => {
let harness: TestHarness;
beforeEach(async () => {
harness = await TestHarness.create({
platformConstructor: RabbitAirPlatform,
platformConfig: {
platform: 'RabbitAir',
devices: [{
name: 'Living Room Purifier',
host: '192.168.1.100',
token: '0'.repeat(32),
}],
},
});
});
it('should complete full startup sequence', async () => {
// Verify platform initialized
expect(harness.platform).toBeDefined();
// Verify accessories registered
expect(harness.homeKit.accessories()).toHaveLength(1);
const accessory = harness.homeKit.accessory('living-room-uuid')!;
expect(accessory.displayName).toBe('Living Room Purifier');
// Verify services configured
const purifierService = harness.homeKit.service('living-room-uuid', 'AirPurifier');
expect(purifierService).toBeDefined();
});
it('should handle platform restart with cached accessories', async () => {
const firstAccessories = harness.homeKit.accessories();
// Simulate platform restart
const harness2 = await TestHarness.create({
platformConstructor: RabbitAirPlatform,
platformConfig: { /* same config */ },
});
// Restore cached accessories
harness2.api.provideCachedAccessories(firstAccessories);
// Platform should recognize and configure cached accessories
expect(harness2.homeKit.accessories()).toHaveLength(1);
});
});
```
#### Task 4.2: Test Network Resilience
Create `test/integration/network-resilience.test.ts`:
```typescript
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { TestHarness, NetworkSimulator } from 'hap-test';
import { RabbitAirPlatform } from '../../src/platform';
describe('Network Resilience', () => {
let harness: TestHarness;
let simulator: NetworkSimulator;
beforeEach(async () => {
simulator = new NetworkSimulator();
harness = await TestHarness.create({
platformConstructor: RabbitAirPlatform,
platformConfig: { /* ... */ },
});
harness.homeKit.setNetworkSimulator(simulator);
});
it('should handle network latency gracefully', async () => {
vi.useFakeTimers();
simulator.setLatency(200);
const active = harness.homeKit.characteristic('uuid', 'AirPurifier', 'Active')!;
const setPromise = active.setValue(1);
await vi.advanceTimersByTimeAsync(200);
await setPromise;
expect(active.value).toBe(1);
vi.useRealTimers();
});
it('should retry on packet loss', async () => {
simulator.setPacketLoss(0.5); // 50% packet loss
const active = harness.homeKit.characteristic('uuid', 'AirPurifier', 'Active')!;
// Should eventually succeed despite packet loss
await expect(active.setValue(1)).resolves.not.toThrow();
});
it('should handle device disconnection', async () => {
simulator.disconnect();
const active = harness.homeKit.characteristic('uuid', 'AirPurifier', 'Active')!;
await expect(active.setValue(1)).rejects.toThrow('Network disconnected');
// Reconnect and retry
simulator.reconnect();
await expect(active.setValue(1)).resolves.not.toThrow();
});
});
```
#### Task 4.3: Test Device State Synchronization
Create `test/integration/accessory-updates.test.ts`:
```typescript
import { describe, it, expect, beforeEach } from 'vitest';
import { TestHarness, toHaveValue } from 'hap-test';
describe('RabbitAir Device State Updates', () => {
let harness: TestHarness;
beforeEach(async () => {
harness = await TestHarness.create({
platformConstructor: RabbitAirPlatform,
platformConfig: { /* ... */ },
});
});
it('should sync all characteristics on device state update', async () => {
const accessory = harness.platform.accessories[0];
// Simulate device state update from UDP poll
await accessory.updateFromDevice({
power: true,
mode: 'auto',
fanSpeed: 'high',
airQuality: 1, // Good
filterLife: 75,
});
const active = harness.homeKit.characteristic('uuid', 'AirPurifier', 'Active')!;
const state = harness.homeKit.characteristic('uuid', 'AirPurifier', 'TargetAirPurifierState')!;
const speed = harness.homeKit.characteristic('uuid', 'AirPurifier', 'RotationSpeed')!;
const filter = harness.homeKit.characteristic('uuid', 'AirPurifier', 'FilterLifeLevel')!;
const quality = harness.homeKit.characteristic('uuid', 'AirQualitySensor', 'AirQuality')!;
expect(toHaveValue(active, 1).pass).toBe(true);
expect(toHaveValue(state, 1).pass).toBe(true); // Auto
expect(toHaveValue(speed, 75).pass).toBe(true); // High
expect(toHaveValue(filter, 75).pass).toBe(true);
expect(toHaveValue(quality, 2).pass).toBe(true); // Good
});
it('should trigger filter change indication when life is low', async () => {
const accessory = harness.platform.accessories[0];
await accessory.updateFromDevice({
filterLife: 5, // Low filter life
});
const indication = harness.homeKit.characteristic('uuid', 'AirPurifier', 'FilterChangeIndication')!;
expect(toHaveValue(indication, 1).pass).toBe(true); // Change filter
});
});
```
### Phase 5: E2E Testing & Documentation (3-4 hours)
#### Task 5.1: Create Comprehensive E2E Test
Create `test/e2e/rabbitair-plugin.test.ts`:
```typescript
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { TestHarness, toHaveService, toHaveAccessory } from 'hap-test';
import { RabbitAirPlatform } from '../../src/platform';
describe('RabbitAir Plugin E2E', () => {
it('should handle complete user workflow', async () => {
const harness = await TestHarness.create({
platformConstructor: RabbitAirPlatform,
platformConfig: {
platform: 'RabbitAir',
devices: [{
name: 'Bedroom Purifier',
host: '192.168.1.100',
token: '0'.repeat(32),
}],
},
});
// 1. Verify accessory registered
expect(toHaveAccessory(harness.homeKit, 'bedroom-uuid').pass).toBe(true);
// 2. Turn on purifier
const active = harness.homeKit.characteristic('bedroom-uuid', 'AirPurifier', 'Active')!;
await active.setValue(1);
expect(active.value).toBe(1);
// 3. Set to auto mode
const targetState = harness.homeKit.characteristic('bedroom-uuid', 'AirPurifier', 'TargetAirPurifierState')!;
await targetState.setValue(1); // Auto
expect(targetState.value).toBe(1);
// 4. Adjust fan speed
const speed = harness.homeKit.characteristic('bedroom-uuid', 'AirPurifier', 'RotationSpeed')!;
await speed.setValue(75); // High
expect(speed.value).toBe(75);
// 5. Check air quality
const quality = harness.homeKit.characteristic('bedroom-uuid', 'AirQualitySensor', 'AirQuality')!;
const qualityValue = await quality.getValue();
expect(qualityValue).toBeGreaterThanOrEqual(0);
expect(qualityValue).toBeLessThanOrEqual(4);
// 6. Monitor filter life
const filterLife = harness.homeKit.characteristic('bedroom-uuid', 'AirPurifier', 'FilterLifeLevel')!;
const lifeValue = await filterLife.getValue();
expect(lifeValue).toBeGreaterThanOrEqual(0);
expect(lifeValue).toBeLessThanOrEqual(100);
});
});
```
#### Task 5.2: Update Documentation
Create `TESTING.md` update:
```markdown
# Testing Guide
This plugin uses [hap-test](../hap-test) for comprehensive testing.
## Running Tests
```bash
# Run all tests
pnpm test
# Run unit tests only
pnpm test:unit
# Run integration tests
pnpm test:integration
# Run with coverage
pnpm test:coverage
# Watch mode for development
pnpm test:watch
```
## Test Structure
- `test/unit/`: Unit tests for individual components
- `test/integration/`: Integration tests for component interactions
- `test/e2e/`: End-to-end tests for complete workflows
## Writing Tests
### Testing Accessory Setup
```typescript
import { TestHarness, toHaveService } from 'hap-test';
it('should register services correctly', async () => {
const harness = await TestHarness.create({
platformConstructor: RabbitAirPlatform,
platformConfig: { /* ... */ },
});
expect(toHaveService(harness.homeKit, 'AirPurifier').pass).toBe(true);
});
```
### Testing Network Conditions
```typescript
import { NetworkSimulator } from 'hap-test';
it('should handle network latency', async () => {
const simulator = new NetworkSimulator();
simulator.setLatency(200);
harness.homeKit.setNetworkSimulator(simulator);
// Test characteristic operations with latency
});
```
```
#### Task 5.3: Create Migration Guide
Add to `README.md`:
```markdown
## Development
### Testing Framework
This plugin uses `hap-test` for comprehensive testing with:
- **MockHomeKit**: Simulated HomeKit controller
- **NetworkSimulator**: Network condition testing (latency, packet loss)
- **TimeController**: Deterministic time-based testing
- **Custom Matchers**: Type-safe assertions
See [TESTING.md](./TESTING.md) for detailed testing guide.
### Fluent API
The plugin uses `hap-fluent` for type-safe, fluent HomeKit service configuration:
```typescript
const { airPurifier } = handler
.service('AirPurifier', 'Air Purifier')
.characteristic('active')
.onGet(async () => this.getActive())
.onSet(async (value) => this.setActive(value))
.parent
.build();
```
```
## Testing Strategy
### Coverage Targets
- **Unit Tests:** 90%+ line coverage
- **Integration Tests:** All critical paths covered
- **E2E Tests:** Complete user workflows validated
### Key Test Scenarios
1. **Configuration:**
- Valid/invalid token handling
- Multi-device setup
- Missing required fields
2. **Service Setup:**
- All characteristics registered
- Type-safe handlers configured
- Interceptors applied correctly
3. **State Synchronization:**
- Device → HomeKit updates
- HomeKit → Device commands
- Polling and event-driven updates
4. **Network Resilience:**
- Latency handling
- Packet loss retry logic
- Connection failures and recovery
- UDP socket management
5. **Edge Cases:**
- Invalid device responses
- Token expiration
- Concurrent characteristic updates
- Platform restart scenarios
## Success Criteria
- [ ] All existing Mocha tests passing
- [ ] New Vitest tests achieving 90%+ coverage
- [ ] hap-fluent refactor complete with type safety
- [ ] Network resilience tests validate retry logic
- [ ] E2E test validates complete user workflow
- [ ] Documentation updated (README, TESTING.md)
- [ ] CI/CD pipeline updated with new test commands
- [ ] No breaking changes to plugin behavior
## Migration Path
### Week 1: Setup & Initial Tests
- Install dependencies
- Configure Vitest
- Write first unit tests (RabbitAirClient)
- Keep Mocha tests running
### Week 2: Refactor with hap-fluent
- Refactor RabbitAirAccessory
- Refactor RabbitAirPlatform
- Add accessory unit tests
- Verify backward compatibility
### Week 3: Integration & E2E
- Write integration tests
- Write E2E tests
- Achieve coverage targets
- Update documentation
### Week 4: Migration & Cleanup
- Migrate remaining Mocha tests to Vitest
- Remove Mocha dependencies
- Final documentation pass
- Release
## Risks & Mitigations
| Risk | Impact | Mitigation |
|------|--------|------------|
| Breaking changes from refactor | High | Comprehensive test suite, gradual migration |
| hap-fluent API learning curve | Medium | Example code in tests, pair programming |
| Test flakiness with network simulation | Medium | Use fake timers, deterministic scenarios |
| Coverage gaps in existing code | Low | Prioritize critical paths first |
## Alternatives Considered
1. **Keep Mocha, add minimal hap-test:**
- Pro: Less churn
- Con: Miss benefits of unified testing approach
2. **Full rewrite with hap-fluent:**
- Pro: Cleanest result
- Con: Higher risk, longer timeline
3. **Gradual migration (chosen):**
- Pro: Balanced risk, iterative progress
- Con: Temporary dual-framework maintenance
## References
- [hap-fluent README](../hap-fluent/README.md)
- [hap-test README](../hap-test/README.md)
- [Homebridge Plugin Development](https://developers.homebridge.io/)
- [RabbitAir Protocol (python-rabbitair)](https://github.com/rabbit-air/python-rabbitair)