121 lines
4.5 KiB
JavaScript
121 lines
4.5 KiB
JavaScript
'use strict';
|
|
|
|
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const { createState, reconcile, summarize } = require('../src/scheduler');
|
|
|
|
const options = { maxChannels: 3, startGraceMs: 24 * 60 * 60 * 1000 };
|
|
|
|
function makeEvent(id, startIso, endIso) {
|
|
return {
|
|
key: id,
|
|
title: id,
|
|
league: 'Jupiler Pro League',
|
|
language: 'nl',
|
|
startAt: new Date(startIso),
|
|
endAt: new Date(endIso),
|
|
sources: [{ streamId: id, name: id, candidates: [`http://upstream/${id}.ts`] }],
|
|
missingCount: 0,
|
|
};
|
|
}
|
|
|
|
function current(...events) {
|
|
return new Map(events.map((event) => [event.key, event]));
|
|
}
|
|
|
|
function slotKeys(state) {
|
|
return state.slots.map((slot) => slot.eventKey);
|
|
}
|
|
|
|
test('fills the first three slots by start time and queues the rest', () => {
|
|
const state = createState(3);
|
|
const events = current(
|
|
makeEvent('A', '2026-11-06T16:00:00Z', '2026-11-06T20:00:00Z'),
|
|
makeEvent('B', '2026-11-06T16:30:00Z', '2026-11-06T20:30:00Z'),
|
|
makeEvent('C', '2026-11-06T17:00:00Z', '2026-11-06T21:00:00Z'),
|
|
makeEvent('D', '2026-11-06T17:30:00Z', '2026-11-06T21:30:00Z'),
|
|
makeEvent('E', '2026-11-06T18:00:00Z', '2026-11-06T22:00:00Z')
|
|
);
|
|
|
|
reconcile(state, events, Date.parse('2026-11-06T18:00:00Z'), options);
|
|
|
|
assert.deepEqual(slotKeys(state), ['A', 'B', 'C']);
|
|
const summary = summarize(state);
|
|
assert.deepEqual(summary.queue.map((event) => event.key), ['D', 'E']);
|
|
});
|
|
|
|
test('a freed slot is filled by the oldest waiting event', () => {
|
|
const state = createState(3);
|
|
const events = current(
|
|
makeEvent('A', '2026-11-06T16:00:00Z', '2026-11-06T20:00:00Z'),
|
|
makeEvent('B', '2026-11-06T16:30:00Z', '2026-11-06T20:30:00Z'),
|
|
makeEvent('C', '2026-11-06T17:00:00Z', '2026-11-06T21:00:00Z'),
|
|
makeEvent('D', '2026-11-06T17:30:00Z', '2026-11-06T21:30:00Z'),
|
|
makeEvent('E', '2026-11-06T18:00:00Z', '2026-11-06T22:00:00Z')
|
|
);
|
|
|
|
reconcile(state, events, Date.parse('2026-11-06T18:00:00Z'), options);
|
|
assert.deepEqual(slotKeys(state), ['A', 'B', 'C']);
|
|
|
|
reconcile(state, events, Date.parse('2026-11-06T20:01:00Z'), options);
|
|
assert.deepEqual(slotKeys(state), ['D', 'B', 'C']);
|
|
});
|
|
|
|
test('an existing assignment is not preempted by an earlier event', () => {
|
|
const state = createState(3);
|
|
const events = current(
|
|
makeEvent('A', '2026-11-06T16:00:00Z', '2026-11-06T20:00:00Z'),
|
|
makeEvent('B', '2026-11-06T16:30:00Z', '2026-11-06T20:30:00Z'),
|
|
makeEvent('C', '2026-11-06T17:00:00Z', '2026-11-06T21:00:00Z')
|
|
);
|
|
reconcile(state, events, Date.parse('2026-11-06T18:00:00Z'), options);
|
|
|
|
const withEarlier = current(
|
|
...events.values(),
|
|
makeEvent('X', '2026-11-06T17:45:00Z', '2026-11-06T21:45:00Z')
|
|
);
|
|
reconcile(state, withEarlier, Date.parse('2026-11-06T18:10:00Z'), options);
|
|
|
|
assert.deepEqual(slotKeys(state), ['A', 'B', 'C']);
|
|
});
|
|
|
|
test('an assigned event keeps its slot until its end even when it disappears', () => {
|
|
const state = createState(3);
|
|
const a = makeEvent('A', '2026-11-06T16:00:00Z', '2026-11-06T20:00:00Z');
|
|
const b = makeEvent('B', '2026-11-06T16:30:00Z', '2026-11-06T20:30:00Z');
|
|
reconcile(state, current(a, b), Date.parse('2026-11-06T18:00:00Z'), options);
|
|
assert.deepEqual(slotKeys(state), ['A', 'B', null]);
|
|
|
|
reconcile(state, current(), Date.parse('2026-11-06T19:00:00Z'), options);
|
|
reconcile(state, current(), Date.parse('2026-11-06T19:30:00Z'), options);
|
|
assert.deepEqual(slotKeys(state), ['A', 'B', null]);
|
|
|
|
reconcile(state, current(), Date.parse('2026-11-06T20:31:00Z'), options);
|
|
assert.deepEqual(slotKeys(state), [null, null, null]);
|
|
});
|
|
|
|
test('an unassigned event that disappears twice is dropped from the queue', () => {
|
|
const state = createState(3);
|
|
const events = current(
|
|
makeEvent('A', '2026-11-06T16:00:00Z', '2026-11-06T20:00:00Z'),
|
|
makeEvent('B', '2026-11-06T16:30:00Z', '2026-11-06T20:30:00Z'),
|
|
makeEvent('C', '2026-11-06T17:00:00Z', '2026-11-06T21:00:00Z'),
|
|
makeEvent('D', '2026-11-06T17:30:00Z', '2026-11-06T21:30:00Z'),
|
|
makeEvent('E', '2026-11-06T18:00:00Z', '2026-11-06T22:00:00Z')
|
|
);
|
|
|
|
reconcile(state, events, Date.parse('2026-11-06T18:00:00Z'), options);
|
|
const withoutD = current(
|
|
events.get('A'),
|
|
events.get('B'),
|
|
events.get('C'),
|
|
events.get('E')
|
|
);
|
|
|
|
reconcile(state, withoutD, Date.parse('2026-11-06T18:20:00Z'), options);
|
|
reconcile(state, withoutD, Date.parse('2026-11-06T18:40:00Z'), options);
|
|
reconcile(state, withoutD, Date.parse('2026-11-06T20:01:00Z'), options);
|
|
|
|
assert.deepEqual(slotKeys(state), ['E', 'B', 'C']);
|
|
});
|