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

95 lines
3.7 KiB
TypeScript

import * as z from 'zod';
import { describe, expect, it } from 'vitest';
import { csvParam, limitParam, orderParam, qParam, timeRangeQuery, tzParam } from '../../src/api/query.js';
import { isTimeZone, resolveTimeExpression } from '../../src/api/time-expression.js';
const NOW = new Date('2026-10-13T12:00:00Z');
const range = timeRangeQuery(() => NOW);
function issues(schema: z.ZodType, value: unknown) {
const result = schema.safeParse(value);
return result.success ? [] : result.error.issues.map((issue) => ({ path: issue.path, message: issue.message }));
}
describe('time expressions', () => {
it('resolves relative expressions at request time', () => {
expect(range.parse({ from: 'now-24h', to: 'now' })).toEqual({
from: new Date('2026-10-12T12:00:00Z'),
to: NOW,
tz: 'UTC'
});
expect(range.parse({})).toEqual({ from: new Date('2026-10-12T12:00:00Z'), to: NOW, tz: 'UTC' });
expect(resolveTimeExpression('now-90m', NOW, 'UTC')).toEqual(new Date('2026-10-13T10:30:00Z'));
expect(resolveTimeExpression('now-7d', NOW, 'UTC')).toEqual(new Date('2026-10-06T12:00:00Z'));
});
it('snaps to the start of the month and day in the caller zone', () => {
expect(range.parse({ from: 'now/M', tz: 'Europe/Madrid' }).from).toEqual(new Date('2026-09-30T22:00:00Z'));
expect(resolveTimeExpression('now/d', NOW, 'America/New_York')).toEqual(new Date('2026-10-13T04:00:00Z'));
expect(resolveTimeExpression('now/d', NOW, 'UTC')).toEqual(new Date('2026-10-13T00:00:00Z'));
});
it('accepts ISO 8601 with an offset or Z only', () => {
expect(resolveTimeExpression('2026-10-13T10:00:00.250Z', NOW, 'UTC')).toEqual(
new Date('2026-10-13T10:00:00.250Z')
);
expect(resolveTimeExpression('2026-10-13T12:00:00+02:00', NOW, 'UTC')).toEqual(
new Date('2026-10-13T10:00:00Z')
);
expect(resolveTimeExpression('2026-10-13T10:00:00', NOW, 'UTC')).toBeUndefined();
expect(resolveTimeExpression('yesterday', NOW, 'UTC')).toBeUndefined();
expect(resolveTimeExpression('now-5w', NOW, 'UTC')).toBeUndefined();
});
it('rejects an inverted or empty range on from', () => {
expect(issues(range, { from: '2026-10-13T00:00:00Z', to: '2026-10-12T00:00:00Z' })).toEqual([
{ path: ['from'], message: 'must be before to' }
]);
expect(issues(range, { from: 'now', to: 'now' })[0]?.path).toEqual(['from']);
expect(issues(range, { from: 'later' })[0]?.path).toEqual(['from']);
});
it('validates the time zone', () => {
expect(issues(range, { tz: 'Mars/Olympus' })[0]?.path).toEqual(['tz']);
expect(tzParam.parse(undefined)).toBe('UTC');
expect(isTimeZone('Europe/Madrid')).toBe(true);
expect(isTimeZone('Japan')).toBe(true);
expect(isTimeZone('+02:00')).toBe(false);
});
});
describe('list parameters', () => {
it('bounds the page size', () => {
expect(limitParam.parse(undefined)).toBe(25);
expect(limitParam.parse('100')).toBe(100);
for (const value of ['101', '0', 'abc', '2.5']) {
expect(limitParam.safeParse(value).success, value).toBe(false);
}
});
it('reports every invalid parameter at once', () => {
const schema = z.object({ limit: limitParam, order: orderParam.default('desc') });
expect(issues(schema, { limit: '500', order: 'sideways' }).map((issue) => issue.path)).toEqual([
['limit'],
['order']
]);
});
it('trims and bounds the search text', () => {
expect(qParam.parse(' DEMO ')).toBe('DEMO');
expect(qParam.parse(' ')).toBeUndefined();
expect(qParam.safeParse('x'.repeat(101)).success).toBe(false);
});
it('splits comma-separated enumeration values', () => {
const kind = csvParam(['STANDARD', 'EGRESS', 'AGENT']);
expect(kind.parse('EGRESS, AGENT')).toEqual(['EGRESS', 'AGENT']);
expect(kind.safeParse('EGRESS,ROBOT').success).toBe(false);
});
});