Lumi/scripts/verify-content-library.js
2026-07-20 23:23:39 +02:00

158 lines
7.8 KiB
JavaScript

const assert = require("assert");
const fs = require("fs");
const path = require("path");
const root = path.join(__dirname, "..");
const sandbox = fs.mkdtempSync(path.join(root, ".tmp-lumi-content-library-"));
const serviceDir = path.join(sandbox, "src", "services");
fs.mkdirSync(serviceDir, { recursive: true });
for (const file of [
"config.js",
"db.js",
"settings.js",
"upload-security.js",
"content-library.js",
"content-routes.js"
]) {
fs.copyFileSync(path.join(root, "src", "services", file), path.join(serviceDir, file));
}
let database = null;
(async () => {
try {
database = require(path.join(serviceDir, "db.js"));
// Core imports the Web server before main() runs migrate(). The content
// service must therefore be safe to load without touching database tables.
const library = require(path.join(serviceDir, "content-library.js"));
const routes = require(path.join(serviceDir, "content-routes.js"));
database.migrate();
const settings = require(path.join(serviceDir, "settings.js"));
settings.ensureDefaults();
settings.setSetting("session_secret", "content-library-verification-secret");
assert(database.db.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'content_resources'").get());
library.ensureContentLibrary();
assert(library.supportedExtensions().includes(".mp4"));
assert(library.supportedExtensions().includes(".mp3"));
assert(library.supportedExtensions().includes(".webm"));
assert(library.supportedExtensions().includes(".png"));
assert(library.supportedExtensions().includes(".woff2"));
const pngPath = path.join(library.INCOMING_DIR, "verify.png.upload");
fs.writeFileSync(
pngPath,
Buffer.from(
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAusB9Y9ZK1sAAAAASUVORK5CYII=",
"base64"
)
);
const [created] = await library.importUploadedFiles([
{
path: pngPath,
originalname: "Alert Graphic.png",
mimetype: "image/png"
}
], { access_level: "locked", uploaded_by: "verify-admin" });
assert.strictEqual(created.access_level, "locked");
assert.strictEqual(created.category, "image");
assert.strictEqual(created.preview_kind, "image");
assert.strictEqual(library.publicUrl(created, "https://lumi.example"), null);
assert(fs.existsSync(library.resourceFilePath(created)));
assert.match(library.serializeResource(created, { base_url: "https://lumi.example" }).download_url, /\/admin\/resources\/[^/]+\/download\/Alert%20Graphic\.png$/);
const signed = new URL(library.createSignedUrl(created.id, {
base_url: "https://lumi.example",
ttl_seconds: 60
}));
const signedResource = library.verifySignedResource(
created.id,
signed.searchParams.get("expires"),
signed.searchParams.get("sig")
);
assert.strictEqual(signedResource.id, created.id);
const exposed = library.setResourceAccess(created.id, "exposed");
const publicUrl = new URL(library.publicUrl(exposed, "https://lumi.example"));
const publicToken = publicUrl.pathname.split("/")[2];
assert(publicToken.length >= 40);
assert.strictEqual(library.getResourceByExposedToken(publicToken).id, created.id);
const renamed = library.updateResourceName(created.id, "Follower alert");
assert.strictEqual(renamed.display_name, "Follower alert");
assert.strictEqual(library.listResources({ category: "image" }).length, 1);
assert.deepStrictEqual(routes.parseByteRange("bytes=0-9", 100), { start: 0, end: 9 });
assert.deepStrictEqual(routes.parseByteRange("bytes=-10", 100), { start: 90, end: 99 });
assert.strictEqual(routes.parseByteRange("bytes=200-300", 100).invalid, true);
assert.strictEqual(routes.parseByteRange("bytes=0-1,4-5", 100).invalid, true);
const stats = library.getStorageStats();
assert.strictEqual(stats.file_count, 1);
assert(stats.used_bytes > 0);
assert(stats.max_file_bytes > 0);
const configuredStorage = library.updateStorageSettings({
limit_gib: "2.5",
reserve_gib: "0.25",
max_file_mib: "32",
max_files: "7"
});
assert.strictEqual(configuredStorage.limit_bytes, Math.floor(2.5 * 1024 ** 3));
assert.strictEqual(configuredStorage.reserve_bytes, Math.floor(0.25 * 1024 ** 3));
assert.strictEqual(configuredStorage.max_file_bytes, 32 * 1024 ** 2);
assert.strictEqual(configuredStorage.max_files, 7);
assert.deepStrictEqual(library.uploadLimits(), { max_file_bytes: 32 * 1024 ** 2, max_files: 7 });
const lockedAgain = library.setResourceAccess(created.id, "locked");
assert.strictEqual(library.getResourceByExposedToken(publicToken), null);
assert.strictEqual(lockedAgain.public_token, "");
const storedPath = library.resourceFilePath(created);
library.deleteResource(created.id);
assert.strictEqual(fs.existsSync(storedPath), false);
assert.strictEqual(library.getResource(created.id), null);
assert.strictEqual(library.getStorageStats().file_count, 0);
const serverSource = fs.readFileSync(path.join(root, "src", "web", "server.js"), "utf8");
assert(serverSource.includes("registerPublicContentRoutes(app)"));
assert(serverSource.includes("registerContentAdminRoutes(app"));
assert(serverSource.includes('path: "/admin/resources"'));
assert(serverSource.includes("global.lumiFrameworks.resources"));
const overlayRouteSource = fs.readFileSync(path.join(root, "src", "services", "overlay-routes.js"), "utf8");
assert(overlayRouteSource.includes("media-src https: http: data: blob:"), "OBS overlay CSP must allow configured media sources");
const viewSource = fs.readFileSync(path.join(root, "src", "web", "views", "admin-resources.ejs"), "utf8");
assert(viewSource.includes("data-resource-dropzone"));
assert(viewSource.includes("data-resource-preview-modal"));
assert(viewSource.includes("data-resource-media-toggle") && viewSource.includes("data-resource-media-seek"));
assert(viewSource.includes("data-resource-rename-submit") && viewSource.includes("resource-more-actions"));
assert(viewSource.includes("Available to Lumi"));
assert(viewSource.includes("data-resource-access-form") && viewSource.includes("data-confirm-mode=\"modal\"") && viewSource.includes("data-no-destructive-confirm"));
const contentRouteSource = fs.readFileSync(path.join(root, "src", "services", "content-routes.js"), "utf8");
assert(contentRouteSource.includes('/admin/resources/:id/revoke'));
assert(contentRouteSource.includes('/admin/resources/:id/download/:filename'));
const clientSource = fs.readFileSync(path.join(root, "src", "web", "public", "content-library.js"), "utf8");
assert(clientSource.includes("window.LumiConfirm?.destructiveToken"), "Resource actions must use the shared timed confirmation API");
assert(clientSource.includes('headers["X-Confirmation-Token"] = token'), "Resource actions must send the issued confirmation token directly");
assert(clientSource.includes("data-resource-upload-limit-copy") && clientSource.includes("selectedFilesError"), "Saved upload limits must be reflected and enforced in the UI");
const cssSource = fs.readFileSync(path.join(root, "src", "web", "public", "content-library.css"), "utf8");
assert(cssSource.includes(".resource-empty-state[hidden]") && cssSource.includes("display: none !important"));
assert(cssSource.includes(".resource-preview-tile:focus-within .resource-media-controls"));
const coreKnowledge = fs.readFileSync(path.join(root, "knowledge", "core", "lumi-core.md"), "utf8");
assert(coreKnowledge.includes("GET /admin/resources") && coreKnowledge.includes("GET /media/:token/:filename"));
console.log("Content-library verification passed.");
} finally {
try { database?.db?.close(); } catch {}
fs.rmSync(sandbox, { recursive: true, force: true });
}
})().catch((error) => {
console.error(error);
process.exitCode = 1;
});