@@ -3,10 +3,7 @@ import { mock } from 'vitest-mock-extended';
|
||||
import { BrowseMediaCamera } from '../../../src/camera-manager/browse-media/camera';
|
||||
import { CameraManagerEngine } from '../../../src/camera-manager/engine';
|
||||
import { StateWatcherSubscriptionInterface } from '../../../src/card-controller/hass/state-watcher';
|
||||
import {
|
||||
Entity,
|
||||
EntityRegistryManager,
|
||||
} from '../../../src/utils/ha/registry/entity/types';
|
||||
import { Entity, EntityRegistryManager } from '../../../src/ha/registry/entity/types';
|
||||
import { createCameraConfig, createHASS } from '../../test-utils';
|
||||
|
||||
describe('BrowseMediaCamera', () => {
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { BrowseMediaMetadata } from '../../../../src/camera-manager/browse-media/types';
|
||||
import { getViewMediaFromBrowseMediaArray } from '../../../../src/camera-manager/browse-media/utils/browse-media-to-view-media';
|
||||
import {
|
||||
MEDIA_CLASS_IMAGE,
|
||||
MEDIA_CLASS_VIDEO,
|
||||
RichBrowseMedia,
|
||||
} from '../../../../src/utils/ha/browse-media/types';
|
||||
import { createBrowseMedia } from './test-utils';
|
||||
|
||||
const createBrowseMediaChildren = (
|
||||
overrides: Partial<RichBrowseMedia<BrowseMediaMetadata>>[],
|
||||
) => {
|
||||
return overrides.map((override) => createBrowseMedia(override));
|
||||
};
|
||||
|
||||
describe('getViewMediaFromBrowseMediaArray', () => {
|
||||
it('should ignore absent metadata', () => {
|
||||
const children = createBrowseMediaChildren([{ _metadata: undefined }]);
|
||||
expect(getViewMediaFromBrowseMediaArray(children)).toEqual([]);
|
||||
});
|
||||
|
||||
it('should ignore unknown media class', () => {
|
||||
const children = createBrowseMediaChildren([{ media_class: 'UNKNOWN' }]);
|
||||
expect(getViewMediaFromBrowseMediaArray(children)).toEqual([]);
|
||||
});
|
||||
|
||||
it('should generate clip view media', () => {
|
||||
const children = createBrowseMediaChildren([{ media_class: MEDIA_CLASS_VIDEO }]);
|
||||
const viewMedia = getViewMediaFromBrowseMediaArray(children);
|
||||
expect(viewMedia).toHaveLength(1);
|
||||
expect(viewMedia?.[0].getMediaType()).toBe('clip');
|
||||
expect(viewMedia?.[0].getID()).toBe('camera.test/2024-11-19 07:23:00');
|
||||
});
|
||||
|
||||
it('should generate snapshot view media', () => {
|
||||
const children = createBrowseMediaChildren([{ media_class: MEDIA_CLASS_IMAGE }]);
|
||||
const viewMedia = getViewMediaFromBrowseMediaArray(children);
|
||||
expect(viewMedia).toHaveLength(1);
|
||||
expect(viewMedia?.[0].getMediaType()).toBe('snapshot');
|
||||
expect(viewMedia?.[0].getID()).toBe('camera.test/2024-11-19 07:23:00');
|
||||
});
|
||||
|
||||
it('should de-duplicate snapshot/clip duplicates', () => {
|
||||
const children = createBrowseMediaChildren([
|
||||
{ media_class: MEDIA_CLASS_IMAGE },
|
||||
{ media_class: MEDIA_CLASS_IMAGE },
|
||||
{ media_class: MEDIA_CLASS_VIDEO },
|
||||
]);
|
||||
const viewMedia = getViewMediaFromBrowseMediaArray(children);
|
||||
expect(viewMedia).toHaveLength(1);
|
||||
expect(viewMedia?.[0].getMediaType()).toBe('clip');
|
||||
expect(viewMedia?.[0].getID()).toBe('camera.test/2024-11-19 07:23:00');
|
||||
});
|
||||
});
|
||||
@@ -1,23 +0,0 @@
|
||||
import { BrowseMediaMetadata } from '../../../../src/camera-manager/browse-media/types';
|
||||
import { RichBrowseMedia } from '../../../../src/utils/ha/browse-media/types';
|
||||
|
||||
export const createBrowseMedia = (
|
||||
media?: Partial<RichBrowseMedia<BrowseMediaMetadata>>,
|
||||
): RichBrowseMedia<BrowseMediaMetadata> => {
|
||||
return {
|
||||
title: 'Test Media',
|
||||
media_class: 'video',
|
||||
media_content_type: 'video/mp4',
|
||||
media_content_id: 'test',
|
||||
can_play: true,
|
||||
can_expand: false,
|
||||
thumbnail: null,
|
||||
children: null,
|
||||
_metadata: {
|
||||
cameraID: 'camera.test',
|
||||
startDate: new Date('2024-11-19T07:23:00'),
|
||||
endDate: new Date('2025-11-19T07:24:00'),
|
||||
},
|
||||
...media,
|
||||
};
|
||||
};
|
||||
@@ -1,40 +0,0 @@
|
||||
import { sub } from 'date-fns';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { isMediaWithinDates } from '../../../../src/camera-manager/browse-media/utils/within-dates';
|
||||
import { createBrowseMedia } from './test-utils';
|
||||
|
||||
describe('isMediaWithinDates', () => {
|
||||
const rangeStart = new Date('2024-11-19T07:00:00');
|
||||
const rangeEnd = new Date('2024-11-19T08:00:00');
|
||||
|
||||
it('should never match media without metadata', () => {
|
||||
const media = createBrowseMedia({ _metadata: undefined });
|
||||
expect(isMediaWithinDates(media, rangeStart, rangeEnd)).toBe(false);
|
||||
});
|
||||
|
||||
it('should always match media without start or end date', () => {
|
||||
expect(isMediaWithinDates(createBrowseMedia(), undefined, undefined)).toBe(true);
|
||||
});
|
||||
|
||||
it('should match without a start date in the range', () => {
|
||||
expect(isMediaWithinDates(createBrowseMedia(), undefined, rangeEnd)).toBe(true);
|
||||
});
|
||||
|
||||
it('should match without an end date in the range', () => {
|
||||
expect(isMediaWithinDates(createBrowseMedia(), rangeStart, undefined)).toBe(true);
|
||||
});
|
||||
|
||||
it('should match when ranges overlap', () => {
|
||||
expect(isMediaWithinDates(createBrowseMedia(), rangeStart, rangeEnd)).toBe(true);
|
||||
});
|
||||
|
||||
it('should not match when ranges do not overlap', () => {
|
||||
expect(
|
||||
isMediaWithinDates(
|
||||
createBrowseMedia(),
|
||||
sub(rangeStart, { days: 1 }),
|
||||
sub(rangeEnd, { days: 1 }),
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -1,17 +1,29 @@
|
||||
import { add, sub } from 'date-fns';
|
||||
import sortBy from 'lodash-es/sortBy';
|
||||
import { sortBy } from 'lodash-es';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import {
|
||||
MemoryRequestCache,
|
||||
RecordingSegmentsCache,
|
||||
} from '../../src/camera-manager/cache.js';
|
||||
import { RecordingSegmentsCache } from '../../src/camera-manager/cache.js';
|
||||
import { DateRange } from '../../src/camera-manager/range.js';
|
||||
import { RecordingSegment } from '../../src/camera-manager/types.js';
|
||||
import {
|
||||
CameraManagerRequestCache,
|
||||
CameraQuery,
|
||||
Engine,
|
||||
EventQuery,
|
||||
QueryResultsType,
|
||||
QueryReturnType,
|
||||
QueryType,
|
||||
RecordingSegment,
|
||||
} from '../../src/camera-manager/types.js';
|
||||
|
||||
describe('MemoryRequestCache', () => {
|
||||
const cache = new MemoryRequestCache();
|
||||
const request = { request: 'foo' };
|
||||
const response = { response: 'bar' };
|
||||
const cache = new CameraManagerRequestCache();
|
||||
const request: CameraQuery = {
|
||||
type: QueryType.Event,
|
||||
cameraIDs: new Set(['camera-1']),
|
||||
};
|
||||
const response: QueryReturnType<EventQuery> = {
|
||||
type: QueryResultsType.Event,
|
||||
engine: Engine.Generic,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
cache.clear();
|
||||
|
||||
@@ -8,15 +8,15 @@ import { ReolinkCameraManagerEngine } from '../../src/camera-manager/reolink/eng
|
||||
import { Engine } from '../../src/camera-manager/types.js';
|
||||
import { StateWatcherSubscriptionInterface } from '../../src/card-controller/hass/state-watcher.js';
|
||||
import { CardWideConfig } from '../../src/config/schema/types.js';
|
||||
import { EntityRegistryManager } from '../../src/utils/ha/registry/entity/types.js';
|
||||
import { ResolvedMediaCache } from '../../src/utils/ha/resolved-media';
|
||||
import { EntityRegistryManager } from '../../src/ha/registry/entity/types.js';
|
||||
import { ResolvedMediaCache } from '../../src/ha/resolved-media.js';
|
||||
import { EntityRegistryManagerMock } from '../ha/registry/entity/mock.js';
|
||||
import {
|
||||
createCameraConfig,
|
||||
createHASS,
|
||||
createRegistryEntity,
|
||||
createStateEntity,
|
||||
} from '../test-utils';
|
||||
import { EntityRegistryManagerMock } from '../utils/ha/registry/entity/mock.js';
|
||||
|
||||
vi.mock('../../src/utils/ha/entity-registry');
|
||||
vi.mock('../../src/utils/ha/entity-registry/cache');
|
||||
|
||||
@@ -9,12 +9,9 @@ import { ActionsExecutor } from '../../../src/card-controller/actions/types';
|
||||
import { StateWatcher } from '../../../src/card-controller/hass/state-watcher';
|
||||
import { PTZAction } from '../../../src/config/schema/actions/custom/ptz';
|
||||
import { CameraTriggerEventType } from '../../../src/config/schema/cameras';
|
||||
import {
|
||||
Entity,
|
||||
EntityRegistryManager,
|
||||
} from '../../../src/utils/ha/registry/entity/types';
|
||||
import { Entity, EntityRegistryManager } from '../../../src/ha/registry/entity/types';
|
||||
import { EntityRegistryManagerMock } from '../../ha/registry/entity/mock';
|
||||
import { createCameraConfig, createHASS, createRegistryEntity } from '../../test-utils';
|
||||
import { EntityRegistryManagerMock } from '../../utils/ha/registry/entity/mock';
|
||||
|
||||
vi.mock('../../../src/camera-manager/frigate/requests');
|
||||
|
||||
@@ -91,8 +88,8 @@ describe('FrigateCamera', () => {
|
||||
entityRegistryManager: entityRegistryManager,
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
frigateEventWatcher: mock<FrigateEventWatcher>(),
|
||||
}),
|
||||
expect(camera.getConfig().frigate.camera_name).toBe('fnt_dr');
|
||||
});
|
||||
expect(camera.getConfig().frigate.camera_name).toBe('fnt_dr');
|
||||
});
|
||||
|
||||
it('with a camera_entity without camera_name match', async () => {
|
||||
@@ -115,8 +112,8 @@ describe('FrigateCamera', () => {
|
||||
entityRegistryManager: entityRegistryManager,
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
frigateEventWatcher: mock<FrigateEventWatcher>(),
|
||||
}),
|
||||
expect(camera.getConfig().frigate.camera_name).toBeUndefined();
|
||||
});
|
||||
expect(camera.getConfig().frigate.camera_name).toBeUndefined();
|
||||
});
|
||||
|
||||
it('with a camera_entity without platform match', async () => {
|
||||
@@ -139,8 +136,8 @@ describe('FrigateCamera', () => {
|
||||
entityRegistryManager: entityRegistryManager,
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
frigateEventWatcher: mock<FrigateEventWatcher>(),
|
||||
}),
|
||||
expect(camera.getConfig().frigate.camera_name).toBeUndefined();
|
||||
});
|
||||
expect(camera.getConfig().frigate.camera_name).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -161,8 +158,8 @@ describe('FrigateCamera', () => {
|
||||
entityRegistryManager: mock<EntityRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
frigateEventWatcher: mock<FrigateEventWatcher>(),
|
||||
}),
|
||||
expect(camera.getCapabilities()?.has('favorite-events')).toBeTruthy();
|
||||
});
|
||||
expect(camera.getCapabilities()?.has('favorite-events')).toBeTruthy();
|
||||
expect(camera.getCapabilities()?.has('favorite-recordings')).toBeFalsy();
|
||||
expect(camera.getCapabilities()?.has('seek')).toBeTruthy();
|
||||
expect(camera.getCapabilities()?.has('clips')).toBeTruthy();
|
||||
@@ -188,8 +185,8 @@ describe('FrigateCamera', () => {
|
||||
entityRegistryManager: mock<EntityRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
frigateEventWatcher: mock<FrigateEventWatcher>(),
|
||||
}),
|
||||
expect(camera.getCapabilities()?.has('favorite-events')).toBeFalsy();
|
||||
});
|
||||
expect(camera.getCapabilities()?.has('favorite-events')).toBeFalsy();
|
||||
expect(camera.getCapabilities()?.has('favorite-recordings')).toBeFalsy();
|
||||
expect(camera.getCapabilities()?.has('seek')).toBeFalsy();
|
||||
expect(camera.getCapabilities()?.has('clips')).toBeFalsy();
|
||||
@@ -248,8 +245,8 @@ describe('FrigateCamera', () => {
|
||||
entityRegistryManager: mock<EntityRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
frigateEventWatcher: mock<FrigateEventWatcher>(),
|
||||
}),
|
||||
expect(camera.getCapabilities()?.has('ptz')).toBeTruthy();
|
||||
});
|
||||
expect(camera.getCapabilities()?.has('ptz')).toBeTruthy();
|
||||
expect(camera.getCapabilities()?.getPTZCapabilities()).toEqual({
|
||||
left: ['continuous'],
|
||||
right: ['continuous'],
|
||||
@@ -284,8 +281,8 @@ describe('FrigateCamera', () => {
|
||||
entityRegistryManager: mock<EntityRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
frigateEventWatcher: mock<FrigateEventWatcher>(),
|
||||
}),
|
||||
expect(camera.getCapabilities()?.has('ptz')).toBeTruthy();
|
||||
});
|
||||
expect(camera.getCapabilities()?.has('ptz')).toBeTruthy();
|
||||
expect(camera.getCapabilities()?.getPTZCapabilities()).toEqual({
|
||||
left: [],
|
||||
right: [],
|
||||
@@ -319,13 +316,13 @@ describe('FrigateCamera', () => {
|
||||
entityRegistryManager: mock<EntityRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
frigateEventWatcher: eventWatcher,
|
||||
}),
|
||||
expect(eventWatcher.subscribe).toBeCalledWith(
|
||||
hass,
|
||||
expect.objectContaining({
|
||||
instanceID: 'CLIENT_ID',
|
||||
}),
|
||||
);
|
||||
});
|
||||
expect(eventWatcher.subscribe).toBeCalledWith(
|
||||
hass,
|
||||
expect.objectContaining({
|
||||
instanceID: 'CLIENT_ID',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should not subscribe with no trigger events', async () => {
|
||||
@@ -349,8 +346,8 @@ describe('FrigateCamera', () => {
|
||||
entityRegistryManager: mock<EntityRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
frigateEventWatcher: eventWatcher,
|
||||
}),
|
||||
expect(eventWatcher.subscribe).not.toBeCalled();
|
||||
});
|
||||
expect(eventWatcher.subscribe).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should not subscribe without trigger capability', async () => {
|
||||
@@ -395,8 +392,8 @@ describe('FrigateCamera', () => {
|
||||
entityRegistryManager: mock<EntityRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
frigateEventWatcher: eventWatcher,
|
||||
}),
|
||||
expect(eventWatcher.subscribe).not.toBeCalled();
|
||||
});
|
||||
expect(eventWatcher.subscribe).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should unsubscribe on destroy', async () => {
|
||||
@@ -416,8 +413,8 @@ describe('FrigateCamera', () => {
|
||||
entityRegistryManager: mock<EntityRegistryManager>(),
|
||||
stateWatcher: mock<StateWatcher>(),
|
||||
frigateEventWatcher: eventWatcher,
|
||||
}),
|
||||
expect(eventWatcher.unsubscribe).not.toBeCalled();
|
||||
});
|
||||
expect(eventWatcher.unsubscribe).not.toBeCalled();
|
||||
|
||||
await camera.destroy();
|
||||
expect(eventWatcher.unsubscribe).toBeCalled();
|
||||
|
||||
@@ -1,31 +1,32 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { RecordingSegmentsCache, RequestCache } from '../../../src/camera-manager/cache';
|
||||
import { RecordingSegmentsCache } from '../../../src/camera-manager/cache';
|
||||
import { FrigateCameraManagerEngine } from '../../../src/camera-manager/frigate/engine-frigate';
|
||||
import {
|
||||
FrigateEventViewMedia,
|
||||
FrigateRecordingViewMedia,
|
||||
} from '../../../src/camera-manager/frigate/media';
|
||||
import { FrigateEvent, eventSchema } from '../../../src/camera-manager/frigate/types.js';
|
||||
import { CameraManagerRequestCache } from '../../../src/camera-manager/types';
|
||||
import { StateWatcher } from '../../../src/card-controller/hass/state-watcher';
|
||||
import { CameraConfig } from '../../../src/config/schema/cameras';
|
||||
import { AdvancedCameraCardView } from '../../../src/config/schema/common/const';
|
||||
import { RawAdvancedCameraCardConfig } from '../../../src/config/types';
|
||||
import { ViewMedia } from '../../../src/view/media';
|
||||
import { ViewMedia, ViewMediaType } from '../../../src/view/item';
|
||||
import { EntityRegistryManagerMock } from '../../ha/registry/entity/mock';
|
||||
import { TestViewMedia, createCameraConfig, createHASS } from '../../test-utils';
|
||||
import { EntityRegistryManagerMock } from '../../utils/ha/registry/entity/mock';
|
||||
|
||||
const createEngine = (): FrigateCameraManagerEngine => {
|
||||
return new FrigateCameraManagerEngine(
|
||||
new EntityRegistryManagerMock(),
|
||||
new StateWatcher(),
|
||||
new RecordingSegmentsCache(),
|
||||
new RequestCache(),
|
||||
new CameraManagerRequestCache(),
|
||||
);
|
||||
};
|
||||
|
||||
const createRecordingMedia = (): FrigateRecordingViewMedia => {
|
||||
return new FrigateRecordingViewMedia(
|
||||
'recording',
|
||||
ViewMediaType.Recording,
|
||||
'camera-1',
|
||||
{
|
||||
cameraID: 'camera-1',
|
||||
@@ -58,7 +59,7 @@ const createEvent = (): FrigateEvent => {
|
||||
|
||||
const createClipMedia = (): FrigateEventViewMedia => {
|
||||
return new FrigateEventViewMedia(
|
||||
'clip',
|
||||
ViewMediaType.Clip,
|
||||
'camera-1',
|
||||
createEvent(),
|
||||
'event-clip-content-id',
|
||||
@@ -68,7 +69,7 @@ const createClipMedia = (): FrigateEventViewMedia => {
|
||||
|
||||
const createSnapshotMedia = (): FrigateEventViewMedia => {
|
||||
return new FrigateEventViewMedia(
|
||||
'snapshot',
|
||||
ViewMediaType.Snapshot,
|
||||
'camera-1',
|
||||
createEvent(),
|
||||
'event-snapshot-content-id',
|
||||
@@ -133,7 +134,9 @@ describe('getMediaDownloadPath', () => {
|
||||
const endpoint = await createEngine().getMediaDownloadPath(
|
||||
createHASS(),
|
||||
createFrigateCameraConfig(),
|
||||
new ViewMedia('clip', 'camera-1'),
|
||||
new ViewMedia(ViewMediaType.Clip, {
|
||||
cameraID: 'camera-1',
|
||||
}),
|
||||
);
|
||||
expect(endpoint).toBeNull();
|
||||
});
|
||||
@@ -247,9 +250,9 @@ describe('getCameraEndpoints', () => {
|
||||
});
|
||||
|
||||
describe('with event media type', () => {
|
||||
it.each([['clip' as const], ['snapshot' as const]])(
|
||||
it.each([[ViewMediaType.Clip], [ViewMediaType.Snapshot]])(
|
||||
'%s',
|
||||
(mediaType: 'clip' | 'snapshot') => {
|
||||
(mediaType: ViewMediaType) => {
|
||||
const endpoints = createEngine().getCameraEndpoints(
|
||||
createCameraConfig({
|
||||
frigate: {
|
||||
@@ -284,7 +287,10 @@ describe('getCameraEndpoints', () => {
|
||||
},
|
||||
}),
|
||||
{
|
||||
media: new TestViewMedia({ mediaType: 'recording', startTime: startTime }),
|
||||
media: new TestViewMedia({
|
||||
mediaType: ViewMediaType.Recording,
|
||||
startTime: startTime,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -306,7 +312,7 @@ describe('getCameraEndpoints', () => {
|
||||
},
|
||||
}),
|
||||
{
|
||||
media: new TestViewMedia({ mediaType: 'recording' }),
|
||||
media: new TestViewMedia({ mediaType: ViewMediaType.Recording }),
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -18,10 +18,10 @@ import {
|
||||
retainResultSchema,
|
||||
} from '../../../src/camera-manager/frigate/types';
|
||||
import { RecordingSegment } from '../../../src/camera-manager/types';
|
||||
import { homeAssistantWSRequest } from '../../../src/utils/ha/ws-request';
|
||||
import { homeAssistantWSRequest } from '../../../src/ha/ws-request';
|
||||
import { createFrigateEvent, createHASS } from '../../test-utils';
|
||||
|
||||
vi.mock('../../../src/utils/ha/ws-request');
|
||||
vi.mock('../../../src/ha/ws-request');
|
||||
|
||||
describe('frigate requests', () => {
|
||||
afterEach(() => {
|
||||
|
||||
@@ -7,16 +7,14 @@ import { CameraManagerEngineFactory } from '../../src/camera-manager/engine-fact
|
||||
import { CameraManagerEngine } from '../../src/camera-manager/engine.js';
|
||||
import {
|
||||
CameraManager,
|
||||
QueryClassifier,
|
||||
CameraQueryClassifier,
|
||||
QueryResultClassifier,
|
||||
} from '../../src/camera-manager/manager.js';
|
||||
import {
|
||||
CameraEndpoint,
|
||||
CameraEndpoints,
|
||||
CameraEndpointsContext,
|
||||
CameraEvent,
|
||||
CameraManagerCameraMetadata,
|
||||
CameraManagerMediaCapabilities,
|
||||
Engine,
|
||||
EventQuery,
|
||||
EventQueryResults,
|
||||
@@ -25,12 +23,13 @@ import {
|
||||
QueryResultsType,
|
||||
QueryType,
|
||||
} from '../../src/camera-manager/types.js';
|
||||
import { sortMedia } from '../../src/camera-manager/utils/sort-media.js';
|
||||
import { CardController } from '../../src/card-controller/controller.js';
|
||||
import { sortItems } from '../../src/card-controller/view/sort.js';
|
||||
import { CameraConfig } from '../../src/config/schema/cameras.js';
|
||||
import { HomeAssistant } from '../../src/ha/types.js';
|
||||
import { PTZMovementType } from '../../src/types.js';
|
||||
import { ViewMedia } from '../../src/view/media.js';
|
||||
import { Endpoint, PTZMovementType } from '../../src/types.js';
|
||||
import { ViewFolder, ViewItem, ViewMedia } from '../../src/view/item.js';
|
||||
import { ViewItemCapabilities } from '../../src/view/types.js';
|
||||
import {
|
||||
TestViewMedia,
|
||||
createCamera,
|
||||
@@ -38,53 +37,66 @@ import {
|
||||
createCapabilities,
|
||||
createCardAPI,
|
||||
createConfig,
|
||||
createFolder,
|
||||
createHASS,
|
||||
generateViewMediaArray,
|
||||
} from '../test-utils.js';
|
||||
|
||||
describe('QueryClassifier', async () => {
|
||||
it('should classify event query', async () => {
|
||||
expect(QueryClassifier.isEventQuery({ type: QueryType.Event })).toBeTruthy();
|
||||
expect(QueryClassifier.isEventQuery({ type: QueryType.Recording })).toBeFalsy();
|
||||
expect(CameraQueryClassifier.isEventQuery({ type: QueryType.Event })).toBeTruthy();
|
||||
expect(
|
||||
QueryClassifier.isEventQuery({ type: QueryType.RecordingSegments }),
|
||||
CameraQueryClassifier.isEventQuery({ type: QueryType.Recording }),
|
||||
).toBeFalsy();
|
||||
expect(
|
||||
CameraQueryClassifier.isEventQuery({ type: QueryType.RecordingSegments }),
|
||||
).toBeFalsy();
|
||||
expect(
|
||||
CameraQueryClassifier.isEventQuery({ type: QueryType.MediaMetadata }),
|
||||
).toBeFalsy();
|
||||
expect(QueryClassifier.isEventQuery({ type: QueryType.MediaMetadata })).toBeFalsy();
|
||||
});
|
||||
it('should classify recording query', async () => {
|
||||
expect(QueryClassifier.isRecordingQuery({ type: QueryType.Event })).toBeFalsy();
|
||||
expect(QueryClassifier.isRecordingQuery({ type: QueryType.Recording })).toBeTruthy();
|
||||
expect(
|
||||
QueryClassifier.isRecordingQuery({ type: QueryType.RecordingSegments }),
|
||||
CameraQueryClassifier.isRecordingQuery({ type: QueryType.Event }),
|
||||
).toBeFalsy();
|
||||
expect(
|
||||
QueryClassifier.isRecordingQuery({ type: QueryType.MediaMetadata }),
|
||||
CameraQueryClassifier.isRecordingQuery({ type: QueryType.Recording }),
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
CameraQueryClassifier.isRecordingQuery({ type: QueryType.RecordingSegments }),
|
||||
).toBeFalsy();
|
||||
expect(
|
||||
CameraQueryClassifier.isRecordingQuery({ type: QueryType.MediaMetadata }),
|
||||
).toBeFalsy();
|
||||
});
|
||||
it('should classify recording segments query', async () => {
|
||||
expect(
|
||||
QueryClassifier.isRecordingSegmentsQuery({ type: QueryType.Event }),
|
||||
CameraQueryClassifier.isRecordingSegmentsQuery({ type: QueryType.Event }),
|
||||
).toBeFalsy();
|
||||
expect(
|
||||
QueryClassifier.isRecordingSegmentsQuery({ type: QueryType.Recording }),
|
||||
CameraQueryClassifier.isRecordingSegmentsQuery({ type: QueryType.Recording }),
|
||||
).toBeFalsy();
|
||||
expect(
|
||||
QueryClassifier.isRecordingSegmentsQuery({ type: QueryType.RecordingSegments }),
|
||||
CameraQueryClassifier.isRecordingSegmentsQuery({
|
||||
type: QueryType.RecordingSegments,
|
||||
}),
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
QueryClassifier.isRecordingSegmentsQuery({ type: QueryType.MediaMetadata }),
|
||||
CameraQueryClassifier.isRecordingSegmentsQuery({ type: QueryType.MediaMetadata }),
|
||||
).toBeFalsy();
|
||||
});
|
||||
it('should classify media metadata query', async () => {
|
||||
expect(QueryClassifier.isMediaMetadataQuery({ type: QueryType.Event })).toBeFalsy();
|
||||
expect(
|
||||
QueryClassifier.isMediaMetadataQuery({ type: QueryType.Recording }),
|
||||
CameraQueryClassifier.isMediaMetadataQuery({ type: QueryType.Event }),
|
||||
).toBeFalsy();
|
||||
expect(
|
||||
QueryClassifier.isMediaMetadataQuery({ type: QueryType.RecordingSegments }),
|
||||
CameraQueryClassifier.isMediaMetadataQuery({ type: QueryType.Recording }),
|
||||
).toBeFalsy();
|
||||
expect(
|
||||
QueryClassifier.isMediaMetadataQuery({ type: QueryType.MediaMetadata }),
|
||||
CameraQueryClassifier.isMediaMetadataQuery({ type: QueryType.RecordingSegments }),
|
||||
).toBeFalsy();
|
||||
expect(
|
||||
CameraQueryClassifier.isMediaMetadataQuery({ type: QueryType.MediaMetadata }),
|
||||
).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -641,7 +653,7 @@ describe('CameraManager', async () => {
|
||||
|
||||
const results = new Map([[baseEventQuery, baseEventQueryResults]]);
|
||||
engine.getEvents.mockResolvedValue(results);
|
||||
const media = sortMedia(generateViewMediaArray({ count: 5 }));
|
||||
const media = sortItems(generateViewMediaArray({ count: 5 }));
|
||||
engine.generateMediaFromEvents.mockReturnValue(media);
|
||||
|
||||
expect(await manager.executeMediaQueries([baseEventQuery])).toEqual(media);
|
||||
@@ -697,7 +709,7 @@ describe('CameraManager', async () => {
|
||||
|
||||
const results = new Map([[baseRecordingQuery, baseRecordingQueryResults]]);
|
||||
engine.getRecordings.mockResolvedValue(results);
|
||||
const media = sortMedia(generateViewMediaArray({ count: 5 }));
|
||||
const media = sortItems(generateViewMediaArray({ count: 5 }));
|
||||
engine.generateMediaFromRecordings.mockReturnValue(media);
|
||||
|
||||
expect(await manager.executeMediaQueries([baseRecordingQuery])).toEqual(media);
|
||||
@@ -718,7 +730,7 @@ describe('CameraManager', async () => {
|
||||
describe('should extend media queries', () => {
|
||||
const dateBase = new Date('2024-03-01T20:01:00');
|
||||
const mediaTwoCameras = generateViewMediaArray({ count: 5 });
|
||||
const mediaMixedStart: ViewMedia[] = [
|
||||
const mediaMixedStart: ViewItem[] = [
|
||||
new TestViewMedia({
|
||||
startTime: dateBase,
|
||||
}),
|
||||
@@ -728,6 +740,7 @@ describe('CameraManager', async () => {
|
||||
new TestViewMedia({
|
||||
startTime: add(dateBase, { days: 2 }),
|
||||
}),
|
||||
new ViewFolder(createFolder()),
|
||||
];
|
||||
|
||||
it('without hass', async () => {
|
||||
@@ -748,7 +761,7 @@ describe('CameraManager', async () => {
|
||||
generateViewMediaArray({ count: 5 }),
|
||||
{
|
||||
queries: [{ ...baseEventQuery, limit: 50 }],
|
||||
results: sortMedia(generateViewMediaArray({ count: 5 })),
|
||||
results: sortItems(generateViewMediaArray({ count: 5 })),
|
||||
},
|
||||
],
|
||||
[
|
||||
@@ -759,7 +772,7 @@ describe('CameraManager', async () => {
|
||||
generateViewMediaArray({ count: 5, cameraIDs: ['office'] }),
|
||||
{
|
||||
queries: [{ ...baseEventQuery, limit: 50 }],
|
||||
results: sortMedia(mediaTwoCameras),
|
||||
results: sortItems(mediaTwoCameras),
|
||||
},
|
||||
],
|
||||
[
|
||||
@@ -793,7 +806,7 @@ describe('CameraManager', async () => {
|
||||
mediaTwoCameras,
|
||||
{
|
||||
queries: [{ ...baseEventQuery, limit: 50, start: dateBase }],
|
||||
results: sortMedia(mediaMixedStart.concat(mediaTwoCameras)),
|
||||
results: sortItems(mediaMixedStart.concat(mediaTwoCameras)),
|
||||
},
|
||||
'later' as const,
|
||||
],
|
||||
@@ -811,7 +824,7 @@ describe('CameraManager', async () => {
|
||||
mediaTwoCameras,
|
||||
{
|
||||
queries: [{ ...baseEventQuery, limit: 50, start: dateBase }],
|
||||
results: sortMedia(mediaMixedStart.concat(mediaTwoCameras)),
|
||||
results: sortItems(mediaMixedStart.concat(mediaTwoCameras)),
|
||||
},
|
||||
'earlier' as const,
|
||||
],
|
||||
@@ -823,7 +836,7 @@ describe('CameraManager', async () => {
|
||||
inputQueries: Map<EventQuery, EventQueryResults>,
|
||||
|
||||
// The previously received media.
|
||||
inputMediaResults: ViewMedia[],
|
||||
inputResults: ViewItem[],
|
||||
|
||||
// The queries expected to be dispatched.
|
||||
newChunkQueries: EventQuery[],
|
||||
@@ -834,7 +847,7 @@ describe('CameraManager', async () => {
|
||||
// The expect extended queries and results.
|
||||
expected?: {
|
||||
queries: EventQuery[];
|
||||
results: ViewMedia[];
|
||||
results: ViewItem[];
|
||||
} | null,
|
||||
direction?: 'earlier' | 'later',
|
||||
) => {
|
||||
@@ -853,7 +866,7 @@ describe('CameraManager', async () => {
|
||||
expect(
|
||||
await manager.extendMediaQueries(
|
||||
[...inputQueries.keys()],
|
||||
inputMediaResults,
|
||||
inputResults,
|
||||
direction ?? 'later',
|
||||
),
|
||||
).toEqual(expected);
|
||||
@@ -895,7 +908,7 @@ describe('CameraManager', async () => {
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
|
||||
expect(await manager.initializeCamerasFromConfig()).toBeTruthy();
|
||||
|
||||
const result: CameraEndpoint = {
|
||||
const result: Endpoint = {
|
||||
endpoint: 'http://localhost/path/to/media',
|
||||
};
|
||||
vi.mocked(engine.getMediaDownloadPath).mockResolvedValue(result);
|
||||
@@ -919,7 +932,7 @@ describe('CameraManager', async () => {
|
||||
|
||||
expect(await manager.initializeCamerasFromConfig()).toBeTruthy();
|
||||
|
||||
const result: CameraManagerMediaCapabilities = {
|
||||
const result: ViewItemCapabilities = {
|
||||
canFavorite: false,
|
||||
canDownload: false,
|
||||
};
|
||||
|
||||
@@ -6,14 +6,14 @@ import { CameraProxyConfig } from '../../../src/camera-manager/types';
|
||||
import { ActionsExecutor } from '../../../src/card-controller/actions/types';
|
||||
import { StateWatcher } from '../../../src/card-controller/hass/state-watcher';
|
||||
import { ProxyConfig } from '../../../src/config/schema/cameras';
|
||||
import { EntityRegistryManagerLive } from '../../../src/utils/ha/registry/entity';
|
||||
import { EntityRegistryManagerLive } from '../../../src/ha/registry/entity';
|
||||
import { EntityRegistryManagerMock } from '../../ha/registry/entity/mock';
|
||||
import {
|
||||
createCameraConfig,
|
||||
createHASS,
|
||||
createRegistryEntity,
|
||||
createStateEntity,
|
||||
} from '../../test-utils';
|
||||
import { EntityRegistryManagerMock } from '../../utils/ha/registry/entity/mock';
|
||||
|
||||
describe('ReolinkCamera', () => {
|
||||
const cameraEntity = createRegistryEntity({
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
vi,
|
||||
} from 'vitest';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import { RequestCache } from '../../../src/camera-manager/cache';
|
||||
import {
|
||||
ReolinkCameraManagerEngine,
|
||||
ReolinkQueryResultsClassifier,
|
||||
@@ -17,6 +16,7 @@ import {
|
||||
import { ReolinkEventQueryResults } from '../../../src/camera-manager/reolink/types';
|
||||
import { CameraManagerStore } from '../../../src/camera-manager/store';
|
||||
import {
|
||||
CameraManagerRequestCache,
|
||||
Engine,
|
||||
EventQuery,
|
||||
QueryResultsType,
|
||||
@@ -24,14 +24,12 @@ import {
|
||||
QueryType,
|
||||
} from '../../../src/camera-manager/types';
|
||||
import { StateWatcher } from '../../../src/card-controller/hass/state-watcher';
|
||||
import { BrowseMediaManager } from '../../../src/utils/ha/browse-media/browse-media-manager';
|
||||
import {
|
||||
BrowseMedia,
|
||||
browseMediaSchema,
|
||||
} from '../../../src/utils/ha/browse-media/types';
|
||||
import { EntityRegistryManager } from '../../../src/utils/ha/registry/entity/types';
|
||||
import { ResolvedMediaCache } from '../../../src/utils/ha/resolved-media';
|
||||
import { homeAssistantWSRequest } from '../../../src/utils/ha/ws-request';
|
||||
import { BrowseMedia, browseMediaSchema } from '../../../src/ha/browse-media/types';
|
||||
import { BrowseMediaWalker } from '../../../src/ha/browse-media/walker';
|
||||
import { EntityRegistryManager } from '../../../src/ha/registry/entity/types';
|
||||
import { ResolvedMediaCache } from '../../../src/ha/resolved-media';
|
||||
import { homeAssistantWSRequest } from '../../../src/ha/ws-request';
|
||||
import { EntityRegistryManagerMock } from '../../ha/registry/entity/mock';
|
||||
import {
|
||||
createCamera,
|
||||
createCameraConfig,
|
||||
@@ -39,9 +37,8 @@ import {
|
||||
createRegistryEntity,
|
||||
createStore,
|
||||
} from '../../test-utils';
|
||||
import { EntityRegistryManagerMock } from '../../utils/ha/registry/entity/mock';
|
||||
|
||||
vi.mock('../../../src/utils/ha/ws-request');
|
||||
vi.mock('../../../src/ha/ws-request');
|
||||
|
||||
const TEST_CAMERAS: BrowseMedia = {
|
||||
title: 'Reolink',
|
||||
@@ -181,15 +178,15 @@ const TEST_FILES: BrowseMedia = {
|
||||
};
|
||||
|
||||
const createEngine = (options?: {
|
||||
browseMediaManager?: BrowseMediaManager;
|
||||
browseMediaManager?: BrowseMediaWalker;
|
||||
entityRegistryManager?: EntityRegistryManager;
|
||||
}): ReolinkCameraManagerEngine => {
|
||||
return new ReolinkCameraManagerEngine(
|
||||
options?.entityRegistryManager ?? new EntityRegistryManagerMock(),
|
||||
mock<StateWatcher>(),
|
||||
options?.browseMediaManager ?? new BrowseMediaManager(),
|
||||
options?.browseMediaManager ?? new BrowseMediaWalker(),
|
||||
new ResolvedMediaCache(),
|
||||
new RequestCache(),
|
||||
new CameraManagerRequestCache(),
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ import { CameraManagerEngineFactory } from '../../src/camera-manager/engine-fact
|
||||
import { CameraManagerStore } from '../../src/camera-manager/store.js';
|
||||
import { Engine } from '../../src/camera-manager/types.js';
|
||||
import { StateWatcherSubscriptionInterface } from '../../src/card-controller/hass/state-watcher.js';
|
||||
import { EntityRegistryManager } from '../../src/utils/ha/registry/entity/types.js';
|
||||
import { ResolvedMediaCache } from '../../src/utils/ha/resolved-media.js';
|
||||
import { EntityRegistryManager } from '../../src/ha/registry/entity/types.js';
|
||||
import { ResolvedMediaCache } from '../../src/ha/resolved-media.js';
|
||||
import { TestViewMedia, createCameraConfig } from '../test-utils.js';
|
||||
|
||||
describe('CameraManagerStore', async () => {
|
||||
@@ -139,14 +139,22 @@ describe('CameraManagerStore', async () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('getCameraConfigForMedia', async () => {
|
||||
const store = setupStore();
|
||||
describe('getCameraConfigForMedia', () => {
|
||||
it('should return the camera config for media with a camera ID', async () => {
|
||||
const store = setupStore();
|
||||
|
||||
const media_1 = new TestViewMedia({ cameraID: 'camera-visible' });
|
||||
expect(store.getCameraConfigForMedia(media_1)).toBe(configVisible);
|
||||
const media_1 = new TestViewMedia({ cameraID: 'camera-visible' });
|
||||
expect(store.getCameraConfigForMedia(media_1)).toBe(configVisible);
|
||||
|
||||
const media_2 = new TestViewMedia({ cameraID: 'camera-not-exist' });
|
||||
expect(store.getCameraConfigForMedia(media_2)).toBeNull();
|
||||
const media_2 = new TestViewMedia({ cameraID: 'camera-not-exist' });
|
||||
expect(store.getCameraConfigForMedia(media_2)).toBeNull();
|
||||
});
|
||||
|
||||
it('should return null as the camera config for media without a camera ID', async () => {
|
||||
const store = setupStore();
|
||||
const media = new TestViewMedia({ cameraID: null });
|
||||
expect(store.getCameraConfigForMedia(media)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
it('getEngineOfType', async () => {
|
||||
@@ -199,10 +207,18 @@ describe('CameraManagerStore', async () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('getEngineForMedia', async () => {
|
||||
const store = setupStore();
|
||||
const media = new TestViewMedia({ cameraID: 'camera-visible' });
|
||||
expect(store.getEngineForMedia(media)).toBe(engineGeneric);
|
||||
describe('getEngineForMedia', () => {
|
||||
it('should return the engine for media with a camera ID', async () => {
|
||||
const store = setupStore();
|
||||
const media = new TestViewMedia({ cameraID: 'camera-visible' });
|
||||
expect(store.getEngineForMedia(media)).toBe(engineGeneric);
|
||||
});
|
||||
|
||||
it('should return null as the engine for media without a camera ID', async () => {
|
||||
const store = setupStore();
|
||||
const media = new TestViewMedia({ cameraID: null });
|
||||
expect(store.getEngineForMedia(media)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAllDependentCameras', () => {
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { sortMedia } from '../../../src/camera-manager/utils/sort-media.js';
|
||||
import { TestViewMedia } from '../../test-utils.js';
|
||||
|
||||
describe('sortMedia', () => {
|
||||
const media_1 = new TestViewMedia({
|
||||
id: 'id-1',
|
||||
startTime: new Date('2023-04-29T14:25'),
|
||||
cameraID: 'camera-1',
|
||||
});
|
||||
const media_2 = new TestViewMedia({
|
||||
id: 'id-2',
|
||||
startTime: new Date('2023-04-29T14:26'),
|
||||
cameraID: 'camera-1',
|
||||
});
|
||||
const media_3_dup_id = new TestViewMedia({
|
||||
id: 'id-2',
|
||||
startTime: new Date('2023-04-29T14:26'),
|
||||
cameraID: 'camera-1',
|
||||
});
|
||||
const media_4_no_id = new TestViewMedia({
|
||||
id: null,
|
||||
startTime: new Date('2023-04-29T14:27'),
|
||||
cameraID: 'camera-1',
|
||||
});
|
||||
|
||||
it('should sort sorted media', () => {
|
||||
const media = [media_1, media_2];
|
||||
expect(sortMedia(media)).toEqual(media);
|
||||
});
|
||||
it('should sort unsorted media', () => {
|
||||
expect(sortMedia([media_2, media_1])).toEqual([media_1, media_2]);
|
||||
});
|
||||
it('should remove duplicate id', () => {
|
||||
expect(sortMedia([media_1, media_2, media_3_dup_id])).toEqual([media_1, media_2]);
|
||||
});
|
||||
it('should sort by id when time not available', () => {
|
||||
expect(
|
||||
sortMedia([
|
||||
new TestViewMedia({ id: 'snake' }),
|
||||
new TestViewMedia({ id: 'zebra' }),
|
||||
new TestViewMedia({ id: 'aardvark' }),
|
||||
]),
|
||||
).toEqual([
|
||||
new TestViewMedia({ id: 'aardvark' }),
|
||||
new TestViewMedia({ id: 'snake' }),
|
||||
new TestViewMedia({ id: 'zebra' }),
|
||||
]);
|
||||
});
|
||||
it('should remove de-duplicate by object if no id', () => {
|
||||
expect(sortMedia([media_1, media_2, media_4_no_id, media_4_no_id])).toEqual([
|
||||
media_1,
|
||||
media_2,
|
||||
media_4_no_id,
|
||||
]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user