feat(nordvpn-client): gate macos connect on stable wireguard persistence
This commit is contained in:
@@ -23,6 +23,8 @@ module.exports = {
|
||||
typeof cleanupMacWireguardAndDnsState === "function" ? cleanupMacWireguardAndDnsState : undefined,
|
||||
collectMacWireguardDiagnostics:
|
||||
typeof collectMacWireguardDiagnostics === "function" ? collectMacWireguardDiagnostics : undefined,
|
||||
acquireOperationLock:
|
||||
typeof acquireOperationLock === "function" ? acquireOperationLock : undefined,
|
||||
inspectMacWireguardHelperSecurity:
|
||||
typeof inspectMacWireguardHelperSecurity === "function" ? inspectMacWireguardHelperSecurity : undefined,
|
||||
getMacTailscalePath:
|
||||
@@ -31,10 +33,16 @@ module.exports = {
|
||||
typeof isBenignMacWireguardAbsentError === "function" ? isBenignMacWireguardAbsentError : undefined,
|
||||
isMacTailscaleActive:
|
||||
typeof isMacTailscaleActive === "function" ? isMacTailscaleActive : undefined,
|
||||
checkMacWireguardPersistence:
|
||||
typeof checkMacWireguardPersistence === "function" ? checkMacWireguardPersistence : undefined,
|
||||
normalizeSuccessfulConnectState:
|
||||
typeof normalizeSuccessfulConnectState === "function" ? normalizeSuccessfulConnectState : undefined,
|
||||
normalizeStatusState:
|
||||
typeof normalizeStatusState === "function" ? normalizeStatusState : undefined,
|
||||
parseMacWireguardHelperStatus:
|
||||
typeof parseMacWireguardHelperStatus === "function" ? parseMacWireguardHelperStatus : undefined,
|
||||
shouldRejectMacDnsBaseline:
|
||||
typeof shouldRejectMacDnsBaseline === "function" ? shouldRejectMacDnsBaseline : undefined,
|
||||
shouldManageMacDnsService:
|
||||
typeof shouldManageMacDnsService === "function" ? shouldManageMacDnsService : undefined,
|
||||
sanitizeOutputPayload:
|
||||
@@ -142,6 +150,36 @@ test("buildMacDnsState records DNS and search domains per service", () => {
|
||||
);
|
||||
});
|
||||
|
||||
test("shouldRejectMacDnsBaseline flags a NordVPN-pinned restore snapshot", () => {
|
||||
const { shouldRejectMacDnsBaseline } = loadInternals();
|
||||
assert.equal(typeof shouldRejectMacDnsBaseline, "function");
|
||||
|
||||
assert.equal(
|
||||
shouldRejectMacDnsBaseline({
|
||||
services: [
|
||||
{
|
||||
name: "Wi-Fi",
|
||||
dnsServers: ["103.86.96.100", "103.86.99.100"],
|
||||
searchDomains: [],
|
||||
},
|
||||
],
|
||||
}),
|
||||
true
|
||||
);
|
||||
assert.equal(
|
||||
shouldRejectMacDnsBaseline({
|
||||
services: [
|
||||
{
|
||||
name: "Wi-Fi",
|
||||
dnsServers: ["1.1.1.1"],
|
||||
searchDomains: [],
|
||||
},
|
||||
],
|
||||
}),
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
test("getMacTailscalePath falls back to /opt/homebrew/bin/tailscale when PATH lookup is missing", () => {
|
||||
const { getMacTailscalePath } = loadInternals();
|
||||
assert.equal(typeof getMacTailscalePath, "function");
|
||||
@@ -293,6 +331,92 @@ test("collectMacWireguardDiagnostics captures bounded wg/ifconfig/route/process
|
||||
assert.equal(result.processes.includes("wireguard-go"), true);
|
||||
});
|
||||
|
||||
test("parseMacWireguardHelperStatus reads active helper key-value output", () => {
|
||||
const { parseMacWireguardHelperStatus } = loadInternals();
|
||||
assert.equal(typeof parseMacWireguardHelperStatus, "function");
|
||||
|
||||
const result = parseMacWireguardHelperStatus("active=1\ninterfaceName=nordvpnctl\n");
|
||||
|
||||
assert.equal(result.active, true);
|
||||
assert.equal(result.interfaceName, "nordvpnctl");
|
||||
});
|
||||
|
||||
test("checkMacWireguardPersistence waits for both helper-active and verified exit", async () => {
|
||||
const { checkMacWireguardPersistence } = loadInternals();
|
||||
assert.equal(typeof checkMacWireguardPersistence, "function");
|
||||
|
||||
const helperStatuses = [
|
||||
{ active: false, interfaceName: "nordvpnctl" },
|
||||
{ active: true, interfaceName: "nordvpnctl" },
|
||||
];
|
||||
const verifications = [
|
||||
{ ok: false, ipInfo: { ok: false, error: "timeout" } },
|
||||
{ ok: true, ipInfo: { ok: true, country: "Germany", city: "Frankfurt" } },
|
||||
];
|
||||
|
||||
const result = await checkMacWireguardPersistence(
|
||||
{ country: "Germany", city: "" },
|
||||
{
|
||||
attempts: 2,
|
||||
delayMs: 1,
|
||||
getHelperStatus: async () => helperStatuses.shift(),
|
||||
verifyConnection: async () => verifications.shift(),
|
||||
sleep: async () => {},
|
||||
}
|
||||
);
|
||||
|
||||
assert.equal(result.stable, true);
|
||||
assert.equal(result.attempts, 2);
|
||||
assert.equal(result.helperStatus.active, true);
|
||||
assert.equal(result.verified.ok, true);
|
||||
});
|
||||
|
||||
test("checkMacWireguardPersistence returns the last failed status when stability is not reached", async () => {
|
||||
const { checkMacWireguardPersistence } = loadInternals();
|
||||
assert.equal(typeof checkMacWireguardPersistence, "function");
|
||||
|
||||
const result = await checkMacWireguardPersistence(
|
||||
{ country: "Germany", city: "" },
|
||||
{
|
||||
attempts: 2,
|
||||
delayMs: 1,
|
||||
getHelperStatus: async () => ({ active: false, interfaceName: "nordvpnctl" }),
|
||||
verifyConnection: async () => ({ ok: false, ipInfo: { ok: false, error: "timeout" } }),
|
||||
sleep: async () => {},
|
||||
}
|
||||
);
|
||||
|
||||
assert.equal(result.stable, false);
|
||||
assert.equal(result.attempts, 2);
|
||||
assert.equal(result.helperStatus.active, false);
|
||||
assert.equal(result.verified.ok, false);
|
||||
});
|
||||
|
||||
test("acquireOperationLock cleans a stale dead-pid lock before taking ownership", () => {
|
||||
const { acquireOperationLock } = loadInternals();
|
||||
assert.equal(typeof acquireOperationLock, "function");
|
||||
|
||||
const tmpDir = fs.mkdtempSync(path.join(fs.mkdtempSync("/tmp/nordvpn-client-test-"), "lock-"));
|
||||
const lockPath = path.join(tmpDir, "operation.lock");
|
||||
fs.writeFileSync(
|
||||
lockPath,
|
||||
JSON.stringify({
|
||||
action: "connect",
|
||||
pid: 0,
|
||||
startedAt: new Date(0).toISOString(),
|
||||
startedAtMs: 0,
|
||||
})
|
||||
);
|
||||
|
||||
const lock = acquireOperationLock("disconnect", { lockPath });
|
||||
const lockFile = JSON.parse(fs.readFileSync(lockPath, "utf8"));
|
||||
|
||||
assert.equal(lockFile.action, "disconnect");
|
||||
assert.equal(lockFile.pid, process.pid);
|
||||
lock.release();
|
||||
assert.equal(fs.existsSync(lockPath), false);
|
||||
});
|
||||
|
||||
test("shouldAttemptMacWireguardDisconnect does not trust active=false when residual state exists", () => {
|
||||
const { shouldAttemptMacWireguardDisconnect } = loadInternals();
|
||||
assert.equal(typeof shouldAttemptMacWireguardDisconnect, "function");
|
||||
@@ -370,14 +494,15 @@ test("normalizeSuccessfulConnectState marks the connect snapshot active after ve
|
||||
assert.equal(state.wireguard.endpoint, "de1227.nordvpn.com:51820");
|
||||
});
|
||||
|
||||
test("shouldFinalizeMacWireguardConnect requires a verified wireguard connect", () => {
|
||||
test("shouldFinalizeMacWireguardConnect requires a verified and stable wireguard connect", () => {
|
||||
const { shouldFinalizeMacWireguardConnect } = loadInternals();
|
||||
assert.equal(typeof shouldFinalizeMacWireguardConnect, "function");
|
||||
|
||||
assert.equal(shouldFinalizeMacWireguardConnect({ backend: "wireguard" }, { ok: true }), true);
|
||||
assert.equal(shouldFinalizeMacWireguardConnect({ backend: "wireguard" }, { ok: false }), false);
|
||||
assert.equal(shouldFinalizeMacWireguardConnect({ backend: "cli" }, { ok: true }), false);
|
||||
assert.equal(shouldFinalizeMacWireguardConnect(null, { ok: true }), false);
|
||||
assert.equal(shouldFinalizeMacWireguardConnect({ backend: "wireguard" }, { ok: true }, { stable: true }), true);
|
||||
assert.equal(shouldFinalizeMacWireguardConnect({ backend: "wireguard" }, { ok: true }, { stable: false }), false);
|
||||
assert.equal(shouldFinalizeMacWireguardConnect({ backend: "wireguard" }, { ok: false }, { stable: true }), false);
|
||||
assert.equal(shouldFinalizeMacWireguardConnect({ backend: "cli" }, { ok: true }, { stable: true }), false);
|
||||
assert.equal(shouldFinalizeMacWireguardConnect(null, { ok: true }, { stable: true }), false);
|
||||
});
|
||||
|
||||
test("normalizeStatusState marks macOS wireguard connected when public IP matches the last successful target", () => {
|
||||
|
||||
Reference in New Issue
Block a user