Lumi/scripts/verify-stream-testing.js
2026-07-25 00:04:03 +02:00

96 lines
5.6 KiB
JavaScript

const assert = require("assert");
const fs = require("fs");
const path = require("path");
const root = path.join(__dirname, "..");
const { ladderFor, ffmpegArgs, streamTestingService, resolveFfmpegExecutable } = require("../src/services/stream-testing");
const protocol = require("../plugins/lumi_transcription/backend/companion/protocol");
async function main() {
assert.equal(typeof resolveFfmpegExecutable(), "string");
const fullHd = ladderFor({ width: 1920, height: 1080, fps: 60 });
assert.deepStrictEqual(fullHd.variants.map((item) => item.name), ["source", "720p", "480p"]);
assert(fullHd.variants.every((item) => item.width <= 1920 && item.height <= 1080), "adaptive ladder must never upscale");
const hd = ladderFor({ width: 1280, height: 720, fps: 30 });
assert.deepStrictEqual(hd.variants.map((item) => item.name), ["source", "480p"]);
const sd = ladderFor({ width: 854, height: 480, fps: 30 });
assert.deepStrictEqual(sd.variants.map((item) => item.name), ["source"]);
const fakeSession = {
source: fullHd,
receiver: { encoder: "h264_nvenc" },
listenerUrl: "rtmp://0.0.0.0:19350/lumi-test/private-secret",
directory: path.join(root, ".tmp-stream-test"),
};
const args = ffmpegArgs(fakeSession);
assert(args.includes("h264_nvenc") && args.includes("master.m3u8"));
assert(args.includes("v:0,a:0,name:source v:1,a:1,name:720p v:2,a:2,name:480p"));
assert(args.includes("delete_segments+independent_segments+program_date_time+temp_file"));
assert(args.includes("512") && args.includes("1024"), "receiver queues must be bounded");
for (const type of ["stream_test_create", "stream_test_obs_metrics", "stream_test_caption", "stream_test_stop"]) {
const parsed = protocol.parseEnvelope(Buffer.from(JSON.stringify(protocol.envelope(type, {}, null))));
assert.strictEqual(parsed.type, type);
}
const server = fs.readFileSync(path.join(root, "src/web/server.js"), "utf8");
const service = fs.readFileSync(path.join(root, "src/services/stream-testing.js"), "utf8");
const gateway = fs.readFileSync(path.join(root, "plugins/lumi_transcription/backend/companion/gateway.js"), "utf8");
const nativeBridge = fs.readFileSync(path.join(root, "companion/native/obs-bridge/src/plugin.cpp"), "utf8");
const companion = fs.readFileSync(path.join(root, "companion/src/Lumi.Companion.App/CompanionRuntime.cs"), "utf8");
const companionUi = fs.readFileSync(path.join(root, "companion/src/Lumi.Companion.App/MainWindow.axaml.cs"), "utf8");
const companionView = fs.readFileSync(path.join(root, "companion/src/Lumi.Companion.App/MainWindow.axaml"), "utf8");
const bridgeManager = fs.readFileSync(path.join(root, "companion/src/Lumi.Companion.App/ObsBridgeManager.cs"), "utf8");
const webUi = fs.readFileSync(path.join(root, "src/web/views/admin-stream-testing.ejs"), "utf8");
assert.match(server, /admin\/stream-testing", requireRole\("admin"\)/);
assert.match(server, /admin\/stream-testing\/media\/:id\/:name", requireRole\("admin"\)/);
assert.match(service, /MAX_SESSION_MS/);
assert.match(service, /INACTIVITY_MS/);
assert.match(service, /MAX_OUTPUT_BYTES/);
assert.match(service, /redactDiagnostic/);
assert.match(service, /caption_delay/);
assert.match(service, /receiver_slow/);
assert.match(service, /PUBLIC_INGEST_PORT/);
assert.match(service, /Microsoft", "WinGet", "Packages/);
assert.strictEqual(/^(?:master|stream_(?:source|720p|480p))(?:\.m3u8|_\d{6}\.ts)$/.test("master.m3u8"), true);
assert.match(service, /spawn\(FFMPEG, args, \{[^}]*stdio:/s);
assert.match(service, /testsrc2=size=1280x720:rate=30/);
assert.match(service, /sine=frequency=880:sample_rate=48000/);
assert(!service.includes("shell: true"), "receiver commands must not use a shell");
assert.match(gateway, /stream_test_create/);
assert.match(gateway, /Companion disconnected; the private receiver was stopped/);
assert.match(nativeBridge, /stream_test_snapshot/);
assert.match(nativeBridge, /obs_frontend_save_streaming_service/);
assert.match(nativeBridge, /stream_test_restore/);
assert(!nativeBridge.includes("obs_service_release(existing)"), "OBS frontend service getters return borrowed references that must not be released");
assert.match(nativeBridge, /obs_frontend_set_streaming_service\(test\)[\s\S]*obs_service_release\(test\)/);
assert.match(nativeBridge, /obs_frontend_set_streaming_service\(restored\)[\s\S]*obs_service_release\(restored\)/);
assert.match(companion, /StreamTestRecoveryStore/);
assert.match(companion, /StartStreamTestAsync/);
assert.match(companion, /RestoreObsAfterStreamTestAsync/);
assert.match(companion, /finally\s*\{\s*Interlocked\.Exchange\(ref _streamTestRestoreRunning, 0\)/s);
assert.match(companion, /StreamTestDetail = streamTestPending/);
assert.match(companion, /stream_test_start_failed/);
assert.match(companionUi, /CheckUpdateButton\.IsEnabled = !state\.UpdateCheckRunning/);
assert.match(companionUi, /CheckUpdateButton\.Content = state\.UpdateCheckRunning \? "Checking…" : "Check now"/);
assert.match(companionUi, /ObsBridgeUpdateAvailable/);
assert.match(companionUi, /Update OBS Bridge/);
assert.match(companionView, /OverviewBridgeUpdatePanel/);
assert.match(companionView, /ConnectionBridgeUpdatePanel/);
assert.match(bridgeManager, /InstalledVersion/);
assert.match(bridgeManager, /UpdateAvailable/);
assert.match(webUi, /data-stream-player/);
assert.match(webUi, /data-stream-quality/);
assert.match(webUi, /data-stream-captions/);
assert.match(webUi, /data-stream-timeline/);
await streamTestingService.close();
console.log("Stream testing and Companion lifecycle verification passed.");
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});