import assert from "node:assert/strict"; import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import test from "node:test"; import { AppRuntimeConfig } from "../main/config/runtime-config"; import { loadUpdateSettings, readUpdateFileConfig } from "../main/updates/update-config"; const baseConfig: AppRuntimeConfig = { title: "Scoreko", userModelId: "com.scoreko.desktop", userDataDirectoryName: "scoreko", nodecgPort: "9090", bundleName: "scoreko-dev", mainDashboardRoute: "dashboard/scoreko-dev/main.html?standalone=true", loadingDashboardRoute: "dashboard/loading/main.html?standalone=true", loadDelayMs: 0, startupTimeoutMs: 30000, nodecgKillTimeoutMs: 2500, updatesEnabled: true, updateCheckDelayMs: 5000, }; test("loadUpdateSettings keeps updates disabled when the runtime config disables them", () => { const rootPath = makeTempRoot({ enabled: true, apiUrl: "https://gitea.local/releases/latest", }); const settings = loadUpdateSettings({ ...baseConfig, updatesEnabled: false }, rootPath, () => undefined); assert.equal(settings.enabled, false); assert.equal(settings.apiUrl, "https://gitea.local/releases/latest"); }); test("loadUpdateSettings fails closed on insecure production update URLs", () => { const rootPath = makeTempRoot({ enabled: true, apiUrl: "http://gitea.local/releases/latest", }); const settings = loadUpdateSettings(baseConfig, rootPath, () => undefined, { allowInsecureHttp: false }); assert.equal(settings.enabled, false); assert.equal(settings.apiUrl, undefined); }); test("loadUpdateSettings lets runtime config override file settings", () => { const rootPath = makeTempRoot({ enabled: true, apiUrl: "https://file.local/releases/latest", releasePageUrl: "https://file.local/releases", assetPattern: "File-.*\\.exe$", }); const settings = loadUpdateSettings( { ...baseConfig, updateApiUrl: "https://env.local/releases/latest", updateReleasePageUrl: "https://env.local/releases", updateAssetPattern: "Env-.*\\.exe$", }, rootPath, () => undefined, ); assert.deepEqual(settings, { enabled: true, apiUrl: "https://env.local/releases/latest", releasePageUrl: "https://env.local/releases", assetPattern: "Env-.*\\.exe$", }); }); test("readUpdateFileConfig normalizes malformed config into an empty file config", () => { const rootPath = makeTempRoot(["not", "an", "object"]); assert.deepEqual(readUpdateFileConfig(baseConfig, rootPath, () => undefined), {}); }); test("readUpdateFileConfig logs invalid JSON and returns an empty file config", () => { const rootPath = fs.mkdtempSync(path.join(os.tmpdir(), "scoreko-update-settings-")); const staticPath = path.join(rootPath, "static"); fs.mkdirSync(staticPath, { recursive: true }); fs.writeFileSync(path.join(staticPath, "updates.json"), "{ invalid", "utf8"); const messages: unknown[][] = []; const settings = readUpdateFileConfig(baseConfig, rootPath, (...args: unknown[]) => { messages.push(args); }); assert.deepEqual(settings, {}); assert.equal(messages.length, 1); }); function makeTempRoot(config: unknown): string { const rootPath = fs.mkdtempSync(path.join(os.tmpdir(), "scoreko-update-settings-")); const staticPath = path.join(rootPath, "static"); fs.mkdirSync(staticPath, { recursive: true }); fs.writeFileSync(path.join(staticPath, "updates.json"), JSON.stringify(config), "utf8"); return rootPath; }