diff --git a/src/camera-manager/engine-factory.ts b/src/camera-manager/engine-factory.ts index 1abdbb17..87b0e471 100644 --- a/src/camera-manager/engine-factory.ts +++ b/src/camera-manager/engine-factory.ts @@ -24,8 +24,8 @@ export class CameraManagerEngineFactory { this._resolvedMediaCache = resolvedMediaCache; } - public async createEngine(engine: Engine): Promise { - let cameraManagerEngine: CameraManagerEngine | null = null; + public async createEngine(engine: Engine): Promise { + let cameraManagerEngine: CameraManagerEngine; switch (engine) { case Engine.Generic: const { GenericCameraManagerEngine } = await import('./generic/engine-generic'); diff --git a/src/camera-manager/manager.ts b/src/camera-manager/manager.ts index b7f6b459..db2b38d0 100644 --- a/src/camera-manager/manager.ts +++ b/src/camera-manager/manager.ts @@ -129,6 +129,8 @@ export class CameraManager { return; } + this._store.reset(); + // For each camera merge the config (which has no defaults) into the camera // global config (which does have defaults). The merging must happen in this // order, to ensure that the defaults in the cameras global config do not diff --git a/src/camera-manager/store.ts b/src/camera-manager/store.ts index 4f454234..b5ec864e 100644 --- a/src/camera-manager/store.ts +++ b/src/camera-manager/store.ts @@ -41,6 +41,13 @@ export class CameraManagerStore implements CameraManagerReadOnlyConfigStore { this._enginesByType.set(engine.getEngineType(), engine); } + public reset(): void { + this._allConfigs.clear(); + this._visibleConfigs.clear(); + this._enginesByCamera.clear(); + this._enginesByType.clear(); + } + public getCameraConfig(cameraID: string): CameraConfig | null { return this._allConfigs.get(cameraID) ?? null; } @@ -74,11 +81,7 @@ export class CameraManagerStore implements CameraManagerReadOnlyConfigStore { } public getCameraConfigForMedia(media: ViewMedia): CameraConfig | null { - const cameraID = media.getCameraID(); - if (!cameraID) { - return null; - } - return this.getCameraConfig(cameraID); + return this.getCameraConfig(media.getCameraID()); } public getEngineOfType(engine: Engine): CameraManagerEngine | null { @@ -108,11 +111,7 @@ export class CameraManagerStore implements CameraManagerReadOnlyConfigStore { } public getEngineForMedia(media: ViewMedia): CameraManagerEngine | null { - const cameraID = media.getCameraID(); - if (!cameraID) { - return null; - } - return this.getEngineForCameraID(cameraID); + return this.getEngineForCameraID(media.getCameraID()); } public getAllEngines(): CameraManagerEngine[] { diff --git a/src/config/types.ts b/src/config/types.ts index b882804b..621a649e 100644 --- a/src/config/types.ts +++ b/src/config/types.ts @@ -1340,7 +1340,7 @@ export const dimensionsConfigSchema = z .or( z .string() - .regex(/^\s*\d+\s*[:\/]\s*\d+\s*$/) + .regex(/^\s*\d+\s*[:/]\s*\d+\s*$/) .transform((input) => input.split(/[:\/]/).map((d) => Number(d))), ) .default(dimensionsConfigDefault.aspect_ratio), diff --git a/tests/camera-manager/store.test.ts b/tests/camera-manager/store.test.ts new file mode 100644 index 00000000..bd66f082 --- /dev/null +++ b/tests/camera-manager/store.test.ts @@ -0,0 +1,157 @@ +import { describe, expect, it } from 'vitest'; +import { mock } from 'vitest-mock-extended'; +import { CameraManagerEngineFactory } from '../../src/camera-manager/engine-factory.js'; +import { CameraManagerStore } from '../../src/camera-manager/store.js'; +import { Engine } from '../../src/camera-manager/types.js'; +import { EntityRegistryManager } from '../../src/utils/ha/entity-registry/index.js'; +import { ResolvedMediaCache } from '../../src/utils/ha/resolved-media.js'; +import { TestViewMedia, createCameraConfig } from '../test-utils.js'; + +describe('CameraManagerStore', async () => { + const config_visible = createCameraConfig(); + const config_hidden = createCameraConfig({ + hide: true, + }); + + const engineFactory = new CameraManagerEngineFactory( + mock(), + mock(), + ); + + const engineGeneric = await engineFactory.createEngine(Engine.Generic); + const engineFrigate = await engineFactory.createEngine(Engine.Frigate); + + const setupStore = async (): Promise => { + const store = new CameraManagerStore(); + store.addCamera('camera-visible', config_visible, engineGeneric); + store.addCamera('camera-hidden', config_hidden, engineFrigate); + return store; + }; + + it('getCameraConfig', async () => { + const store = await setupStore(); + expect(store.getCameraConfig('camera-visible')).toBe(config_visible); + expect(store.getCameraConfig('camera-hidden')).toBe(config_hidden); + expect(store.getCameraConfig('camera-not-exist')).toBeNull(); + }); + + it('hasCameraID', async () => { + const store = await setupStore(); + expect(store.hasCameraID('camera-visible')).toBeTruthy(); + expect(store.hasCameraID('camera-hidden')).toBeTruthy(); + }); + + it('hasVisibleCameraID', async () => { + const store = await setupStore(); + expect(store.hasVisibleCameraID('camera-visible')).toBeTruthy(); + expect(store.hasVisibleCameraID('camera-hidden')).toBeFalsy(); + }); + + it('getCameraCount', async () => { + const store = await setupStore(); + expect(store.getCameraCount()).toBe(2); + }); + + it('getVisibleCameraCount', async () => { + const store = await setupStore(); + expect(store.getVisibleCameraCount()).toBe(1); + }); + + it('getCameras', async () => { + const store = await setupStore(); + expect(store.getCameras()).toEqual( + new Map([ + ['camera-visible', config_visible], + ['camera-hidden', config_hidden], + ]), + ); + }); + + it('getVisibleCameras', async () => { + const store = await setupStore(); + expect(store.getVisibleCameras()).toEqual( + new Map([['camera-visible', config_visible]]), + ); + }); + + it('getCameraIDs', async () => { + const store = await setupStore(); + expect(store.getCameraIDs()).toEqual(new Set(['camera-visible', 'camera-hidden'])); + }); + + it('getVisibleCameraIDs', async () => { + const store = await setupStore(); + expect(store.getVisibleCameraIDs()).toEqual(new Set(['camera-visible'])); + }); + + it('reset', async () => { + const store = await setupStore(); + + store.reset(); + + expect(store.getCameraCount()).toBe(0); + expect(store.getVisibleCameraCount()).toBe(0); + }); + + it('getCameraConfigForMedia', async () => { + const store = await setupStore(); + + const media_1 = new TestViewMedia({ cameraID: 'camera-visible' }); + expect(store.getCameraConfigForMedia(media_1)).toBe(config_visible); + + const media_2 = new TestViewMedia({ cameraID: 'camera-not-exist' }); + expect(store.getCameraConfigForMedia(media_2)).toBeNull(); + }); + + it('getEngineOfType', async () => { + const store = await setupStore(); + expect(store.getEngineOfType(Engine.Generic)).toBe(engineGeneric); + expect(store.getEngineOfType(Engine.Frigate)).toBe(engineFrigate); + expect(store.getEngineOfType(Engine.MotionEye)).toBeNull(); + }); + + it('getEngineForCameraID', async () => { + const store = await setupStore(); + expect(store.getEngineForCameraID('camera-visible')).toBe(engineGeneric); + expect(store.getEngineForCameraID('camera-hidden')).toBe(engineFrigate); + expect(store.getEngineForCameraID('camera-not-exist')).toBeNull(); + }); + + describe('getEnginesForCameraIDs', async () => { + it('empty input', async () => { + const store = await setupStore(); + expect(store.getEnginesForCameraIDs(new Set())).toBeNull(); + }); + + it('multiple cameras', async () => { + const store = await setupStore(); + store.addCamera('camera-visible2', config_visible, engineGeneric); + expect( + store.getEnginesForCameraIDs( + new Set([ + 'camera-visible', + 'camera-visible2', + 'camera-hidden', + 'camera-not-exist', + ]), + ), + ).toEqual( + new Map([ + [engineGeneric, new Set(['camera-visible', 'camera-visible2'])], + [engineFrigate, new Set(['camera-hidden'])], + ]), + ); + }); + }); + + it('getEngineForMedia', async () => { + const store = await setupStore(); + const media = new TestViewMedia({ cameraID: 'camera-visible' }); + expect(store.getEngineForMedia(media)).toBe(engineGeneric); + }); + + it('getAllEngines', async () => { + const store = await setupStore(); + expect(store.getAllEngines()).toEqual([engineGeneric, engineFrigate]); + }); +});