UNPKG

@homebridge-plugins/homebridge-air

Version:

The AirNow plugin allows you to monitor the current AirQuality for your Zip Code from HomeKit and Siri.

194 lines 8.86 kB
import { beforeEach, describe, expect, it, vi } from 'vitest'; import { AirPlatform } from '../platform.js'; // Mock the dependencies const mockLog = { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn(), }; const mockAPI = { hap: { uuid: { generate: vi.fn().mockReturnValue('test-uuid'), }, }, platformAccessory: vi.fn(), registerPlatformAccessories: vi.fn(), updatePlatformAccessories: vi.fn(), unregisterPlatformAccessories: vi.fn(), matter: { unregisterPlatformAccessories: vi.fn(), }, on: vi.fn(), // Mock the event listener }; const mockConfig = { name: 'Air Quality', options: { allowInvalidCharacters: false, }, }; describe('airPlatform validateAndCleanDisplayName', () => { let platform; beforeEach(() => { // Create platform instance with mocked dependencies platform = new AirPlatform(mockLog, mockConfig, mockAPI); vi.clearAllMocks(); }); it('should remove invalid "/" character from device name', async () => { const result = await platform.validateAndCleanDisplayName('Update/Restart Failure', 'city', 'Update/Restart Failure'); expect(result).toBe('UpdateRestart Failure'); expect(mockLog.warn).toHaveBeenCalledWith(expect.stringContaining('WARNING: The accessory \'Update/Restart Failure\' has an invalid \'city\' characteristic (\'Update/Restart Failure\')')); }); it('should remove invalid characters and clean start/end', async () => { const result = await platform.validateAndCleanDisplayName('Test Device', 'city', '/Update/Restart/'); expect(result).toBe('UpdateRestart'); expect(mockLog.warn).toHaveBeenCalledTimes(3); // warning + invalid chars + start/end cleanup }); it('should return unchanged name for valid characters', async () => { const result = await platform.validateAndCleanDisplayName('Valid City', 'city', 'Valid City'); expect(result).toBe('Valid City'); expect(mockLog.warn).not.toHaveBeenCalled(); }); it('should handle single character names after cleanup', async () => { const result = await platform.validateAndCleanDisplayName('Test', 'city', 'A/B'); expect(result).toBe('AB'); }); it('should return original value when allowInvalidCharacters is true', async () => { platform.config.options = { allowInvalidCharacters: true }; const result = await platform.validateAndCleanDisplayName('Test', 'city', 'Update/Restart Failure'); expect(result).toBe('Update/Restart Failure'); expect(mockLog.warn).not.toHaveBeenCalled(); }); it('should handle multiple consecutive invalid characters', async () => { const result = await platform.validateAndCleanDisplayName('Test', 'city', 'Update///Restart***Failure'); expect(result).toBe('UpdateRestartFailure'); expect(mockLog.warn).toHaveBeenCalled(); }); it('should handle AQICN station ID format without warnings', async () => { const result = await platform.validateAndCleanDisplayName('/station/@92323', 'city', '/station/@92323', 'aqicn'); expect(result).toBe('Station 92323'); expect(mockLog.warn).not.toHaveBeenCalled(); }); it('should handle AQICN station name format', async () => { const result = await platform.validateAndCleanDisplayName('/station/winterthur-veltheim/switzerland', 'city', '/station/winterthur-veltheim/switzerland', 'aqicn'); expect(result).toBe('Winterthur Veltheim Switzerland'); expect(mockLog.warn).not.toHaveBeenCalled(); }); it('should handle AQICN city path format', async () => { const result = await platform.validateAndCleanDisplayName('/city/switzerland/zurich', 'city', '/city/switzerland/zurich', 'aqicn'); expect(result).toBe('Switzerland Zurich'); expect(mockLog.warn).not.toHaveBeenCalled(); }); it('should handle regular AQICN city names normally', async () => { const result = await platform.validateAndCleanDisplayName('Zurich', 'city', 'Zurich', 'aqicn'); expect(result).toBe('Zurich'); expect(mockLog.warn).not.toHaveBeenCalled(); }); it('should still validate non-AQICN providers normally', async () => { const result = await platform.validateAndCleanDisplayName('/station/@92323', 'city', '/station/@92323', 'airnow'); expect(result).toBe('station92323'); expect(mockLog.warn).toHaveBeenCalled(); }); it('should still validate non-city fields normally for AQICN', async () => { const result = await platform.validateAndCleanDisplayName('/station/@92323', 'name', '/station/@92323', 'aqicn'); expect(result).toBe('station92323'); expect(mockLog.warn).toHaveBeenCalled(); }); }); describe('airPlatform generateAqicnDisplayName', () => { let platform; beforeEach(() => { platform = new AirPlatform(mockLog, mockConfig, mockAPI); vi.clearAllMocks(); }); it('should convert station ID format to readable name', () => { const result = platform.generateAqicnDisplayName('/station/@92323'); expect(result).toBe('Station 92323'); }); it('should convert station name format to readable name', () => { const result = platform.generateAqicnDisplayName('/station/winterthur-veltheim/switzerland'); expect(result).toBe('Winterthur Veltheim Switzerland'); }); it('should convert city path format to readable name', () => { const result = platform.generateAqicnDisplayName('/city/switzerland/zurich-airport'); expect(result).toBe('Switzerland Zurich Airport'); }); it('should handle single word station names', () => { const result = platform.generateAqicnDisplayName('/station/zurich'); expect(result).toBe('Zurich'); }); it('should return regular city names unchanged', () => { const result = platform.generateAqicnDisplayName('Zurich'); expect(result).toBe('Zurich'); }); it('should handle empty string', () => { const result = platform.generateAqicnDisplayName(''); expect(result).toBe(''); }); }); describe('airPlatform matter fallback cleanup', () => { let platform; beforeEach(() => { platform = new AirPlatform(mockLog, mockConfig, mockAPI); vi.clearAllMocks(); }); it('should unregister stale matter accessory while in HAP mode', async () => { const staleAccessory = { UUID: 'matter-uuid-1', displayName: 'Matter Device 1', }; platform.configureMatterAccessory(staleAccessory); // configureMatterAccessory kicks off async cleanup without awaiting. await Promise.resolve(); await Promise.resolve(); expect(mockAPI.matter.unregisterPlatformAccessories).toHaveBeenCalledWith('@homebridge-plugins/homebridge-air', 'Air', [staleAccessory]); }); }); describe('airPlatform verifyConfig provider validation', () => { let platform; beforeEach(() => { platform = new AirPlatform(mockLog, mockConfig, mockAPI); vi.clearAllMocks(); }); it('should require AirNow location as zip+city or lat+lon', async () => { platform.config.devices = [ { provider: 'airnow', apiKey: 'test-key', city: undefined, zipCode: undefined, latitude: undefined, longitude: undefined, }, ]; const errorSpy = vi.spyOn(platform, 'errorLog').mockResolvedValue(undefined); await platform.verifyConfig(); expect(errorSpy).toHaveBeenCalledWith('AirNow requires either (zipCode + city) or (latitude + longitude)'); }); it('should allow AQICN with city path and no coordinates', async () => { platform.config.devices = [ { provider: 'aqicn', apiKey: 'test-key', city: '/station/@92323', }, ]; const errorSpy = vi.spyOn(platform, 'errorLog').mockResolvedValue(undefined); await platform.verifyConfig(); expect(errorSpy).not.toHaveBeenCalledWith('AQICN requires either city/station path/URL or (latitude + longitude)'); }); it('should report missing longitude when only latitude is provided', async () => { platform.config.devices = [ { provider: 'airnow', apiKey: 'test-key', latitude: 47.5, }, ]; const errorSpy = vi.spyOn(platform, 'errorLog').mockResolvedValue(undefined); await platform.verifyConfig(); expect(errorSpy).toHaveBeenCalledWith('Missing your Longitude'); }); }); //# sourceMappingURL=platform.test.js.map