openvidu/openvidu-dashboard/dashboard-ce/backend/test/unit/ingress.test.ts

97 lines
3.6 KiB
TypeScript

import type * as z from 'zod';
import { describe, expect, it } from 'vitest';
import type { IngressDocument } from '@openvidu-dashboard/typings';
import { toIngressListItem } from '../../src/ingress/ingress-mappers.js';
import { activityRange, ingressesListPlan } from '../../src/ingress/ingress-query.js';
import { ingressListItem } from '../../src/ingress/ingress-responses.js';
import { ingressesListQuery } from '../../src/ingress/ingress-schemas.js';
import { loadFixture } from '../support/fixtures.js';
const NOW = new Date('2026-10-13T12:00:00Z');
const list = ingressesListQuery(() => NOW);
const ingresses = loadFixture<IngressDocument>('ingresses');
function fixture(id: string): IngressDocument {
const doc = ingresses.find((candidate) => candidate._id === id);
if (doc === undefined) {
throw new Error(`the fixtures need ${id}`);
}
return structuredClone(doc);
}
function firstIssue(schema: z.ZodType, value: unknown) {
const result = schema.safeParse(value);
return result.success
? undefined
: { path: result.error.issues[0]?.path, message: result.error.issues[0]?.message };
}
describe('ingress mappers', () => {
it('lists the public fields of a definition with its error flag', () => {
const item = toIngressListItem({ ...fixture('IN_0bsRtmpReu5e'), status: 'inactive', lastError: 'boom' });
expect(item).toMatchObject({ ingressId: 'IN_0bsRtmpReu5e', status: 'inactive', hasError: true });
for (const key of ['url', 'streamKey', 'participantName', 'schemaVersion', 'expiresAt', 'lastError']) {
expect(item).not.toHaveProperty(key);
}
for (const doc of ingresses) {
const mapped = toIngressListItem(doc);
expect(ingressListItem.strict().parse(mapped)).toEqual(mapped);
}
});
});
describe('ingress schemas and list plan', () => {
it('rejects the invalid parameters on their own path', () => {
expect(firstIssue(list, { status: 'active' })?.path).toEqual(['status']);
expect(firstIssue(list, { inputType: 'srt' })?.path).toEqual(['inputType']);
expect(firstIssue(list, { sort: 'lastSessionAt' })?.path).toEqual(['sort']);
expect(firstIssue(list, { q: 'c' })).toEqual({ path: ['q'], message: 'must have at least two characters' });
});
it('selects the activity range with the filters and ranks every sort key', () => {
const params = (query: Record<string, string>) => list.parse(query);
const plan = (query: Record<string, string>) => ingressesListPlan(params(query));
const range = (query: Record<string, string>) => activityRange(params(query).from, params(query).to);
expect(plan({})).toMatchObject({ kind: 'ranked', match: range({}) });
expect(plan({ q: 'Ca', status: 'error', inputType: 'whip' })).toMatchObject({
kind: 'ranked',
match: { ...range({}), nameLower: { $regex: '^ca' }, status: 'error', inputType: 'whip' }
});
expect(plan({ q: 'IN_ab12cd', status: 'publishing' })).toEqual({ kind: 'id', filter: { _id: 'IN_ab12cd' } });
expect(range({}).$or).toHaveLength(3);
const asOf = new Date('2026-10-13T12:00:00.000Z');
const rank = (sort: string) => {
const result = plan({ sort });
return result.kind === 'ranked' ? result.rank(asOf) : undefined;
};
expect(rank('createdAt')).toBe('$createdAt');
expect(rank('sessions')).toEqual({ $ifNull: ['$sessions', 0] });
expect(rank('lastSession')).toEqual({ $ifNull: ['$lastSessionAt', new Date(0)] });
expect(rank('totalDuration')).toMatchObject({
$add: [
{ $ifNull: ['$totalDurationMs', 0] },
{
$cond: [
{ $eq: ['$status', 'publishing'] },
{ $subtract: [asOf, { $ifNull: ['$lastSessionAt', asOf] }] },
0
]
}
]
});
expect(() => list.parse({ sort: 'lastSessionAt' })).toThrow();
});
});