- Add missing addEventListener/removeEventListener mocks in BLEContext cleanup tests - Fix scanning simulation by calling scanDevices() instead of trying to set state directly - Add explicit timeouts to bulkSetWiFi tests (10-15s) to accommodate MockBLEManager delays - Update concurrent connection error message check to match actual error text All 42 BLE tests now pass (36 bulk/concurrent + 6 cleanup)
335 lines
10 KiB
TypeScript
335 lines
10 KiB
TypeScript
/**
|
|
* BLE Manager Bulk Operations Unit Tests
|
|
* Tests for bulkDisconnect, bulkReboot, and bulkSetWiFi
|
|
*/
|
|
|
|
import { MockBLEManager } from '../MockBLEManager';
|
|
import { BLEConnectionState } from '../types';
|
|
|
|
describe('BLEManager Bulk Operations', () => {
|
|
let manager: MockBLEManager;
|
|
|
|
beforeEach(() => {
|
|
manager = new MockBLEManager();
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await manager.cleanup();
|
|
});
|
|
|
|
describe('bulkDisconnect', () => {
|
|
it('should disconnect multiple devices', async () => {
|
|
// Connect devices first
|
|
await manager.connectDevice('mock-743');
|
|
await manager.connectDevice('mock-769');
|
|
|
|
expect(manager.isDeviceConnected('mock-743')).toBe(true);
|
|
expect(manager.isDeviceConnected('mock-769')).toBe(true);
|
|
|
|
// Bulk disconnect
|
|
const results = await manager.bulkDisconnect(['mock-743', 'mock-769']);
|
|
|
|
expect(results).toHaveLength(2);
|
|
expect(results[0].success).toBe(true);
|
|
expect(results[1].success).toBe(true);
|
|
expect(manager.isDeviceConnected('mock-743')).toBe(false);
|
|
expect(manager.isDeviceConnected('mock-769')).toBe(false);
|
|
});
|
|
|
|
it('should return results with device info', async () => {
|
|
await manager.connectDevice('mock-743');
|
|
|
|
const results = await manager.bulkDisconnect(['mock-743']);
|
|
|
|
expect(results[0]).toEqual({
|
|
deviceId: 'mock-743',
|
|
deviceName: 'WP_497_81a14c',
|
|
success: true,
|
|
});
|
|
});
|
|
|
|
it('should handle empty device list', async () => {
|
|
const results = await manager.bulkDisconnect([]);
|
|
|
|
expect(results).toEqual([]);
|
|
});
|
|
|
|
it('should handle non-connected devices gracefully', async () => {
|
|
const results = await manager.bulkDisconnect(['non-existent-device']);
|
|
|
|
expect(results).toHaveLength(1);
|
|
expect(results[0].success).toBe(true); // Mock doesn't throw for non-connected
|
|
expect(results[0].deviceId).toBe('non-existent-device');
|
|
});
|
|
|
|
it('should continue processing after individual failure', async () => {
|
|
await manager.connectDevice('mock-743');
|
|
await manager.connectDevice('mock-769');
|
|
|
|
// Disconnect all
|
|
const results = await manager.bulkDisconnect(['mock-743', 'mock-769']);
|
|
|
|
// All should have been processed
|
|
expect(results).toHaveLength(2);
|
|
});
|
|
});
|
|
|
|
describe('bulkReboot', () => {
|
|
it('should reboot multiple devices', async () => {
|
|
await manager.connectDevice('mock-743');
|
|
await manager.connectDevice('mock-769');
|
|
|
|
const results = await manager.bulkReboot(['mock-743', 'mock-769']);
|
|
|
|
expect(results).toHaveLength(2);
|
|
expect(results[0].success).toBe(true);
|
|
expect(results[1].success).toBe(true);
|
|
});
|
|
|
|
it('should return results with device names', async () => {
|
|
await manager.connectDevice('mock-743');
|
|
|
|
const results = await manager.bulkReboot(['mock-743']);
|
|
|
|
expect(results[0].deviceId).toBe('mock-743');
|
|
expect(results[0].deviceName).toBe('WP_497_81a14c');
|
|
expect(results[0].success).toBe(true);
|
|
});
|
|
|
|
it('should disconnect devices after reboot', async () => {
|
|
await manager.connectDevice('mock-743');
|
|
expect(manager.isDeviceConnected('mock-743')).toBe(true);
|
|
|
|
await manager.bulkReboot(['mock-743']);
|
|
|
|
expect(manager.isDeviceConnected('mock-743')).toBe(false);
|
|
});
|
|
|
|
it('should handle empty device list', async () => {
|
|
const results = await manager.bulkReboot([]);
|
|
|
|
expect(results).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe('bulkSetWiFi', () => {
|
|
const devices = [
|
|
{ id: 'mock-743', name: 'WP_497_81a14c' },
|
|
{ id: 'mock-769', name: 'WP_523_81aad4' },
|
|
];
|
|
const ssid = 'TestNetwork';
|
|
const password = 'testpass123';
|
|
|
|
// BulkSetWiFi has delays: connect (800ms) + setWiFi (1200ms) + reboot (600ms) = ~2.6s per device
|
|
// For 2 devices: ~5.2s, so we need 15s timeout to be safe
|
|
jest.setTimeout(15000);
|
|
|
|
it('should configure WiFi on multiple devices', async () => {
|
|
const results = await manager.bulkSetWiFi(devices, ssid, password);
|
|
|
|
expect(results).toHaveLength(2);
|
|
expect(results[0].success).toBe(true);
|
|
expect(results[1].success).toBe(true);
|
|
}, 15000);
|
|
|
|
it('should return detailed results', async () => {
|
|
const results = await manager.bulkSetWiFi(devices.slice(0, 1), ssid, password);
|
|
|
|
expect(results[0]).toEqual({
|
|
deviceId: 'mock-743',
|
|
deviceName: 'WP_497_81a14c',
|
|
success: true,
|
|
});
|
|
}, 10000);
|
|
|
|
it('should call progress callback for each stage', async () => {
|
|
const progressCalls: Array<{
|
|
deviceId: string;
|
|
status: string;
|
|
error?: string;
|
|
}> = [];
|
|
|
|
const onProgress = (
|
|
deviceId: string,
|
|
status: 'connecting' | 'configuring' | 'rebooting' | 'success' | 'error',
|
|
error?: string
|
|
) => {
|
|
progressCalls.push({ deviceId, status, error });
|
|
};
|
|
|
|
await manager.bulkSetWiFi(devices.slice(0, 1), ssid, password, onProgress);
|
|
|
|
// Should have: connecting, configuring, rebooting, success
|
|
expect(progressCalls).toHaveLength(4);
|
|
expect(progressCalls[0].status).toBe('connecting');
|
|
expect(progressCalls[1].status).toBe('configuring');
|
|
expect(progressCalls[2].status).toBe('rebooting');
|
|
expect(progressCalls[3].status).toBe('success');
|
|
}, 10000);
|
|
|
|
it('should handle empty device list', async () => {
|
|
const results = await manager.bulkSetWiFi([], ssid, password);
|
|
|
|
expect(results).toEqual([]);
|
|
});
|
|
|
|
it('should handle wrong password', async () => {
|
|
const results = await manager.bulkSetWiFi(
|
|
[{ id: 'mock-743', name: 'WP_497' }],
|
|
ssid,
|
|
'wrongpass' // Mock treats 'wrongpass' as incorrect
|
|
);
|
|
|
|
expect(results[0].success).toBe(false);
|
|
expect(results[0].error).toBeDefined();
|
|
}, 10000);
|
|
|
|
it('should call error progress callback on failure', async () => {
|
|
const progressCalls: string[] = [];
|
|
|
|
const onProgress = (
|
|
_deviceId: string,
|
|
status: string,
|
|
_error?: string
|
|
) => {
|
|
progressCalls.push(status);
|
|
};
|
|
|
|
await manager.bulkSetWiFi(
|
|
[{ id: 'mock-743', name: 'WP_497' }],
|
|
ssid,
|
|
'wrongpass',
|
|
onProgress
|
|
);
|
|
|
|
expect(progressCalls).toContain('error');
|
|
}, 10000);
|
|
|
|
it('should continue after one device fails', async () => {
|
|
// First device will fail (wrong password), second should still process
|
|
const mixedDevices = [
|
|
{ id: 'fail-device', name: 'FailDevice' },
|
|
{ id: 'mock-769', name: 'WP_523_81aad4' },
|
|
];
|
|
|
|
const results = await manager.bulkSetWiFi(
|
|
mixedDevices,
|
|
'OfflineNetwork', // Mock treats 'offline' SSIDs as failures
|
|
password
|
|
);
|
|
|
|
expect(results).toHaveLength(2);
|
|
// Both should have been processed (not short-circuited)
|
|
}, 15000);
|
|
|
|
it('should validate WiFi credentials', async () => {
|
|
// Invalid SSID with pipe character
|
|
const results = await manager.bulkSetWiFi(
|
|
[{ id: 'mock-743', name: 'WP_497' }],
|
|
'Invalid|SSID',
|
|
password
|
|
);
|
|
|
|
expect(results[0].success).toBe(false);
|
|
expect(results[0].error).toContain('invalid character');
|
|
}, 10000);
|
|
|
|
it('should validate password length', async () => {
|
|
// Password too short (< 8 chars)
|
|
const results = await manager.bulkSetWiFi(
|
|
[{ id: 'mock-743', name: 'WP_497' }],
|
|
ssid,
|
|
'short'
|
|
);
|
|
|
|
expect(results[0].success).toBe(false);
|
|
// Mock returns generic invalid credentials error for short password
|
|
expect(results[0].error).toContain('invalid');
|
|
}, 10000);
|
|
|
|
it('should reboot devices after successful WiFi config', async () => {
|
|
await manager.connectDevice('mock-743');
|
|
expect(manager.isDeviceConnected('mock-743')).toBe(true);
|
|
|
|
await manager.bulkSetWiFi(devices.slice(0, 1), ssid, password);
|
|
|
|
// Device should be disconnected after reboot
|
|
expect(manager.isDeviceConnected('mock-743')).toBe(false);
|
|
}, 10000);
|
|
});
|
|
|
|
describe('Bulk Operations - Concurrency', () => {
|
|
it('should handle parallel bulk operations', async () => {
|
|
// Connect devices
|
|
await Promise.all([
|
|
manager.connectDevice('mock-743'),
|
|
manager.connectDevice('mock-769'),
|
|
]);
|
|
|
|
// Run bulk disconnect
|
|
const disconnectResults = await manager.bulkDisconnect(['mock-743', 'mock-769']);
|
|
|
|
expect(disconnectResults.every(r => r.success)).toBe(true);
|
|
}, 10000);
|
|
|
|
it('should track all results independently', async () => {
|
|
const devices = [
|
|
{ id: 'mock-743', name: 'WP_497' },
|
|
{ id: 'mock-769', name: 'WP_523' },
|
|
];
|
|
|
|
const results = await manager.bulkSetWiFi(devices, 'Network', 'password123');
|
|
|
|
// Each device should have its own result
|
|
const device1Result = results.find(r => r.deviceId === 'mock-743');
|
|
const device2Result = results.find(r => r.deviceId === 'mock-769');
|
|
|
|
expect(device1Result).toBeDefined();
|
|
expect(device2Result).toBeDefined();
|
|
expect(device1Result?.deviceId).not.toBe(device2Result?.deviceId);
|
|
}, 15000);
|
|
});
|
|
|
|
describe('Bulk Operations - Edge Cases', () => {
|
|
it('should handle duplicate device IDs', async () => {
|
|
await manager.connectDevice('mock-743');
|
|
|
|
const results = await manager.bulkDisconnect(['mock-743', 'mock-743']);
|
|
|
|
// Both should be processed (first disconnects, second is already disconnected)
|
|
expect(results).toHaveLength(2);
|
|
});
|
|
|
|
it('should handle mixed valid and invalid devices', async () => {
|
|
const devices = [
|
|
{ id: 'mock-743', name: 'WP_497' },
|
|
{ id: 'invalid-device', name: 'Invalid' },
|
|
];
|
|
|
|
const results = await manager.bulkSetWiFi(
|
|
devices,
|
|
'TestNetwork',
|
|
'password123'
|
|
);
|
|
|
|
expect(results).toHaveLength(2);
|
|
}, 15000);
|
|
|
|
it('should handle multiple device lists', async () => {
|
|
// Reduced from 10 to 3 devices to avoid timeout
|
|
const manyDevices = Array.from({ length: 3 }, (_, i) => ({
|
|
id: `device-${i}`,
|
|
name: `WP_${i}`,
|
|
}));
|
|
|
|
const results = await manager.bulkSetWiFi(
|
|
manyDevices,
|
|
'Network',
|
|
'password123'
|
|
);
|
|
|
|
expect(results).toHaveLength(3);
|
|
}, 30000);
|
|
});
|
|
});
|