E2E Testing Guide: Personal Edition Features
**Purpose**: Comprehensive guide for end-to-end testing of personal edition features (media control, creative tools, smart home automation)
**Audience**: QA engineers, developers, technical contributors
**Coverage Target**: 85%+ across all personal edition modules
**Last Updated**: 2026-02-22
---
Table of Contents
- Overview
- Testing Philosophy
- Media Control Testing
- Creative Tools Testing
- Smart Home Testing
- Test Infrastructure
- Best Practices
- Coverage Report Template
- Troubleshooting
---
1. Overview
E2E Testing Philosophy for Personal Edition
Personal edition E2E testing validates **complete user workflows** across multiple integrations, simulating real-world usage patterns. Unlike unit tests (which test individual functions) or integration tests (which test module interactions), E2E tests verify **entire feature flows** from user action to system response.
Key Principles
- **User-Centric**: Test from user perspective, not implementation
- **Workflow-Based**: Validate complete scenarios, not isolated features
- **Cross-Platform**: Test integration between different services (e.g., "play focus music + dim lights")
- **Virtual Devices**: Use mocks instead of real hardware for speed and reliability
- **Coverage-Driven**: Aim for 85%+ coverage with meaningful tests
Coverage Goals
| Module Type | Coverage Target | Priority |
|---|---|---|
| Media Integration (OAuth, playback) | 90% | P0 (Critical) |
| Creative Tools (Canva, Adobe, Figma) | 85% | P1 (High) |
| Smart Home Hubs (SmartThings, Hue, Nest) | 90% | P0 (Critical) |
| Automation Engine (TAP pattern) | 85% | P1 (High) |
| Voice Dispatcher (NLP parsing) | 80% | P2 (Standard) |
| Energy Monitor (usage tracking) | 80% | P2 (Standard) |
Test Execution Patterns
# Run all personal edition tests
npm test -- src/lib/integrations/spotify/ src/lib/integrations/applemusic/ src/lib/creative-tools/ src/lib/smarthome/
# Run coverage report
npm run test:coverage -- src/lib/integrations/ src/lib/creative-tools/ src/lib/smarthome/
# Run specific module
npm test -- src/lib/smarthome/__tests__/automation-engine.test.ts
# Run with verbose output
npm test -- --reporter=verbose src/lib/media/---
2. Testing Philosophy
Virtual Device Mocking Strategy
**Why Mock Instead of Real Hardware?**
- **Speed**: Tests run in <500ms vs. minutes with real devices
- **Reliability**: No network failures, device offline issues
- **Determinism**: Same test data every run
- **Cost**: No need to purchase 20+ smart home devices
- **CI/CD**: Can run in automated pipelines
**Virtual Device Mocks (from Phase 63C-01)**:
MockHueBridge(853 lines) - Simulates Hue Bridge APIMockSmartThingsHub(404 lines) - Simulates SmartThings HubMockNestThermostat(573 lines) - Simulates Nest SDM APIMockHomeKitBridge(519 lines) - Simulates Home Assistant WebSocketVirtualDeviceFactory(420 lines) - Creates test devices on demand
**Location**: src/lib/smarthome/__tests__/mocks/virtual-devices.ts
Test Pyramid for Personal Edition
E2E Tests (15%)
/ \
/ \
Integration (35%) Unit Tests (50%)
/ \
OAuth flows Individual
Hub communication functions**E2E Test Focus**:
- Cross-platform workflows (media + smart home)
- Complete automation scenarios (TAP pattern with multiple devices)
- Voice command NLP variations
- Multi-step creative workflows (design → export → social media)
Test Isolation Strategies
- **Database Isolation**:
afterAll(async () => {
await db.query('ROLLBACK');
});
```
- **API Mocking**:
- **Time Mocking**:
---
3. Media Control Testing
OAuth Flow Testing (Spotify, Apple Music)
Test Pattern: OAuth Authorization Code Flow
describe('Spotify OAuth Flow', () => {
it('should complete full OAuth authorization', async () => {
// Step 1: Redirect to Spotify
const authUrl = await SpotifyClient.getAuthUrl(tenantId, {
scopes: ['user-read-playback', 'user-library-read'],
state: 'random-state-123'
});
expect(authUrl).toMatch(/^https:\/\/accounts\.spotify\.com\/authorize/);
expect(authUrl).toContain('client_id=');
// Step 2: Handle callback with code
const callbackCode = 'auth-code-456';
const mockTokenResponse = {
access_token: 'token-789',
refresh_token: 'refresh-101',
expires_in: 3600
};
// Mock Spotify token endpoint
vi.spyOn(axios, 'post').mockResolvedValue({ data: mockTokenResponse });
const tokens = await SpotifyClient.handleCallback(tenantId, callbackCode, 'random-state-123');
expect(tokens.access_token).toBe('token-789');
expect(tokens.refresh_token).toBe('refresh-101');
// Step 3: Verify tokens stored in database
const storedTokens = await db.query(
'SELECT * FROM tenant_settings WHERE tenant_id = $1 AND key = $2',
[tenantId, 'spotify_tokens']
);
expect(storedTokens.rows.length).toBe(1);
expect(JSON.parse(storedTokens.rows[0].value).access_token).toBe('token-789');
});
});Test: Token Refresh
it('should refresh access token before expiry', async () => {
// Set up expired token
await db.query(`
INSERT INTO tenant_settings (tenant_id, key, value)
VALUES ($1, 'spotify_tokens', $2)
`, [tenantId, JSON.stringify({
access_token: 'old-token',
refresh_token: 'refresh-token',
expires_at: new Date(Date.now() - 1000).toISOString() // Expired
})]);
// Mock refresh endpoint
vi.spyOn(axios, 'post').mockResolvedValue({
data: {
access_token: 'new-token',
expires_in: 3600
}
});
const client = new SpotifyClient(tenantId);
await client.ensureValidToken();
// Verify new token in database
const result = await db.query(
"SELECT value->>'access_token' FROM tenant_settings WHERE tenant_id = $1 AND key = 'spotify_tokens'",
[tenantId]
);
expect(result.rows[0].access_token).toBe('new-token');
});Playback Control Workflows
Test: Basic Playback
it('should play, pause, and skip track', async () => {
const client = new SpotifyClient(tenantId);
// Mock Spotify API
vi.spyOn(axios, 'put').mockResolvedValue({ status: 204 });
// Play
await client.play('device-123');
expect(axios.put).toHaveBeenCalledWith(
'https://api.spotify.com/v1/me/player/play',
{ device_ids: ['device-123'] }
);
// Pause
await client.pause();
expect(axios.put).toHaveBeenCalledWith('https://api.spotify.com/v1/me/player/pause');
// Skip
await client.skipToNext();
expect(axios.post).toHaveBeenCalledWith('https://api.spotify.com/v1/me/player/next');
});Test: Cross-Platform Workflow (E2E)
it('should play focus music and dim lights', async () => {
// This test integrates media control + smart home
// Step 1: Play focus playlist on Spotify
const spotify = new SpotifyClient(tenantId);
await spotify.setShuffle(true);
await spotify.play('device-123', {
context_uri: 'spotify:playlist:37i9dQZF1DXcBJigayD7B' // Focus playlist
});
// Step 2: Dim lights via Hue Bridge
const hue = new HueBridgeClient(tenantId, {
bridgeId: 'hue-bridge-123',
apiKey: 'test-api-key'
});
await hue.setLightState('light-1', {
on: true,
brightness: 20, // Dim to 20%
color: { x: 0.5, y: 0.3 } // Warm orange
});
await hue.setLightState('light-2', {
on: true,
brightness: 20,
color: { x: 0.5, y: 0.3 }
});
// Step 3: Verify state
const playbackState = await spotify.getPlaybackState();
expect(playbackState.is_playing).toBe(true);
const light1State = await hue.getLightState('light-1');
expect(light1State.brightness).toBe(20);
});Playlist Synchronization Testing
Test: Sync Playlist with Cache
it('should sync playlist from Spotify and cache locally', async () => {
// Mock Spotify API response
vi.spyOn(axios, 'get').mockResolvedValue({
data: {
items: [
{ track: { id: 'track-1', name: 'Song 1', uri: 'spotify:track:1' } },
{ track: { id: 'track-2', name: 'Song 2', uri: 'spotify:track:2' } },
{ track: { id: 'track-3', name: 'Song 3', uri: 'spotify:track:3' } }
]
}
});
const playlistService = new PlaylistService(db, tenantId);
await playlistService.syncPlaylist('playlist-123');
// Verify tracks in database
const tracks = await db.query(
`SELECT * FROM media_playlists
WHERE tenant_id = $1 AND playlist_id = $2
ORDER BY position`,
[tenantId, 'playlist-123']
);
expect(tracks.rows.length).toBe(3);
expect(tracks.rows[0].track_name).toBe('Song 1');
// Verify cache (should not call API again)
await playlistService.syncPlaylist('playlist-123');
expect(axios.get).toHaveBeenCalledTimes(1); // Called once, cached
});Mock Patterns for Provider APIs
Spotify API Mock
// src/lib/integrations/spotify/__tests__/mocks.ts
export const mockSpotifyApi = () => {
vi.mock('spotify-web-api-node', () => ({
Client: vi.fn().mockImplementation(() => ({
getMe: vi.fn().mockResolvedValue({ id: 'user-123' }),
getPlayer: vi.fn().mockResolvedValue({
device: { id: 'device-123', name: 'Living Room' },
is_playing: true,
item: { name: 'Test Track' }
}),
play: vi.fn().mockResolvedValue({ success: true }),
pause: vi.fn().mockResolvedValue({ success: true })
}))
}));
};Apple Music API Mock
export const mockAppleMusicApi = () => {
vi.mock('music-api-kit', () => ({
MusicKit: vi.fn().mockImplementation(() => ({
authorize: vi.fn().mockResolvedValue(true),
setAuthToken: vi.fn(),
api: {
library: {
playlists: vi.fn().mockResolvedValue({
data: [{ id: 'pl-1', attributes: { name: 'Test Playlist' } }]
})
}
}
}))
}));
};---
4. Creative Tools Testing
Design Tool OAuth Testing (Canva, Figma, Adobe)
Test: Canva OAuth 2.0 Flow
describe('Canva OAuth Integration', () => {
it('should authorize user and access designs', async () => {
const client = new CanvaClient(tenantId);
// Mock Canva OAuth endpoint
vi.spyOn(axios, 'get').mockResolvedValueOnce({
data: { authorization_url: 'https://www.canva.com/oauth' }
});
const authUrl = await client.getAuthUrl();
expect(authUrl).toContain('response_type=code');
expect(authUrl).toContain('client_id=');
// Mock callback handling
vi.spyOn(axios, 'post').mockResolvedValue({
data: {
access_token: 'canva-token-123',
refresh_token: 'canva-refresh-456',
expires_in: 7200
}
});
await client.handleCallback('auth-code-789');
// Verify tokens stored
const tokens = await client.getTokens();
expect(tokens.access_token).toBe('canva-token-123');
});
});Test: Figma File Operations
it('should read and update Figma design', async () => {
const client = new FigmaClient(tenantId, { token: 'figma-token' });
// Mock Figma API
vi.spyOn(axios, 'get').mockResolvedValue({
data: {
document: {
id: 'file-123',
name: 'Test Design',
children: [{
type: 'CANVAS',
name: 'Page 1',
children: [
{ type: 'RECTANGLE', id: 'rect-1', width: 100, height: 100 }
]
}]
}
}
});
const file = await client.getFile('file-123');
expect(file.name).toBe('Test Design');
expect(file.document.children[0].children[0].type).toBe('RECTANGLE');
// Update design
vi.spyOn(axios, 'post').mockResolvedValue({ status: 200 });
await client.updateFile('file-123', {
updates: [{
node_id: 'rect-1',
relativeTransform: { x: 50, y: 50 }
}]
});
expect(axios.post).toHaveBeenCalledWith(
'https://api.figma.com/v1/files/file-123',
expect.objectContaining({ updates: expect.any(Array) })
);
});Photo Editing Validation (Sharp Operations)
Test: Image Processing Pipeline
describe('PhotoEditorService', () => {
it('should crop, resize, and apply filters', async () => {
const editor = new PhotoEditorService();
// Create test image buffer
const testImage = Buffer.from('fake-image-data');
// Mock Sharp operations
const mockResize = vi.fn().mockReturnThis();
const mockCrop = vi.fn().mockReturnThis();
const mockToFormat = vi.fn().mockResolvedValue(Buffer.from('processed-image'));
vi.doMock('sharp', () => ({
default: vi.fn(() => ({
resize: mockResize,
crop: mockCrop,
toFormat: mockToFormat
}))
});
// Execute pipeline
const result = await editor.processImage(testImage, {
crop: { left: 10, top: 10, width: 100, height: 100 },
resize: { width: 800, height: 600 },
filter: 'grayscale',
format: 'jpeg'
});
// Verify operations called in order
expect(mockCrop).toHaveBeenCalledWith({
left: 10, top: 10, width: 100, height: 100
});
expect(mockResize).toHaveBeenCalledWith(800, 600);
expect(mockToFormat).toHaveBeenCalledWith('jpeg');
});
});AI Suggestions Testing (LLM Mocking)
Test: Color Scheme Generation
it('should generate color scheme using LLM', async () => {
const service = new CreativeSuggestionsService(db, llmRouter);
// Mock LLM response
vi.spyOn(llmRouter, 'call').mockResolvedValue({
choices: [{
message: {
content: JSON.stringify({
colors: [
{ hex: '#FF5733', name: 'Vibrant Red' },
{ hex: '#33FF57', name: 'Fresh Green' },
{ hex: '#3357FF', name: 'Calm Blue' }
]
})
}
}]
});
const suggestions = await service.generateColorScheme('modern', 'tech');
expect(suggestions.colors).toHaveLength(3);
expect(suggestions.colors[0].hex).toBe('#FF5733');
expect(llmRouter.call).toHaveBeenCalledWith(
expect.stringContaining('color scheme'),
expect.any(Object)
);
});Evernote OAuth 1.0a Signature Validation
Test: HMAC-SHA1 Signature
it('should generate valid OAuth 1.0a signature', async () => {
const client = new EvernoteClient(tenantId, {
consumerKey: 'test-key',
consumerSecret: 'test-secret'
});
const url = 'https://sandbox.evernote.com/edam/user';
const params = {
oauth_consumer_key: 'test-key',
oauth_token: 'token-123',
oauth_timestamp: '1234567890',
oauth_nonce: 'random-nonce'
};
const signature = await client.generateSignature('GET', url, params);
// Verify signature format (HMAC-SHA1)
expect(signature).toMatch(/^[a-f0-9]{40}$/);
// Verify signature matches expected value
const expectedSignature = crypto
.createHmac('sha1', 'test-secret&token-secret')
.update('GET&' + encodeURIComponent(url) + '&' + encodeURIComponent(sortParams(params)))
.digest('hex');
expect(signature).toBe(expectedSignature);
});Cross-Tool Export Workflows
Test: Canva → Figma → Export
it('should export design from Canva to Figma format', async () => {
// Step 1: Export from Canva
const canva = new CanvaClient(tenantId, { token: 'canva-token' });
vi.spyOn(axios, 'get').mockResolvedValue({
data: { export_url: 'https://canva.com/export/design-123.pdf' }
});
const exportUrl = await canva.exportDesign('design-123');
// Step 2: Convert to Figma format
const figma = new FigmaClient(tenantId, { token: 'figma-token' });
vi.spyOn(axios, 'post').mockResolvedValue({
data: { file: { id: 'figma-456', key: 'converted-design' } }
});
const converted = await figma.importFromCanva(exportUrl);
expect(converted.file.id).toBe('figma-456');
});---
5. Smart Home Testing
Hub Client Testing Patterns
Test: SmartThings Hub Discovery
describe('SmartThings Hub', () => {
it('should discover hub and authenticate via OAuth', async () => {
const client = new SmartThingsClient(tenantId);
// Mock OAuth flow
vi.spyOn(axios, 'post').mockResolvedValueOnce({
data: {
access_token: 'st-token-123',
refresh_token: 'st-refresh-456'
}
});
await client.authenticate('auth-code-789');
// Mock device discovery
vi.spyOn(axios, 'get').mockResolvedValue({
data: {
items: [
{ deviceId: 'switch-1', name: 'Living Room Light', type: 'switch' },
{ deviceId: 'switch-2', name: 'Kitchen Light', type: 'switch' }
]
}
});
const devices = await client.getDevices();
expect(devices).toHaveLength(2);
expect(devices[0].name).toBe('Living Room Light');
});
});Test: Hue Bridge mDNS Discovery
it('should discover Hue Bridge via mDNS', async () => {
const discovery = new HueDiscoveryService();
// Mock mDNS browser
vi.mock('mdns', () => ({
createBrowser: vi.fn().mockImplementation(() => ({
on: vi.fn((event, listener) => {
if (event === 'ready') {
listener({
addresses: ['192.168.1.100'],
name: 'Philips hue bridge',
port: 80
});
}
})
}))
}));
const bridges = await discovery.discover();
expect(bridges).toHaveLength(1);
expect(bridges[0].ip).toBe('192.168.1.100');
});Automation Workflow Testing (TAP Pattern)
Test: Multi-Device Scene
describe('AutomationEngine', () => {
it('should execute "Movie Night" scene', async () => {
const engine = new AutomationEngine(tenantId, db);
// Create rule with multiple actions
const rule = await engine.createRule({
name: 'Movie Night',
trigger: {
type: 'voice',
pattern: 'movie mode'
},
conditions: [],
actions: [
{ deviceId: 'light-1', action: 'set_brightness', params: { level: 20 } },
{ deviceId: 'light-2', action: 'set_brightness', params: { level: 20 } },
{ deviceId: 'light-3', action: 'set_color', params: { x: 0.5, y: 0.3 } },
{ deviceId: 'thermostat-1', action: 'set_temperature', params: { temp: 68 } }
]
});
// Trigger rule
vi.spyOn(engine, 'executeActions').mockResolvedValue(true);
await engine.trigger('voice', { text: 'movie mode' });
// Verify all actions executed
expect(engine.executeActions).toHaveBeenCalledWith([
{ deviceId: 'light-1', action: 'set_brightness', params: { level: 20 } },
{ deviceId: 'light-2', action: 'set_brightness', params: { level: 20 } },
{ deviceId: 'light-3', action: 'set_color', params: { x: 0.5, y: 0.3 } },
{ deviceId: 'thermostat-1', action: 'set_temperature', params: { temp: 68 } }
]);
});
});Voice Command NLP Testing
Test: Natural Language Variations
describe('VoiceDispatcher', () => {
it('should handle 30+ command variations', async () => {
const dispatcher = new VoiceDispatcher(tenantId);
const variations = [
'turn on the lights',
'lights on',
'switch on lights',
'enable lights',
'activate lights',
'power on lights'
];
for (const command of variations) {
const parsed = dispatcher.parse(command);
expect(parsed.action).toBe('on');
expect(parsed.target).toContain('light');
}
});
it('should handle multi-step commands', async () => {
const result = dispatcher.parse('turn on lights and set to blue');
expect(result.actions).toHaveLength(2);
expect(result.actions[0].action).toBe('on');
expect(result.actions[1].action).toBe('set_color');
expect(result.actions[1].params.color).toMatch(/blue/i);
});
});---
6. Test Infrastructure
Virtual Device Mock Reference
**Location**: src/lib/smarthome/__tests__/mocks/virtual-devices.ts
Usage Example
import { MockHueBridge, VirtualDeviceFactory } from './mocks/virtual-devices';
// Create mock Hue Bridge
const mockBridge = new MockHueBridge({
bridgeId: 'hue-test-bridge',
ipAddress: '192.168.1.100',
apiKey: 'test-api-key'
});
// Add virtual lights
mockBridge.addLight({
id: 'light-1',
name: 'Living Room Light',
state: { on: false, brightness: 0 }
});
mockBridge.addLight({
id: 'light-2',
name: 'Kitchen Light',
state: { on: false, brightness: 0 }
});
// Use in test
const client = new HueBridgeClient(tenantId, { bridgeId: 'hue-test-bridge' });
await client.setLightState('light-1', { on: true, brightness: 50 });
// Verify state
const state = await client.getLightState('light-1');
expect(state.on).toBe(true);
expect(state.brightness).toBe(50);Virtual Device Factory
const factory = new VirtualDeviceFactory();
// Create test scene
const livingRoom = factory.createScene('living-room', {
lights: ['light-1', 'light-2'],
thermostat: 'thermostat-1',
temperature: 70
});
// Verify scene state
expect(livingRoom.getDevice('light-1').state.on).toBe(false);
livingRoom.activate('movie-mode');
expect(livingRoom.getDevice('light-1').state.brightness).toBe(20);Test Setup Utilities
**Location**: src/lib/smarthome/__tests__/test-setup.ts
Database Helpers
import { seedTestDevices, seedTestScenes } from './test-setup';
describe('Smart Home Integration', () => {
let tenantId: string;
beforeEach(async () => {
// Create test tenant
tenantId = await createTestTenant();
// Seed test devices
await seedTestDevices(tenantId, [
{ id: 'light-1', name: 'Living Room Light', type: 'light', hub: 'hue' },
{ id: 'thermostat-1', name: 'Nest', type: 'thermostat', hub: 'nest' }
]);
});
afterEach(async () => {
// Cleanup test data
await db.query('DELETE FROM smarthome_devices WHERE tenant_id = $1', [tenantId]);
});
});API Response Mocks
**Location**: src/lib/smarthome/__tests__/mocks/api-responses.ts
Usage Example
import { mockOAuthTokens, mockDeviceDiscoveryResponse } from './mocks/api-responses';
describe('SmartThings Client', () => {
beforeEach(() => {
// Mock OAuth token response
mockOAuthTokens({
access_token: 'test-token',
refresh_token: 'test-refresh',
expires_in: 3600
});
// Mock device discovery
mockDeviceDiscoveryResponse([
{ deviceId: 'switch-1', name: 'Test Switch' }
]);
});
});---
7. Best Practices
Mock vs. Real Service Usage
**When to Use Mocks**:
- ✅ Unit tests (fast, deterministic)
- ✅ CI/CD pipelines (no external dependencies)
- ✅ Feature development (before integration)
- ✅ Edge case testing (error scenarios)
**When to Use Real Services**:
- ✅ Integration verification (before release)
- ✅ API contract validation (after provider updates)
- ✅ Performance testing (realistic load)
- ❌ Daily development (too slow, unreliable)
Test Isolation Strategies
- **Database Transactions**:
afterEach(async () => {
await db.query('ROLLBACK');
});
```
- **Time Isolation**:
- **Environment Isolation**:
Rate Limiting Validation
it('should enforce 150 req/min rate limit for SmartThings', async () => {
const client = new SmartThingsClient(tenantId);
// Mock API to track requests
const requestCount = { count: 0 };
vi.spyOn(axios, 'get').mockImplementation(() => {
requestCount.count++;
if (requestCount.count > 150) {
throw new Error('Rate limit exceeded');
}
return { data: {} };
});
// Make 151 requests
for (let i = 0; i < 151; i++) {
try {
await client.getDevices();
} catch (e) {
expect(e.message).toBe('Rate limit exceeded');
expect(i).toBe(150); // Should fail on 151st request
}
}
});Error Scenario Coverage
it('should handle OAuth token expiry gracefully', async () => {
const client = new SpotifyClient(tenantId);
// Mock expired token response
vi.spyOn(axios, 'get').mockRejectedValueOnce({
response: { status: 401 },
config: { url: 'https://api.spotify.com/v1/me' }
});
// Mock token refresh
vi.spyOn(axios, 'post').mockResolvedValue({
data: { access_token: 'new-token', expires_in: 3600 }
});
// Should automatically refresh and retry
const user = await client.getCurrentUser();
expect(user).toBeDefined();
expect(axios.post).toHaveBeenCalledWith(
'https://accounts.spotify.com/api/token',
expect.objectContaining({ grant_type: 'refresh_token' })
);
});Performance Benchmarking (<500ms Target)
it('should execute automation rule in <500ms', async () => {
const engine = new AutomationEngine(tenantId);
const startTime = Date.now();
await engine.executeRule('rule-123', {
trigger: { type: 'state', deviceId: 'sensor-1', value: 'motion' },
actions: [
{ deviceId: 'light-1', action: 'on' },
{ deviceId: 'light-2', action: 'on' }
]
});
const duration = Date.now() - startTime;
expect(duration).toBeLessThan(500);
});---
8. Coverage Report Template
Module-by-Module Breakdown
## Coverage Summary
**Overall Coverage**: XX%
**Target**: 85%
**Gap**: XX%
### Media Integration
| Module | Coverage | Tests | Gap |
|--------|----------|-------|-----|
| SpotifyClient | XX% | 50 | XX% |
| AppleMusicClient | XX% | 50 | XX% |
| PlaybackService | XX% | 27 | XX% |
| PlaylistService | XX% | 21 | XX% |
| RecommendationService | XX% | 23 | XX% |
**Gap Analysis**: [describe what's missing]
### Creative Tools
[Same table format]
### Smart Home
[Same table format]Gap Identification
## Immediate Gaps (Below 85%)
1. **SmartThingsClient** (Current: 40%, Target: 85%, Gap: 45%)
- Missing: Pagination, offline device handling, batch commands
- Priority: HIGH
- Estimated Effort: 4-6 hours
2. **HueBridgeClient** (Current: 35%, Target: 85%, Gap: 50%)
- Missing: Group operations, scene management, discovery edge cases
- Priority: HIGH
- Estimated Effort: 4-6 hoursImprovement Tracking
## Coverage Trends
| Date | Overall | Media | Creative | Smart Home |
|------|---------|-------|----------|------------|
| 2026-02-20 | 35% | 45% | 40% | 30% |
| 2026-02-22 | 45% | 85% | 50% | 35% |
| 2026-02-25 (Target) | 85% | 90% | 85% | 85% |---
9. Troubleshooting
Common Issues
1. Mock Constructor Issues
**Problem**: DeviceRegistry is not a constructor
**Solution**: Use class constructor mocks, not getInstance pattern:
// ❌ Wrong (singleton pattern)
vi.mock('../device-registry', () => ({
DeviceRegistry: {
getInstance: () => ({ ... })
}
}));
// ✅ Correct (class constructor)
vi.mock('../device-registry', () => {
class MockDeviceRegistry {
getDevices = vi.fn(() => [...]);
}
return { DeviceRegistry: MockDeviceRegistry };
});2. Missing Mock Methods
**Problem**: this.deviceRegistry.searchDevices is not a function
**Solution**: Implement all interface methods in mocks:
class MockDeviceRegistry {
getDevices = vi.fn(() => [...]);
searchDevices = vi.fn((query) => [...]); // Add missing method
getDevice = vi.fn((id) => devices.find(d => d.id === id));
updateDeviceState = vi.fn();
// ... etc
}3. Database Coupling
**Problem**: Tests expect real database connection
**Solution**: Mock database at module level:
vi.mock('../../lib/database', () => ({
getDatabase: () => ({
query: vi.fn(() => ({ rows: [], rowCount: 0 })),
connect: vi.fn(),
end: vi.fn()
})
}));4. Async Timeout Issues
**Problem**: Tests timeout waiting for promises
**Solution**: Use fake timers or increase timeout:
it('should handle long-running operation', async () => {
vi.useFakeTimers();
const promise = someAsyncOperation();
vi.advanceTimersByTime(5000); // Advance 5 seconds
await promise; // Now resolves immediately
}, 10000); // 10 second timeoutTest Execution Time
**Current Performance**:
- Media tests: ~10 seconds (50 tests)
- Creative tools: ~12 seconds (120 tests)
- Smart home: ~15 seconds (127 tests, 121 failing)
- **Total**: ~37 seconds (target: <60 seconds)
**Optimization Tips**:
- Use
vi.mock()for heavy dependencies - Avoid real network calls
- Use fake timers for schedule testing
- Parallel test execution (vitest --threads)
- Disable coverage reports during development
---
Conclusion
This guide provides comprehensive patterns and examples for E2E testing of personal edition features. Follow the testing philosophy, use virtual device mocks, and maintain 85%+ coverage target to ensure production readiness.
**Key Resources**:
- Test Infrastructure:
src/lib/smarthome/__tests__/mocks/ - Coverage Reports:
coverage/index.html - API Documentation:
docs/MEDIA_INTEGRATION.md,docs/smarthome/*.md
**Next Steps**:
- Fix mock infrastructure issues (documented in 63D-01-03-SUMMARY.md)
- Achieve 85% coverage target
- Add E2E workflow tests for cross-platform scenarios
- Document coverage gaps and improvement roadmap