Files
bitvid/tests/services/playbackService_order.test.mjs
T
google-labs-jules[bot] c22037a575 feat: make default playback source configurable
- Added `DEFAULT_PLAYBACK_SOURCE` to `config/instance-config.js` to allow choosing between "url" (default) and "torrent" priority.
- Updated `js/constants.js` to derive `URL_FIRST_ENABLED` flag from the configuration.
- Refactored `PlaybackService.execute()` in `js/services/playbackService.js` to robustly support "Torrent First" logic with fallback to URL, while maintaining "URL First" with fallback to Torrent.
- Added unit tests in `tests/services/playbackService_order.test.mjs` to verify playback order and fallback mechanics.
2026-01-26 15:13:56 +00:00

228 lines
7.1 KiB
JavaScript

import { describe, test, mock, before, after, beforeEach, afterEach } from "node:test";
import assert from "node:assert/strict";
import { PlaybackService } from "../../js/services/playbackService.js";
describe("PlaybackService Ordering", () => {
let video;
let window;
before(() => {
// Mock global window/document
global.window = {
HTMLMediaElement: {
HAVE_NOTHING: 0,
HAVE_METADATA: 1,
HAVE_CURRENT_DATA: 2,
HAVE_FUTURE_DATA: 3,
HAVE_ENOUGH_DATA: 4,
},
Event: class Event { constructor(type) { this.type = type; } },
localStorage: {
getItem: () => null,
setItem: () => {},
removeItem: () => {}
}
};
global.document = {
createElement: (tag) => {
if (tag === 'video') return video;
return {};
}
};
window = global.window;
});
after(() => {
delete global.window;
delete global.document;
});
describe("Playback Execution Order", () => {
let playMock;
let loadMock;
let pauseMock;
let eventListeners;
beforeEach(() => {
mock.timers.enable({ apis: ['setTimeout'] });
eventListeners = {};
video = {
play: mock.fn(async () => {}),
load: mock.fn(),
pause: mock.fn(),
removeAttribute: mock.fn(),
addEventListener: (event, handler) => {
if (!eventListeners[event]) eventListeners[event] = [];
eventListeners[event].push(handler);
},
removeEventListener: (event, handler) => {
if (eventListeners[event]) {
eventListeners[event] = eventListeners[event].filter(h => h !== handler);
}
},
dispatchEvent: (event) => {
if (eventListeners[event.type]) {
eventListeners[event.type].forEach(h => h(event));
}
},
readyState: 0,
networkState: 0,
dataset: {},
muted: false
};
playMock = video.play;
loadMock = video.load;
pauseMock = video.pause;
});
afterEach(() => {
mock.timers.reset();
});
test("urlFirstEnabled=true: Tries URL first, succeeds", async () => {
const service = new PlaybackService({
logger: () => {},
urlFirstEnabled: true,
isValidMagnetUri: () => true,
});
const probeUrl = mock.fn(async () => ({ outcome: "good", status: 200 }));
const playViaWebTorrent = mock.fn(async () => ({ infoHash: "123" }));
const session = service.createSession({
url: "https://example.com/vid.mp4",
magnet: "magnet:?xt=urn:btih:123",
videoElement: video,
probeUrl,
playViaWebTorrent,
});
const startPromise = session.start();
await new Promise(resolve => process.nextTick(resolve));
await new Promise(resolve => process.nextTick(resolve));
video.dispatchEvent(new window.Event("playing"));
const result = await startPromise;
assert.equal(probeUrl.mock.callCount(), 1, "Should probe URL");
assert.equal(playViaWebTorrent.mock.callCount(), 0, "Should NOT call torrent");
assert.equal(result.source, "url");
});
test("urlFirstEnabled=true: Tries URL first, fails, falls back to Torrent", async () => {
const service = new PlaybackService({
logger: () => {},
urlFirstEnabled: true,
isValidMagnetUri: () => true,
});
// Probe returns error
const probeUrl = mock.fn(async () => ({ outcome: "bad", status: 404 }));
const playViaWebTorrent = mock.fn(async () => ({ infoHash: "123" }));
const session = service.createSession({
url: "https://example.com/vid.mp4",
magnet: "magnet:?xt=urn:btih:123",
videoElement: video,
probeUrl,
playViaWebTorrent,
});
const result = await session.start();
assert.equal(probeUrl.mock.callCount(), 1, "Should probe URL");
assert.equal(playViaWebTorrent.mock.callCount(), 1, "Should fall back to torrent");
assert.equal(result.source, "torrent");
});
test("urlFirstEnabled=false: Tries Torrent first, succeeds", async () => {
const service = new PlaybackService({
logger: () => {},
urlFirstEnabled: false,
isValidMagnetUri: () => true,
});
const probeUrl = mock.fn(async () => ({ outcome: "good", status: 200 }));
const playViaWebTorrent = mock.fn(async () => ({ infoHash: "123" }));
const session = service.createSession({
url: "https://example.com/vid.mp4",
magnet: "magnet:?xt=urn:btih:123",
videoElement: video,
probeUrl,
playViaWebTorrent,
});
const result = await session.start();
assert.equal(playViaWebTorrent.mock.callCount(), 1, "Should call torrent first");
assert.equal(probeUrl.mock.callCount(), 0, "Should NOT probe URL");
assert.equal(result.source, "torrent");
});
test("urlFirstEnabled=false: Tries Torrent first, fails (throws), falls back to URL", async () => {
const service = new PlaybackService({
logger: () => {},
urlFirstEnabled: false,
isValidMagnetUri: () => true,
torrentClient: { cleanup: mock.fn() } // Mock cleanup
});
const probeUrl = mock.fn(async () => ({ outcome: "good", status: 200 }));
const playViaWebTorrent = mock.fn(async () => {
throw new Error("Torrent failed");
});
const session = service.createSession({
url: "https://example.com/vid.mp4",
magnet: "magnet:?xt=urn:btih:123",
videoElement: video,
probeUrl,
playViaWebTorrent,
});
const startPromise = session.start();
// Wait for async operations
await new Promise(resolve => process.nextTick(resolve));
await new Promise(resolve => process.nextTick(resolve));
// Simulate successful playback for URL
video.dispatchEvent(new window.Event("playing"));
const result = await startPromise;
assert.equal(playViaWebTorrent.mock.callCount(), 1, "Should call torrent first");
assert.equal(probeUrl.mock.callCount(), 1, "Should fallback to probe URL");
assert.equal(result.source, "url");
});
test("forcedSource=torrent overrides urlFirstEnabled=true", async () => {
const service = new PlaybackService({
logger: () => {},
urlFirstEnabled: true,
isValidMagnetUri: () => true,
});
const probeUrl = mock.fn();
const playViaWebTorrent = mock.fn(async () => ({ infoHash: "123" }));
const session = service.createSession({
url: "https://example.com/vid.mp4",
magnet: "magnet:?xt=urn:btih:123",
videoElement: video,
probeUrl,
playViaWebTorrent,
forcedSource: "torrent"
});
const result = await session.start();
assert.equal(playViaWebTorrent.mock.callCount(), 1);
assert.equal(probeUrl.mock.callCount(), 0);
assert.equal(result.source, "torrent");
});
});
});