const test = require("node:test"); const assert = require("node:assert/strict"); const fs = require("node:fs"); const path = require("node:path"); const vm = require("node:vm"); function loadInternals() { const scriptPath = path.join(__dirname, "nordvpn-client.js"); const source = fs.readFileSync(scriptPath, "utf8").replace(/\nmain\(\);\s*$/, "\n"); const wrapped = `${source} module.exports = { buildMacTailscaleState: typeof buildMacTailscaleState === "function" ? buildMacTailscaleState : undefined, buildWireguardConfig: typeof buildWireguardConfig === "function" ? buildWireguardConfig : undefined, buildLookupResult: typeof buildLookupResult === "function" ? buildLookupResult : undefined, getMacTailscalePath: typeof getMacTailscalePath === "function" ? getMacTailscalePath : undefined, isMacTailscaleActive: typeof isMacTailscaleActive === "function" ? isMacTailscaleActive : undefined, detectMacWireguardActiveFromIfconfig: typeof detectMacWireguardActiveFromIfconfig === "function" ? detectMacWireguardActiveFromIfconfig : undefined, resolveHostnameWithFallback: typeof resolveHostnameWithFallback === "function" ? resolveHostnameWithFallback : undefined, verifyConnectionWithRetry: typeof verifyConnectionWithRetry === "function" ? verifyConnectionWithRetry : undefined, };`; const sandbox = { require, module: { exports: {} }, exports: {}, __dirname, __filename: scriptPath, process: { ...process, exit() {} }, console, setTimeout, clearTimeout, Buffer, }; vm.runInNewContext(wrapped, sandbox, { filename: scriptPath }); return sandbox.module.exports; } test("detectMacWireguardActiveFromIfconfig detects nordvpn utun client address", () => { const { detectMacWireguardActiveFromIfconfig } = loadInternals(); assert.equal(typeof detectMacWireguardActiveFromIfconfig, "function"); const ifconfig = ` utun8: flags=8051 mtu 1380 utun9: flags=8051 mtu 1420 \tinet 10.5.0.2 --> 10.5.0.2 netmask 0xff000000 `; assert.equal(detectMacWireguardActiveFromIfconfig(ifconfig), true); assert.equal(detectMacWireguardActiveFromIfconfig("utun7: flags=8051\n\tinet 100.64.0.4"), false); }); test("buildLookupResult supports lookup all=true mode", () => { const { buildLookupResult } = loadInternals(); assert.equal(typeof buildLookupResult, "function"); assert.equal( JSON.stringify(buildLookupResult("104.26.9.44", { all: true })), JSON.stringify([{ address: "104.26.9.44", family: 4 }]) ); assert.equal(JSON.stringify(buildLookupResult("104.26.9.44", { all: false })), JSON.stringify(["104.26.9.44", 4])); }); test("buildWireguardConfig includes NordVPN DNS for the vanilla macOS config path", () => { const { buildWireguardConfig } = loadInternals(); assert.equal(typeof buildWireguardConfig, "function"); const config = buildWireguardConfig( { hostname: "tr73.nordvpn.com", ips: [{ ip: { version: 4, ip: "45.89.52.1" } }], technologies: [{ identifier: "wireguard_udp", metadata: [{ name: "public_key", value: "PUBKEY" }] }], }, "PRIVATEKEY" ); assert.equal(config.includes("DNS = 103.86.96.100, 103.86.99.100"), true); assert.equal(config.includes("AllowedIPs = 0.0.0.0/0"), true); }); test("getMacTailscalePath falls back to /opt/homebrew/bin/tailscale when PATH lookup is missing", () => { const { getMacTailscalePath } = loadInternals(); assert.equal(typeof getMacTailscalePath, "function"); assert.equal( getMacTailscalePath({ commandExists: () => "", fileExists: (target) => target === "/opt/homebrew/bin/tailscale", }), "/opt/homebrew/bin/tailscale" ); }); test("buildMacTailscaleState records whether tailscale was active", () => { const { buildMacTailscaleState } = loadInternals(); assert.equal(typeof buildMacTailscaleState, "function"); assert.equal( JSON.stringify(buildMacTailscaleState(true)), JSON.stringify({ tailscaleWasActive: true, }) ); }); test("isMacTailscaleActive treats Running backend as active", () => { const { isMacTailscaleActive } = loadInternals(); assert.equal(typeof isMacTailscaleActive, "function"); assert.equal(isMacTailscaleActive({ BackendState: "Running" }), true); assert.equal(isMacTailscaleActive({ BackendState: "Stopped" }), false); }); test("verifyConnectionWithRetry retries transient reachability failures", async () => { const { verifyConnectionWithRetry } = loadInternals(); assert.equal(typeof verifyConnectionWithRetry, "function"); let attempts = 0; const result = await verifyConnectionWithRetry( { country: "Italy", city: "Milan" }, { attempts: 3, delayMs: 1, getPublicIpInfo: async () => { attempts += 1; if (attempts === 1) { return { ok: false, error: "read EHOSTUNREACH" }; } return { ok: true, country: "Italy", city: "Milan" }; }, } ); assert.equal(result.ok, true); assert.equal(result.ipInfo.country, "Italy"); assert.equal(attempts, 2); }); test("resolveHostnameWithFallback uses fallback resolvers when system lookup fails", async () => { const { resolveHostnameWithFallback } = loadInternals(); assert.equal(typeof resolveHostnameWithFallback, "function"); const calls = []; const address = await resolveHostnameWithFallback("ipapi.co", { resolvers: ["1.1.1.1", "8.8.8.8"], resolveWithResolver: async (hostname, resolver) => { calls.push(`${resolver}:${hostname}`); if (resolver === "1.1.1.1") return []; return ["104.26.9.44"]; }, }); assert.equal(address, "104.26.9.44"); assert.deepEqual(calls, ["1.1.1.1:ipapi.co", "8.8.8.8:ipapi.co"]); });