const assert = require("assert"); const fs = require("fs"); const os = require("os"); const path = require("path"); const root = path.join(__dirname, ".."); const { dependencyIssues, ensureRuntimeDependencies } = require("../src/services/dependency-manager"); const { targetForRequestedVersion } = require("../src/services/repo-update"); const { buildStatus } = require("../src/services/update-index"); const { normalizeRepositoryRef } = require("../src/services/update-repository"); const { buildCorePreservePaths } = require("../src/services/update-manager"); function readJson(relativePath) { return JSON.parse(fs.readFileSync(path.join(root, relativePath), "utf8")); } const releaseIndex = readJson("release-index.json"); const releaseVersions = releaseIndex.releases.map((release) => release.version); assert.deepEqual(releaseVersions, ["0.2.2", "0.2.1", "0.2.0", "0.1.9"]); assert.equal(new Set(releaseVersions).size, releaseVersions.length, "release versions must be unique"); for (const release of releaseIndex.releases) { assert.equal(normalizeRepositoryRef(release.ref), release.ref); assert.match(release.ref, /^refs\/tags\/v\d+\.\d+\.\d+$/); assert.equal(release.data_policy, "preserve"); } const currentRelease = releaseIndex.releases[0]; assert.equal(currentRelease.version, readJson("package.json").version); assert.deepEqual(currentRelease.replaces_versions, ["1.2.0"]); for (const [pluginId, version] of Object.entries(currentRelease.plugins)) { assert.equal(readJson(`plugins/${pluginId}/plugin.json`).version, version, `${pluginId} release catalog version`); } for (const [toolId, version] of Object.entries(currentRelease.tools)) { assert.equal(readJson(`plugins/${toolId}/tool_info.json`).version, version, `${toolId} release catalog version`); } const baseTarget = { current_version: "0.2.0", available_versions: [ { version: "0.2.2", ref: "refs/tags/v0.2.2", rollback_safe: true }, { version: "0.2.1", ref: "refs/tags/v0.2.1", rollback_safe: true }, { version: "0.2.0", ref: "refs/tags/v0.2.0", rollback_safe: true }, { version: "0.1.9", ref: "refs/tags/v0.1.9", rollback_safe: true } ], warnings: [] }; const selected = targetForRequestedVersion(baseTarget, "0.1.9", "Core version"); assert.equal(selected.safe_target_version, "0.1.9"); assert.equal(selected.source_branch, "refs/tags/v0.1.9"); assert.equal(selected.update_available, true); assert.equal(selected.requires_manual_confirmation, true); assert.throws(() => targetForRequestedVersion(baseTarget, "9.9.9", "Core version"), /not listed/); assert.throws(() => normalizeRepositoryRef("--upload-pack=bad"), /Invalid/); assert.throws(() => normalizeRepositoryRef("refs/tags/../main"), /Invalid/); const corrected = buildStatus({ kind: "core", currentVersion: "1.2.0", manifest: readJson("update-manifest.json"), changelog: [], sourceBranch: "main", channel: "stable" }); assert.equal(corrected.version_correction, true); assert.equal(corrected.safe_target_version, "0.2.2"); assert.equal(corrected.update_available, true); assert.equal(corrected.blocked, false); const preserve = buildCorePreservePaths(root); for (const required of ["data", "plugins", "node_modules", "knowledge/community", "knowledge/corrections", ".env"]) { assert.equal(preserve.has(required), true, `${required} must survive core updates`); } const serverSource = fs.readFileSync(path.join(root, "src", "web", "server.js"), "utf8"); assert.doesNotMatch(serverSource, /require\([^\n]*plugins\/okf\/backend/); assert.match(serverSource, /global\.lumiFrameworks\?\.okf\?\.saveCorrection/); const repoUpdateSource = fs.readFileSync(path.join(root, "src", "services", "repo-update.js"), "utf8"); assert.match(repoUpdateSource, /Plugin update failed while \$\{stage\}/); assert.match(repoUpdateSource, /last_update_stage: stage/); assert.match(repoUpdateSource, /stage = "preparing the selected repository version"[\s\S]*verifyPluginFiles\(pluginId, managed\.path, target\.safe_target_version\)[\s\S]*stage = "creating the rollback snapshot"/); assert.match(repoUpdateSource, /stage = "replacing plugin files"[\s\S]*applyPluginFiles\(path\.join\(managed\.path, "plugins", pluginId\)/); assert.match(repoUpdateSource, /last_update_stage: "complete",\s*last_error: null/); const connectorSource = fs.readFileSync(path.join(root, "src", "services", "overlay-connectors.js"), "utf8"); assert.match(connectorSource, /try\s*{[\s\S]*require\("obs-websocket-js"\)/); const fixture = fs.mkdtempSync(path.join(os.tmpdir(), "lumi-dependencies-")); try { fs.writeFileSync(path.join(fixture, "package.json"), JSON.stringify({ dependencies: { example: "^1.0.0" } })); fs.writeFileSync(path.join(fixture, "package-lock.json"), JSON.stringify({ lockfileVersion: 3, packages: { "": {}, "node_modules/example": { version: "1.2.3" } } })); assert.deepEqual(dependencyIssues(fixture), [{ dependency: "example", reason: "missing", expected: "1.2.3", optional: false }]); let invocation = null; const result = ensureRuntimeDependencies({ rootPath: fixture, platform: "linux", runner(command, args) { invocation = { command, args }; const packageDir = path.join(fixture, "node_modules", "example"); fs.mkdirSync(packageDir, { recursive: true }); fs.writeFileSync(path.join(packageDir, "package.json"), JSON.stringify({ version: "1.2.3" })); return { status: 0, stdout: "", stderr: "" }; } }); assert.equal(result.ready, true); assert.equal(result.installed, true); assert.equal(invocation.args.includes("ci"), true); assert.deepEqual(dependencyIssues(fixture), []); } finally { fs.rmSync(fixture, { recursive: true, force: true }); } const optionalFixture = fs.mkdtempSync(path.join(os.tmpdir(), "lumi-optional-dependency-")); try { fs.writeFileSync(path.join(optionalFixture, "package.json"), JSON.stringify({ optionalDependencies: { optional: "^1.0.0" } })); fs.writeFileSync(path.join(optionalFixture, "package-lock.json"), JSON.stringify({ lockfileVersion: 3, packages: { "": {}, "node_modules/optional": { version: "1.0.0", optional: true } } })); const degraded = ensureRuntimeDependencies({ rootPath: optionalFixture, platform: "linux", runner: () => ({ status: 1, stderr: "offline" }) }); assert.equal(degraded.ready, true); assert.equal(degraded.optional_failed, true); } finally { fs.rmSync(optionalFixture, { recursive: true, force: true }); } console.log("Update system verification passed: immutable refs, exact versions, preserved data, dependency repair, and optional boundaries.");