Provide a pure persisted policy contract for optional app-side throughput limits and local download windows. Cover overnight timing, local Date DST behavior, invalid values, and manual window overrides without creating Streamlink arguments.
87 lines
3.4 KiB
TypeScript
87 lines
3.4 KiB
TypeScript
import { describe, expect, test } from 'vitest';
|
|
import {
|
|
decideDownloadStart,
|
|
isWithinLocalDownloadWindow,
|
|
normalizeDownloadPolicy,
|
|
} from './download-policy';
|
|
|
|
describe('download policy normalization', () => {
|
|
test('keeps the persistent shape for a valid app-side throttle and local windows', () => {
|
|
const policy = normalizeDownloadPolicy({
|
|
throttle: { maxBytesPerSecond: 524_288 },
|
|
windows: [{ start: '22:00', end: '06:00' }, { start: '09:30', end: '12:00' }]
|
|
});
|
|
|
|
expect(policy).toEqual({
|
|
throttle: { maxBytesPerSecond: 524_288 },
|
|
windows: [{ start: '22:00', end: '06:00' }, { start: '09:30', end: '12:00' }]
|
|
});
|
|
expect(JSON.parse(JSON.stringify(policy))).toEqual(policy);
|
|
});
|
|
|
|
test('drops invalid throttle limits instead of inventing a process argument', () => {
|
|
for (const maxBytesPerSecond of [0, -1, 1.5, Number.POSITIVE_INFINITY, '500000']) {
|
|
expect(normalizeDownloadPolicy({ throttle: { maxBytesPerSecond } }).throttle).toBeNull();
|
|
}
|
|
});
|
|
|
|
test('drops invalid local window records from the persisted shape', () => {
|
|
expect(normalizeDownloadPolicy({
|
|
windows: [
|
|
{ start: '9:00', end: '12:00' },
|
|
{ start: '12:00', end: '12:00' },
|
|
{ start: '24:00', end: '01:00' },
|
|
{ start: '08:00', end: '10:30' }
|
|
]
|
|
}).windows).toEqual([{ start: '08:00', end: '10:30' }]);
|
|
});
|
|
});
|
|
|
|
describe('local download windows', () => {
|
|
test('accepts both sides of an overnight window', () => {
|
|
const window = { start: '22:00', end: '06:00' };
|
|
|
|
expect(isWithinLocalDownloadWindow(new Date(2026, 0, 12, 23, 30), window)).toBe(true);
|
|
expect(isWithinLocalDownloadWindow(new Date(2026, 0, 13, 5, 30), window)).toBe(true);
|
|
expect(isWithinLocalDownloadWindow(new Date(2026, 0, 13, 6, 0), window)).toBe(false);
|
|
});
|
|
|
|
test('waits for the next overnight start after the morning close', () => {
|
|
const decision = decideDownloadStart(
|
|
normalizeDownloadPolicy({ windows: [{ start: '22:00', end: '06:00' }] }),
|
|
new Date(2026, 0, 13, 7, 15)
|
|
);
|
|
|
|
expect(decision).toMatchObject({ allowed: false, reason: 'outside-window' });
|
|
expect(decision.nextStart).toEqual(new Date(2026, 0, 13, 22, 0));
|
|
});
|
|
|
|
test('uses local Date normalization when the next start falls in a DST gap', () => {
|
|
const now = new Date(2026, 2, 29, 1, 45);
|
|
const decision = decideDownloadStart(
|
|
normalizeDownloadPolicy({ windows: [{ start: '02:30', end: '04:00' }] }),
|
|
now
|
|
);
|
|
|
|
expect(decision).toMatchObject({ allowed: false, reason: 'outside-window' });
|
|
expect(decision.nextStart).toEqual(new Date(2026, 2, 29, 2, 30));
|
|
});
|
|
});
|
|
|
|
describe('manual download policy override', () => {
|
|
test('allows a manual start outside the configured window without removing the app-side throttle', () => {
|
|
const policy = normalizeDownloadPolicy({
|
|
throttle: { maxBytesPerSecond: 256_000 },
|
|
windows: [{ start: '22:00', end: '06:00' }]
|
|
});
|
|
const decision = decideDownloadStart(policy, new Date(2026, 0, 13, 13, 0), true);
|
|
|
|
expect(decision).toEqual({
|
|
allowed: true,
|
|
reason: 'manual-override',
|
|
maxBytesPerSecond: 256_000,
|
|
nextStart: null
|
|
});
|
|
});
|
|
});
|