const assert = require("node:assert/strict"); const fs = require("node:fs"); const path = require("node:path"); const test = require("node:test"); const vm = require("node:vm"); const BACKGROUND_PATH = path.join(__dirname, "background.js"); function createHarness() { let storageChangedListener; let alarmCreateCount = 0; const settings = { apiBaseUrl: "http://localhost:8001", webBaseUrl: "http://localhost:5174", autoDiscoverServiceUrl: false, accessToken: "", tokenSyncEnabled: false, taskPollingEnabled: true, workerId: "test-worker", taskPollIntervalSeconds: 5, }; const chrome = { alarms: { clear: async () => {}, create: async () => { alarmCreateCount += 1; }, onAlarm: { addListener: () => {} }, }, cookies: { get: async () => null }, runtime: { onInstalled: { addListener: () => {} }, onMessage: { addListener: () => {} }, onStartup: { addListener: () => {} }, }, storage: { local: { get: async (defaults) => ({ ...defaults }), set: async () => {}, }, session: { get: async () => ({ actSessions: {} }), set: async () => {} }, sync: { get: async () => ({ ...settings }), set: async (updates) => Object.assign(settings, updates), }, onChanged: { addListener: (listener) => { storageChangedListener = listener; }, }, }, tabs: { query: async () => [] }, }; const context = { chrome, console, fetch: async () => { throw new Error("offline test"); }, importScripts: () => {}, setTimeout: () => 1, clearTimeout: () => {}, URL, DEER_CLIPPER_DEFAULTS: settings, deerNormalizeBaseUrl: (value) => String(value || "").replace(/\/+$/, ""), deerMakeWorkerId: () => "test-worker", deerAuthHeaders: () => ({}), deerExtractAuthTokenFromPage: () => ({ token: "" }), }; context.globalThis = context; vm.runInNewContext(fs.readFileSync(BACKGROUND_PATH, "utf8"), context, { filename: BACKGROUND_PATH, }); return { emitStorageChange(changes) { storageChangedListener(changes, "sync"); }, getAlarmCreateCount: () => alarmCreateCount, resetAlarmCreateCount: () => { alarmCreateCount = 0; }, getApiCandidates: (webBase, currentApiBase) => vm.runInContext(`apiCandidatesForWebBase(${JSON.stringify(webBase)}, ${JSON.stringify(currentApiBase)})`, context), }; } const settle = () => new Promise((resolve) => setImmediate(resolve)); test("self-written sync metadata does not restart browser task polling", async () => { const harness = createHarness(); await settle(); harness.resetAlarmCreateCount(); harness.emitStorageChange({ lastTokenSyncAt: { oldValue: "before", newValue: "after" }, }); await settle(); assert.equal(harness.getAlarmCreateCount(), 0); }); test("polling configuration changes restart browser task polling", async () => { const harness = createHarness(); await settle(); harness.resetAlarmCreateCount(); harness.emitStorageChange({ taskPollingEnabled: { oldValue: false, newValue: true }, }); await settle(); assert.equal(harness.getAlarmCreateCount(), 1); }); test("service discovery prefers the API paired with the open zncm tab", async () => { const harness = createHarness(); assert.deepEqual( Array.from(harness.getApiCandidates("http://localhost:5174", "http://47.94.209.59:2026")), [ "http://localhost:5174", "http://localhost:8001", "http://47.94.209.59:2026", "http://localhost:8001", ].filter((value, index, values) => values.indexOf(value) === index), ); });