2026-10-25 16:35:47 +01:00
|
|
|
import { describe, expect, it } from 'vitest';
|
|
|
|
|
|
|
|
|
|
import { staleFlag, writerBatchInterval } from '../../src/status/freshness.js';
|
|
|
|
|
import { foldWriter } from '../../src/status/status-service.js';
|
|
|
|
|
|
|
|
|
|
const NOW = new Date('2026-10-13T12:00:00.000Z');
|
|
|
|
|
const seen = (secondsAgo: number, batchIntervalMs?: number) => ({
|
|
|
|
|
lastSeenAt: new Date(NOW.getTime() - secondsAgo * 1000),
|
|
|
|
|
...(batchIntervalMs === undefined ? {} : { batchIntervalMs })
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('writer status', () => {
|
openvidu-dashboard: media node labels, and nothing about nodes in CE
The contract gains mediaNode on nodes, rooms and participants, stoppedAt on nodes and the
partial mediaNode index that replaces the nodeId one; the scenario's two servers are
labelled. The status reports the writer without node counts and with stoppedAt when the
server stopped, the rooms list filters by mediaNode, list items carry it, the overview
groups by it for editions and a node scope's media statistics coverage follows it.
CE renders nothing about nodes without per-node-breakdown: no nodes line, By node card,
Node select, scope selector or Node columns, not even locked; the Overview reads "Server
stopped at" for a stopped server. The generator gains --media-nodes, which node rollups
now require.
2026-11-04 12:13:58 +01:00
|
|
|
const stopped = (secondsAgo: number) => {
|
|
|
|
|
const at = new Date(NOW.getTime() - secondsAgo * 1000);
|
|
|
|
|
|
|
|
|
|
return { lastSeenAt: at, stoppedAt: at };
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
it('takes the newest batch', () => {
|
|
|
|
|
expect(foldWriter([seen(50), seen(4), seen(30)], NOW)).toEqual({ lastSeenAt: '2026-10-13T11:59:56.000Z' });
|
2026-10-25 16:35:47 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('reports no batch without nodes', () => {
|
openvidu-dashboard: media node labels, and nothing about nodes in CE
The contract gains mediaNode on nodes, rooms and participants, stoppedAt on nodes and the
partial mediaNode index that replaces the nodeId one; the scenario's two servers are
labelled. The status reports the writer without node counts and with stoppedAt when the
server stopped, the rooms list filters by mediaNode, list items carry it, the overview
groups by it for editions and a node scope's media statistics coverage follows it.
CE renders nothing about nodes without per-node-breakdown: no nodes line, By node card,
Node select, scope selector or Node columns, not even locked; the Overview reads "Server
stopped at" for a stopped server. The generator gains --media-nodes, which node rollups
now require.
2026-11-04 12:13:58 +01:00
|
|
|
expect(foldWriter([], NOW)).toEqual({});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('reports the stop of the newest node when no node is alive', () => {
|
|
|
|
|
expect(foldWriter([stopped(6), seen(3600)], NOW)).toEqual({
|
|
|
|
|
lastSeenAt: '2026-10-13T11:59:54.000Z',
|
|
|
|
|
stoppedAt: '2026-10-13T11:59:54.000Z'
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('reports no stop after a restart', () => {
|
|
|
|
|
expect(foldWriter([stopped(60), seen(3)], NOW)).toEqual({ lastSeenAt: '2026-10-13T11:59:57.000Z' });
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('reports no stop while another node is alive', () => {
|
|
|
|
|
expect(foldWriter([stopped(2), seen(6)], NOW)).toEqual({ lastSeenAt: '2026-10-13T11:59:58.000Z' });
|
2026-10-25 16:35:47 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('judges each node by its own interval and reports the largest one', () => {
|
openvidu-dashboard: media node labels, and nothing about nodes in CE
The contract gains mediaNode on nodes, rooms and participants, stoppedAt on nodes and the
partial mediaNode index that replaces the nodeId one; the scenario's two servers are
labelled. The status reports the writer without node counts and with stoppedAt when the
server stopped, the rooms list filters by mediaNode, list items carry it, the overview
groups by it for editions and a node scope's media statistics coverage follows it.
CE renders nothing about nodes without per-node-breakdown: no nodes line, By node card,
Node select, scope selector or Node columns, not even locked; the Overview reads "Server
stopped at" for a stopped server. The generator gains --media-nodes, which node rollups
now require.
2026-11-04 12:13:58 +01:00
|
|
|
expect(foldWriter([seen(80, 60_000), stopped(120)], NOW)).toEqual({
|
|
|
|
|
lastSeenAt: '2026-10-13T11:58:40.000Z',
|
2026-10-25 16:35:47 +01:00
|
|
|
batchIntervalMs: 60_000
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('writer batch interval', () => {
|
|
|
|
|
it('takes the largest interval of the nodes seen in the last hour', () => {
|
|
|
|
|
expect(writerBatchInterval([seen(5, 10_000), seen(20 * 60, 30_000), seen(2 * 3600, 60_000)], NOW)).toBe(30_000);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('is undefined when no node reports one', () => {
|
|
|
|
|
expect(writerBatchInterval([seen(25)], NOW)).toBeUndefined();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('stale flag', () => {
|
|
|
|
|
const at = (secondsAgo: number) => new Date(NOW.getTime() - secondsAgo * 1000);
|
|
|
|
|
|
|
|
|
|
it('marks an active entity whose last stats flush is older than the window', () => {
|
|
|
|
|
expect(staleFlag('active', at(100), 90_000, NOW)).toBe(true);
|
|
|
|
|
expect(staleFlag('active', at(100), 135_000, NOW)).toBeUndefined();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('never marks a closed entity or one without stats', () => {
|
|
|
|
|
expect(staleFlag('closed', at(7200), 90_000, NOW)).toBeUndefined();
|
|
|
|
|
expect(staleFlag('active', undefined, 90_000, NOW)).toBeUndefined();
|
|
|
|
|
});
|
|
|
|
|
});
|