feat: Add support for Frigate reviews / detections [initial PR] (#2315)
- Add support for Frigate reviews / detections. - Add support for GenAI metadata. - Significant internal refactor to more flexible "UnifiedQuery" to allow mixing cameras with simple metadata and review metadata (e.g. a timeline view of a Frigate camera with reviews, and a Reolink camera with simple metadata). - Add support for folder media as camera media. There are a few more PRs to commit prior to this going live, but commiting this for now due to the scale of the change. BREAKING CHANGE: `media_type` and `events_type` are retired under `live`, `viewer` and `timeline` configuration sections, instead media type is associated (once) with the camera under `media`.
This commit is contained in:
@@ -4,6 +4,7 @@ import { FoldersExecutor } from '../../../src/card-controller/folders/executor';
|
||||
import { FoldersManager } from '../../../src/card-controller/folders/manager';
|
||||
import { FolderQuery } from '../../../src/card-controller/folders/types';
|
||||
import { FolderConfig, FolderConfigWithoutID } from '../../../src/config/schema/folders';
|
||||
import { QuerySource } from '../../../src/query-source';
|
||||
import { ResolvedMediaCache } from '../../../src/ha/resolved-media';
|
||||
import { Endpoint } from '../../../src/types';
|
||||
import { ViewFolder } from '../../../src/view/item';
|
||||
@@ -99,6 +100,39 @@ describe('FoldersManager', () => {
|
||||
expect(manager.getFolderCount()).toBe(0);
|
||||
});
|
||||
|
||||
describe('getDefaultQueryParameters', () => {
|
||||
it('should return null with no folder provided and no internal folders', () => {
|
||||
const manager = new FoldersManager(createCardAPI());
|
||||
expect(manager.getDefaultQueryParameters()).toBeNull();
|
||||
});
|
||||
|
||||
it('should use provided folder', () => {
|
||||
const folder = createFolder();
|
||||
const executor = mock<FoldersExecutor>();
|
||||
const manager = new FoldersManager(createCardAPI(), executor);
|
||||
const query: FolderQuery = { source: QuerySource.Folder, folder, path: [{}] };
|
||||
|
||||
executor.getDefaultQueryParameters.mockReturnValue(query);
|
||||
|
||||
expect(manager.getDefaultQueryParameters(folder)).toEqual(query);
|
||||
expect(executor.getDefaultQueryParameters).toBeCalledWith(folder);
|
||||
});
|
||||
|
||||
it('should fallback to default folder if none provided', () => {
|
||||
const folder = createFolder();
|
||||
const executor = mock<FoldersExecutor>();
|
||||
const manager = new FoldersManager(createCardAPI(), executor);
|
||||
|
||||
const query: FolderQuery = { source: QuerySource.Folder, folder, path: [{}] };
|
||||
|
||||
manager.addFolders([folder]);
|
||||
executor.getDefaultQueryParameters.mockReturnValue(query);
|
||||
|
||||
expect(manager.getDefaultQueryParameters()).toEqual(query);
|
||||
expect(executor.getDefaultQueryParameters).toBeCalledWith(manager.getFolder());
|
||||
});
|
||||
});
|
||||
|
||||
describe('should get folders', () => {
|
||||
it('should get default folder', () => {
|
||||
const manager = new FoldersManager(createCardAPI());
|
||||
@@ -132,63 +166,15 @@ describe('FoldersManager', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('generateDefaultFolderQuery', () => {
|
||||
it('should generate default folder query with implicit folder', () => {
|
||||
const folder: FolderConfig = createFolder();
|
||||
const query: FolderQuery = {
|
||||
folder,
|
||||
path: [{ ha: { id: 'media-source://' } }],
|
||||
};
|
||||
|
||||
const executor = mock<FoldersExecutor>();
|
||||
vi.mocked(executor.generateDefaultFolderQuery).mockReturnValue(query);
|
||||
|
||||
const manager = new FoldersManager(createCardAPI(), executor);
|
||||
manager.addFolders([folder]);
|
||||
|
||||
expect(manager.generateDefaultFolderQuery()).toEqual(query);
|
||||
});
|
||||
|
||||
it('should generate default folder query without any folder', () => {
|
||||
const folder: FolderConfig = createFolder();
|
||||
const query: FolderQuery = {
|
||||
folder,
|
||||
path: [{ ha: { id: 'media-source://' } }],
|
||||
};
|
||||
|
||||
const executor = mock<FoldersExecutor>();
|
||||
vi.mocked(executor.generateDefaultFolderQuery).mockReturnValue(query);
|
||||
|
||||
const manager = new FoldersManager(createCardAPI(), executor);
|
||||
// Folder is not added to the manager.
|
||||
|
||||
expect(manager.generateDefaultFolderQuery()).toBeNull();
|
||||
});
|
||||
|
||||
it('should generate default folder query with explicit folder', () => {
|
||||
const folder: FolderConfig = createFolder();
|
||||
const query: FolderQuery = {
|
||||
folder,
|
||||
path: [{ ha: { id: 'media-source://' } }],
|
||||
};
|
||||
|
||||
const executor = mock<FoldersExecutor>();
|
||||
vi.mocked(executor.generateDefaultFolderQuery).mockReturnValue(query);
|
||||
|
||||
const manager = new FoldersManager(createCardAPI(), executor);
|
||||
|
||||
expect(manager.generateDefaultFolderQuery(folder)).toEqual(query);
|
||||
});
|
||||
});
|
||||
|
||||
describe('generateChildFolderQuery', () => {
|
||||
it('should generate child folder query', () => {
|
||||
const folder: FolderConfig = createFolder();
|
||||
const query: FolderQuery = {
|
||||
source: QuerySource.Folder,
|
||||
folder,
|
||||
path: [{ ha: { id: 'media-source://' } }],
|
||||
};
|
||||
const viewFolder = new ViewFolder(folder);
|
||||
const viewFolder = new ViewFolder(folder, []);
|
||||
|
||||
const executor = mock<FoldersExecutor>();
|
||||
vi.mocked(executor.generateChildFolderQuery).mockReturnValue(query);
|
||||
@@ -216,6 +202,7 @@ describe('FoldersManager', () => {
|
||||
const conditionState = {};
|
||||
const engineOptions = {};
|
||||
const query: FolderQuery = {
|
||||
source: QuerySource.Folder,
|
||||
folder,
|
||||
path: [{ ha: { id: 'media-source://' } }],
|
||||
};
|
||||
@@ -240,6 +227,7 @@ describe('FoldersManager', () => {
|
||||
|
||||
expect(
|
||||
await manager.expandFolder({
|
||||
source: QuerySource.Folder,
|
||||
folder,
|
||||
path: [{ ha: { id: 'media-source://' } }],
|
||||
}),
|
||||
@@ -247,6 +235,26 @@ describe('FoldersManager', () => {
|
||||
|
||||
expect(executor.expandFolder).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should return null for query with unsupported filters', async () => {
|
||||
const hass = createHASS();
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(hass);
|
||||
|
||||
const executor = mock<FoldersExecutor>();
|
||||
const manager = new FoldersManager(api, executor);
|
||||
|
||||
const folder = createFolder({ id: 'folder-1' });
|
||||
const query: FolderQuery = {
|
||||
source: QuerySource.Folder,
|
||||
folder,
|
||||
path: [{ ha: { id: 'media-source://' } }],
|
||||
favorite: true,
|
||||
};
|
||||
|
||||
expect(await manager.expandFolder(query)).toBeNull();
|
||||
expect(executor.expandFolder).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('should get item capabilities', () => {
|
||||
@@ -302,4 +310,23 @@ describe('FoldersManager', () => {
|
||||
expect(executor.favorite).toBeCalledWith(hass, item, true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('areResultsFresh', () => {
|
||||
it('should delegate freshness check to executor', () => {
|
||||
const api = createCardAPI();
|
||||
const executor = mock<FoldersExecutor>();
|
||||
const manager = new FoldersManager(api, executor);
|
||||
const query: FolderQuery = {
|
||||
source: QuerySource.Folder,
|
||||
folder: createFolder(),
|
||||
path: [{}],
|
||||
};
|
||||
const timestamp = new Date();
|
||||
|
||||
executor.areResultsFresh.mockReturnValue(true);
|
||||
|
||||
expect(manager.areResultsFresh(timestamp, query)).toBe(true);
|
||||
expect(executor.areResultsFresh).toHaveBeenCalledWith(timestamp, query);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user