openvibers/openvibe.media Commits
npm test no longer counts a skipped test as passed (roadmap WS-Q task 7; Shared v1.25.0 openvibe-shared/test-runner). The test script was a shell loop that stopped at the first failure and counted nothing, so a test that printed "⚠️ ffmpeg not found: … skipped" and exited 0 was as good as a pass. test/run.js is now the shared runner: every file, one at a time as before (parallel 1), a 300 s timeout per file, and a summary; a file that prints "<label>: skipped (<why>)" is listed with ○ and not counted as passed, and --strict fails on it. The six tests that skipped in free-form words now print that line: jobs-invariant (split/remux execution), clip-cut-job (clip.cut), jobs-previews (preview jobs), duration-reconcile (measurement checks) and vod-duration (real-file finalize check) without ffmpeg, and trust-proxy (direct-caller check) without a non-loopback interface. openvibe-shared is pinned at v1.25.0 (from v1.24.0), named in the README. npm test (Node 22) 64/64 test files passed.
Changes
@@@ -107,7 +107,7 @@ server/vod.duration.reconcile (stored vs measured durations, local and B2/R2),storage.orphans.scan (monthly storage orphan report, service-wide, report only)client-ip.js trust proxy = loopback; req.ip is the only client address--(openvibe-shared v1.24.0, openvibe-contracts v0.71.0, openvibe-sdk v0.12.0: pinned release tarballs, installed by npm)++(openvibe-shared v1.25.0, openvibe-contracts v0.71.0, openvibe-sdk v0.12.0: pinned release tarballs, installed by npm)scripts/smoke-test.sh end-to-end smoke test (boots a temp instance)scripts/backfill-objects.js / reconcile-objects.js / object-invariant.js / no-good-copy-report.js object-model operator toolsscripts/media-jobs.js list jobs, run the size-invariant scan (dry run by default), approve/cancel proposals@@@ -18,7 +18,7 @@"multer": "^1.4.5-lts.1","openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.71.0","openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.12.0",-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.24.0",++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.25.0","sharp": "^0.33.4"}},@@@ -1944,9 +1944,9 @@}},"node_modules/openvibe-shared": {-- "version": "1.24.0",-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.24.0",-- "integrity": "sha512-VPzafrc07Pw0oqmXioOlDRtlX5xu82jNLBYkVYxXvhjkILMpWsyjExItIRt5Y6l2st0ozHm7bDQFTJpKEShMEA==",++ "version": "1.25.0",++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.25.0",++ "integrity": "sha512-oqZ+PvljrWi8IEbtg6bDYai17ZV2sALFx/ezMptniELSr10TgFUiGUQt+b1IWwiHv0T2Y1U2iSrkirJxMNgjSw==","license": "MIT","dependencies": {"jsonwebtoken": "^9.0.0"@@@ -7,7 +7,7 @@"start": "node server/index.js","init-db": "node server/db/init.js","smoke": "bash scripts/smoke-test.sh",-- "test": "for f in test/*.test.js; do node \"$f\" || exit 1; done",++ "test": "node test/run.js","n-1:record": "node scripts/n-1-record.js"},"license": "MIT",@@@ -21,7 +21,7 @@"dotenv": "^16.4.5","express": "^4.19.2","multer": "^1.4.5-lts.1",-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.24.0",++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.25.0","sharp": "^0.33.4"}}@@@ -33,7 +33,7 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));const waitFor = async (fn, ms = 30000) => { const end = Date.now() + ms; while (Date.now() < end) { if (await fn()) return true; await sleep(25); } return false; };(async () => {-- if (spawnSync('ffmpeg', ['-version']).status !== 0) { console.log('⚠️ ffmpeg not found: clip.cut skipped'); process.exit(0); }++ if (spawnSync('ffmpeg', ['-version']).status !== 0) { console.log('clip.cut: skipped (ffmpeg not found)'); process.exit(0); }await new Promise((r) => stub.listen(0, '127.0.0.1', r));const base = `http://127.0.0.1:${stub.address().port}`;const db = require('../server/db/database');@@@ -93,7 +93,7 @@ const s3 = http.createServer((req, res) => {let ids = {};if (!hasFfmpeg) {-- console.log('⚠️ ffmpeg not found: the measurement checks are skipped (orphan report still runs)');++ console.log('measurement checks: skipped (ffmpeg not found; the orphan report still runs)');} else {const V = process.env.VOD_PATH;mk(path.join(V, 'a.mp4'), 6);@@@ -137,7 +137,7 @@ const hasFfmpeg = spawnSync('ffmpeg', ['-version']).status === 0;assert.deepStrictEqual([r.status, r.body.code], [400, 'media.job.invalid'], 'parts 2-1000');if (!hasFfmpeg) {-- console.log('⚠️ ffmpeg not found: split/remux execution skipped');++ console.log('split/remux execution: skipped (ffmpeg not found)');} else {const src = path.join(process.env.VOD_PATH, 'rec-10.mp4');const mk6 = spawnSync('ffmpeg', ['-v', 'error', '-y', '-f', 'lavfi', '-i', 'testsrc=duration=6:size=160x120:rate=10', '-f', 'lavfi', '-i', 'sine=frequency=440:duration=6',@@@ -41,7 +41,7 @@ const size = (file) => { const r = spawnSync('ffprobe', ['-v', 'error', '-selectassert.deepStrictEqual(spriteLayout(6, { frames: 100, columns: 10, tile_width: 160 }), { count: 3, interval_seconds: 2, columns: 3, rows: 1, tile_width: 160, tile_height: 90 });assert.deepStrictEqual(spriteLayout(43083, { frames: 100, columns: 10, tile_width: 160 }), { count: 100, interval_seconds: 430.83, columns: 10, rows: 10, tile_width: 160, tile_height: 90 });assert.strictEqual(spriteLayout(1, { frames: 100, columns: 10, tile_width: 160 }).count, 1);-- if (!hasFfmpeg) { console.log('⚠️ ffmpeg not found: preview jobs skipped'); process.exit(0); }++ if (!hasFfmpeg) { console.log('preview jobs: skipped (ffmpeg not found)'); process.exit(0); }await new Promise((r) => stub.listen(0, '127.0.0.1', r));const base = `http://127.0.0.1:${stub.address().port}`;nnew file mode 100644@@@ -0,0 +1,15 @@++#!/usr/bin/env node++/**++ * Runs every test in test/ — the files named *.test.js — one at a time, each in its own process, and++ * fails if any of them fails (openvibe-shared/test-runner).++ *++ * npm test # everything++ * npm test -- objects jobs # only files whose name contains one of the words++ * npm test -- --strict # a skipped test fails the run too++ *++ * The tests use temp SQLite databases and local servers on random ports. A few need ffmpeg (clip cuts,++ * previews, remux, duration measurement) or a non-loopback interface; without them they print++ * `<label>: skipped (<why>)`, and that file is listed with ○ and not counted as passed.++ */++'use strict';++require('openvibe-shared/test-runner').main({ dir: __dirname, timeoutMs: 300000, pad: 40, parallel: 1, hide: /^\[DB\] / });@@@ -43,7 +43,7 @@ const externalAddress = () => {assert.ok(r.ip.endsWith(ext) && r.client.endsWith(ext), `a direct caller cannot choose its IP (got ${r.ip})`);console.log('✅ a non-loopback peer\'s X-Forwarded-For / CF-Connecting-IP are ignored');} else {-- console.log('⚠️ no non-loopback interface: the direct-caller check is skipped');++ console.log('direct-caller check: skipped (no non-loopback interface)');}server.close();// The view counter keys on req.ip, not on the headers.@@@ -158,7 +158,7 @@ function restore() { Object.assign(tools, realTools); }// ── 5. Real ffmpeg: an orphaned 6 s recording made 2 days ago finalizes to ~6 s (probe) ──if (spawnSync('ffmpeg', ['-version']).status !== 0) {-- console.log('⚠️ ffmpeg not found: the real-file finalize check is skipped');++ console.log('real-file finalize check: skipped (ffmpeg not found)');} else {const file = path.join(process.env.VOD_PATH, 'real.mp4');const mk = spawnSync('ffmpeg', ['-v', 'error', '-y', '-f', 'lavfi', '-i', 'testsrc=duration=6:size=160x120:rate=10',
@@ -107,7 +107,7 @@ server/
@@ -107,7 +107,7 @@ server/
vod.duration.reconcile (stored vs measured durations, local and B2/R2),
vod.duration.reconcile (stored vs measured durations, local and B2/R2),
storage.orphans.scan (monthly storage orphan report, service-wide, report only)
storage.orphans.scan (monthly storage orphan report, service-wide, report only)
client-ip.js trust proxy = loopback; req.ip is the only client address
client-ip.js trust proxy = loopback; req.ip is the only client address
-(openvibe-shared v1.24.0, openvibe-contracts v0.71.0, openvibe-sdk v0.12.0: pinned release tarballs, installed by npm)
+(openvibe-shared v1.25.0, openvibe-contracts v0.71.0, openvibe-sdk v0.12.0: pinned release tarballs, installed by npm)
scripts/smoke-test.sh end-to-end smoke test (boots a temp instance)
scripts/smoke-test.sh end-to-end smoke test (boots a temp instance)
scripts/backfill-objects.js / reconcile-objects.js / object-invariant.js / no-good-copy-report.js object-model operator tools
scripts/backfill-objects.js / reconcile-objects.js / object-invariant.js / no-good-copy-report.js object-model operator tools
scripts/media-jobs.js list jobs, run the size-invariant scan (dry run by default), approve/cancel proposals
scripts/media-jobs.js list jobs, run the size-invariant scan (dry run by default), approve/cancel proposals
@@ -18,7 +18,7 @@
@@ -18,7 +18,7 @@
"multer": "^1.4.5-lts.1",
"multer": "^1.4.5-lts.1",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.71.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.71.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.12.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.12.0",
- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.24.0",
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.25.0",
"sharp": "^0.33.4"
"sharp": "^0.33.4"
}
}
},
},
@@ -1944,9 +1944,9 @@
@@ -1944,9 +1944,9 @@
}
}
},
},
"node_modules/openvibe-shared": {
"node_modules/openvibe-shared": {
- "version": "1.24.0",
- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.24.0",
- "integrity": "sha512-VPzafrc07Pw0oqmXioOlDRtlX5xu82jNLBYkVYxXvhjkILMpWsyjExItIRt5Y6l2st0ozHm7bDQFTJpKEShMEA==",
+ "version": "1.25.0",
+ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.25.0",
+ "integrity": "sha512-oqZ+PvljrWi8IEbtg6bDYai17ZV2sALFx/ezMptniELSr10TgFUiGUQt+b1IWwiHv0T2Y1U2iSrkirJxMNgjSw==",
"license": "MIT",
"license": "MIT",
"dependencies": {
"dependencies": {
"jsonwebtoken": "^9.0.0"
"jsonwebtoken": "^9.0.0"
@@ -7,7 +7,7 @@
@@ -7,7 +7,7 @@
"start": "node server/index.js",
"start": "node server/index.js",
"init-db": "node server/db/init.js",
"init-db": "node server/db/init.js",
"smoke": "bash scripts/smoke-test.sh",
"smoke": "bash scripts/smoke-test.sh",
- "test": "for f in test/*.test.js; do node \"$f\" || exit 1; done",
+ "test": "node test/run.js",
"n-1:record": "node scripts/n-1-record.js"
"n-1:record": "node scripts/n-1-record.js"
},
},
"license": "MIT",
"license": "MIT",
@@ -21,7 +21,7 @@
@@ -21,7 +21,7 @@
"dotenv": "^16.4.5",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"express": "^4.19.2",
"multer": "^1.4.5-lts.1",
"multer": "^1.4.5-lts.1",
- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.24.0",
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.25.0",
"sharp": "^0.33.4"
"sharp": "^0.33.4"
}
}
}
}
@@ -33,7 +33,7 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
@@ -33,7 +33,7 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const waitFor = async (fn, ms = 30000) => { const end = Date.now() + ms; while (Date.now() < end) { if (await fn()) return true; await sleep(25); } return false; };
const waitFor = async (fn, ms = 30000) => { const end = Date.now() + ms; while (Date.now() < end) { if (await fn()) return true; await sleep(25); } return false; };
(async () => {
(async () => {
- if (spawnSync('ffmpeg', ['-version']).status !== 0) { console.log('⚠️ ffmpeg not found: clip.cut skipped'); process.exit(0); }
+ if (spawnSync('ffmpeg', ['-version']).status !== 0) { console.log('clip.cut: skipped (ffmpeg not found)'); process.exit(0); }
await new Promise((r) => stub.listen(0, '127.0.0.1', r));
await new Promise((r) => stub.listen(0, '127.0.0.1', r));
const base = `http://127.0.0.1:${stub.address().port}`;
const base = `http://127.0.0.1:${stub.address().port}`;
const db = require('../server/db/database');
const db = require('../server/db/database');
@@ -93,7 +93,7 @@ const s3 = http.createServer((req, res) => {
@@ -93,7 +93,7 @@ const s3 = http.createServer((req, res) => {
let ids = {};
let ids = {};
if (!hasFfmpeg) {
if (!hasFfmpeg) {
- console.log('⚠️ ffmpeg not found: the measurement checks are skipped (orphan report still runs)');
+ console.log('measurement checks: skipped (ffmpeg not found; the orphan report still runs)');
} else {
} else {
const V = process.env.VOD_PATH;
const V = process.env.VOD_PATH;
mk(path.join(V, 'a.mp4'), 6);
mk(path.join(V, 'a.mp4'), 6);
@@ -137,7 +137,7 @@ const hasFfmpeg = spawnSync('ffmpeg', ['-version']).status === 0;
@@ -137,7 +137,7 @@ const hasFfmpeg = spawnSync('ffmpeg', ['-version']).status === 0;
assert.deepStrictEqual([r.status, r.body.code], [400, 'media.job.invalid'], 'parts 2-1000');
assert.deepStrictEqual([r.status, r.body.code], [400, 'media.job.invalid'], 'parts 2-1000');
if (!hasFfmpeg) {
if (!hasFfmpeg) {
- console.log('⚠️ ffmpeg not found: split/remux execution skipped');
+ console.log('split/remux execution: skipped (ffmpeg not found)');
} else {
} else {
const src = path.join(process.env.VOD_PATH, 'rec-10.mp4');
const src = path.join(process.env.VOD_PATH, 'rec-10.mp4');
const mk6 = spawnSync('ffmpeg', ['-v', 'error', '-y', '-f', 'lavfi', '-i', 'testsrc=duration=6:size=160x120:rate=10', '-f', 'lavfi', '-i', 'sine=frequency=440:duration=6',
const mk6 = spawnSync('ffmpeg', ['-v', 'error', '-y', '-f', 'lavfi', '-i', 'testsrc=duration=6:size=160x120:rate=10', '-f', 'lavfi', '-i', 'sine=frequency=440:duration=6',
@@ -41,7 +41,7 @@ const size = (file) => { const r = spawnSync('ffprobe', ['-v', 'error', '-select
@@ -41,7 +41,7 @@ const size = (file) => { const r = spawnSync('ffprobe', ['-v', 'error', '-select
assert.deepStrictEqual(spriteLayout(6, { frames: 100, columns: 10, tile_width: 160 }), { count: 3, interval_seconds: 2, columns: 3, rows: 1, tile_width: 160, tile_height: 90 });
assert.deepStrictEqual(spriteLayout(6, { frames: 100, columns: 10, tile_width: 160 }), { count: 3, interval_seconds: 2, columns: 3, rows: 1, tile_width: 160, tile_height: 90 });
assert.deepStrictEqual(spriteLayout(43083, { frames: 100, columns: 10, tile_width: 160 }), { count: 100, interval_seconds: 430.83, columns: 10, rows: 10, tile_width: 160, tile_height: 90 });
assert.deepStrictEqual(spriteLayout(43083, { frames: 100, columns: 10, tile_width: 160 }), { count: 100, interval_seconds: 430.83, columns: 10, rows: 10, tile_width: 160, tile_height: 90 });
assert.strictEqual(spriteLayout(1, { frames: 100, columns: 10, tile_width: 160 }).count, 1);
assert.strictEqual(spriteLayout(1, { frames: 100, columns: 10, tile_width: 160 }).count, 1);
- if (!hasFfmpeg) { console.log('⚠️ ffmpeg not found: preview jobs skipped'); process.exit(0); }
+ if (!hasFfmpeg) { console.log('preview jobs: skipped (ffmpeg not found)'); process.exit(0); }
await new Promise((r) => stub.listen(0, '127.0.0.1', r));
await new Promise((r) => stub.listen(0, '127.0.0.1', r));
const base = `http://127.0.0.1:${stub.address().port}`;
const base = `http://127.0.0.1:${stub.address().port}`;
new file mode 100644
new file mode 100644
@@ -0,0 +1,15 @@
@@ -0,0 +1,15 @@
+#!/usr/bin/env node
+/**
+ * Runs every test in test/ — the files named *.test.js — one at a time, each in its own process, and
+ * fails if any of them fails (openvibe-shared/test-runner).
+ *
+ * npm test # everything
+ * npm test -- objects jobs # only files whose name contains one of the words
+ * npm test -- --strict # a skipped test fails the run too
+ *
+ * The tests use temp SQLite databases and local servers on random ports. A few need ffmpeg (clip cuts,
+ * previews, remux, duration measurement) or a non-loopback interface; without them they print
+ * `<label>: skipped (<why>)`, and that file is listed with ○ and not counted as passed.
+ */
+'use strict';
+require('openvibe-shared/test-runner').main({ dir: __dirname, timeoutMs: 300000, pad: 40, parallel: 1, hide: /^\[DB\] / });
@@ -43,7 +43,7 @@ const externalAddress = () => {
@@ -43,7 +43,7 @@ const externalAddress = () => {
assert.ok(r.ip.endsWith(ext) && r.client.endsWith(ext), `a direct caller cannot choose its IP (got ${r.ip})`);
assert.ok(r.ip.endsWith(ext) && r.client.endsWith(ext), `a direct caller cannot choose its IP (got ${r.ip})`);
console.log('✅ a non-loopback peer\'s X-Forwarded-For / CF-Connecting-IP are ignored');
console.log('✅ a non-loopback peer\'s X-Forwarded-For / CF-Connecting-IP are ignored');
} else {
} else {
- console.log('⚠️ no non-loopback interface: the direct-caller check is skipped');
+ console.log('direct-caller check: skipped (no non-loopback interface)');
}
}
server.close();
server.close();
// The view counter keys on req.ip, not on the headers.
// The view counter keys on req.ip, not on the headers.
@@ -158,7 +158,7 @@ function restore() { Object.assign(tools, realTools); }
@@ -158,7 +158,7 @@ function restore() { Object.assign(tools, realTools); }
// ── 5. Real ffmpeg: an orphaned 6 s recording made 2 days ago finalizes to ~6 s (probe) ──
// ── 5. Real ffmpeg: an orphaned 6 s recording made 2 days ago finalizes to ~6 s (probe) ──
if (spawnSync('ffmpeg', ['-version']).status !== 0) {
if (spawnSync('ffmpeg', ['-version']).status !== 0) {
- console.log('⚠️ ffmpeg not found: the real-file finalize check is skipped');
+ console.log('real-file finalize check: skipped (ffmpeg not found)');
} else {
} else {
const file = path.join(process.env.VOD_PATH, 'real.mp4');
const file = path.join(process.env.VOD_PATH, 'real.mp4');
const mk = spawnSync('ffmpeg', ['-v', 'error', '-y', '-f', 'lavfi', '-i', 'testsrc=duration=6:size=160x120:rate=10',
const mk = spawnSync('ffmpeg', ['-v', 'error', '-y', '-f', 'lavfi', '-i', 'testsrc=duration=6:size=160x120:rate=10',