"use strict"; // Destructive end-to-end tests of System Monitor metrics and swap management in a disposable, PRIVILEGED // Docker container (swapon/loop devices). Swap files live on loop-mounted ext4/XFS/Btrfs images inside the // container. Run explicitly: CHJ_USERS_INTEGRATION=1 CHJ_USERS_IMAGE=chj-users-it-debian node --test test/systemMonitorIntegration.test.js const test = require("node:test"); const assert = require("node:assert/strict"); const { execFileSync, spawnSync } = require("node:child_process"); const { SessionManager } = require("../src/main/sessions/sessionManager"); const { PrivilegedExecutor } = require("../src/main/sessions/privilegedExec"); const { VirtualMemoryService } = require("../src/main/system/virtualMemoryService"); const enabled = process.env.CHJ_USERS_INTEGRATION === "1"; const image = process.env.CHJ_USERS_IMAGE || "chj-users-it-debian"; const skip = enabled ? false : "Set CHJ_USERS_INTEGRATION=1 to run destructive swap tests in a disposable privileged container."; const container = `chj-swap-it-${process.pid}`; function sh(command, { allowFailure = false } = {}) { const result = spawnSync("docker", ["exec", container, "sh", "-c", command], { encoding: "utf8" }); if (!allowFailure && result.status !== 0) throw new Error(`container command failed (${result.status}): ${command}\n${result.stderr}`); return result.stdout.trim(); } test("System Monitor metrics and swap management on an isolated Linux container", { skip, timeout: 900000 }, async (t) => { execFileSync("docker", ["run", "-d", "--rm", "--privileged", "--name", container, "-p", "127.0.0.1::22", image]); t.after(() => { spawnSync("docker", ["exec", container, "sh", "-c", "swapoff /swap/* /var/lib/swap/* /btrfs/* 2>/dev/null; true"]); spawnSync("docker", ["rm", "-f", container]); }); // /proc/swaps excludes the swap header page, so sizes are compared with a one-page tolerance. const port = Number(execFileSync("docker", ["port", container, "22/tcp"], { encoding: "utf8" }).trim().split(":").pop()); for (let attempt = 0; attempt < 50 && spawnSync("docker", ["exec", container, "test", "-s", "/run/sshd.pid"]).status !== 0; attempt += 1) await new Promise((resolve) => setTimeout(resolve, 200)); // CHJ_SWAP_SECOND_FS=btrfs forces Btrfs for the second file system. // Loop file systems: ext4 at /swap (3 GiB sparse), XFS at /var/lib/swap (when mkfs.xfs exists), Btrfs at /var/lib/swap only if no XFS. sh("mkdir -p /swap /var/lib/swap && truncate -s 3G /img-ext4 && mkfs.ext4 -q /img-ext4 && mount -o loop /img-ext4 /swap"); const xfs = process.env.CHJ_SWAP_SECOND_FS !== "btrfs" && sh("command -v mkfs.xfs >/dev/null && truncate -s 3G /img-xfs && mkfs.xfs -q /img-xfs && mount -o loop /img-xfs /var/lib/swap && echo yes || echo no", { allowFailure: true }) === "yes"; const btrfs = !xfs && sh("command -v mkfs.btrfs >/dev/null && truncate -s 3G /img-btrfs && mkfs.btrfs -q /img-btrfs && mount -o loop /img-btrfs /var/lib/swap && echo yes || echo no", { allowFailure: true }) === "yes"; sh("printf '# keep me\\nproc /proc proc defaults 0 0\\n' > /etc/fstab"); const fstabBefore = sh("cat /etc/fstab"); t.diagnostic(`${image}: second file system ${xfs ? "XFS" : btrfs ? "Btrfs" : "none"}; kernel ${sh("uname -r")}`); const hostKeys = new Map(); const profiles = new Map([ ["admin", { id: "admin", label: "it", host: "127.0.0.1", port, username: "admin", authMethod: "password" }], ["plain", { id: "plain", label: "it", host: "127.0.0.1", port, username: "plain", authMethod: "password" }] ]); const passwords = { admin: "AdminPass-1", plain: "PlainPass-1" }; const profileService = { get: (id) => profiles.get(id), getHostKey: (_h, p) => hostKeys.get(p) || null, trustHostKey: (_h, p, fp) => hostKeys.set(p, fp), getStoredPassword: (id) => passwords[id], markUsed() {} }; const manager = new SessionManager({ profileService, latencyMonitor: { start: () => () => {} } }); const executor = new PrivilegedExecutor({ sessionManager: manager }); const service = new VirtualMemoryService({ sessionManager: manager, privilegedExecutor: executor }); t.after(() => manager.disconnectAll("test")); for (const id of ["admin", "plain"]) { try { await manager.connect({ sessionId: id, profileId: id }); } catch (error) { manager.trustPending({ sessionId: id, profileId: id, fingerprint: error.fingerprint }); await manager.connect({ sessionId: id, profileId: id }); } } const auth = { sudoPassword: "AdminPass-1" }; await t.test("Linux metrics keep their fields and add non-overlapping memory detail", async () => { const metrics = await manager.readSystemMetrics("admin"); assert.equal(metrics.os, "linux"); assert.ok(metrics.cpu.logicalCores > 0); assert.ok(metrics.memory.total > 0); assert.equal(metrics.loadAverageSupported, true); const detail = metrics.memoryDetail; assert.ok(detail.total > 0 && detail.available !== null); const sum = detail.used + detail.free + detail.buffers + detail.cached + detail.slabReclaimable; assert.ok(Math.abs(sum - detail.total) < 1024 * 1024, "slices add up to MemTotal without double counting"); }); await t.test("read-only inspection works without sudo; a user without sudo cannot change swap", async () => { const state = await service.read("plain"); assert.equal(state.platform, "linux"); assert.equal(state.root, false); assert.ok(Array.isArray(state.swapAreas)); await assert.rejects(() => service.manage("plain", { action: "create", path: "/swap/x", sizeMiB: 64, persist: false, sudoPassword: "PlainPass-1" }), { code: "SUDO_NOT_PERMITTED" }); assert.equal((await service.read("plain")).canManage, false); }); await t.test("invalid requests are refused before anything runs on the server", async () => { for (const [payload, code] of [ [{ action: "create", path: "/swap/a", sizeMiB: 10, persist: false }, "VM_SWAP_SIZE_INVALID"], [{ action: "create", path: "/swap/a", sizeMiB: 70000, persist: false }, "VM_SWAP_SIZE_INVALID"], [{ action: "create", path: "/swap/a", sizeMiB: "64; reboot", persist: false }, "VM_SWAP_SIZE_INVALID"], [{ action: "create", path: "/etc/passwd", sizeMiB: 64, persist: false }, "VM_SWAP_PATH_INVALID"], [{ action: "delete", path: "/swap/../etc/shadow" }, "VM_SWAP_PATH_INVALID"], [{ action: "create", path: "/swap/a", sizeMiB: 64 }, "VM_ARGUMENTS_INVALID"], [{ action: "priority", path: "/swap/a", priority: 99999 }, "VM_SWAP_PRIORITY_INVALID"], [{ action: "wipe", path: "/swap/a" }, "VM_ACTION_INVALID"] ]) await assert.rejects(() => service.manage("admin", { ...payload, ...auth }), { code }, JSON.stringify(payload)); await assert.rejects(() => service.manage("admin", { action: "create", path: "/swapfile", sizeMiB: 64, persist: false, ...auth }), { code: "VM_SWAP_FS_UNSUPPORTED" }, "overlay root file system is refused"); await assert.rejects(() => service.manage("admin", { action: "create", path: "/swap/big", sizeMiB: 2048, persist: false, ...auth }), { code: "VM_SWAP_DISK_FULL" }); assert.equal(sh("ls /swap | grep -v lost+found || true"), "", "nothing was left behind"); }); await t.test("create, persist with fstab backup, resize, priority, second area and delete", async () => { let result = await service.manage("admin", { action: "create", path: "/swap/a", sizeMiB: 128, priority: 5, persist: true, ...auth }); assert.ok(result.active.some((area) => area.path === "/swap/a" && area.priority === 5 && Math.abs(area.size - 128 * 1024 * 1024) <= 65536)); assert.match(sh("cat /etc/fstab"), /^\/swap\/a none swap sw,pri=5 0 0 # CH-J chj\.system-monitor$/m); assert.ok(sh("cat /etc/fstab").startsWith(fstabBefore), "unrelated fstab lines are kept"); assert.match(sh("ls /etc/fstab.chj-backup-*"), /fstab\.chj-backup-/); assert.equal(sh("stat -c '%a %U' /swap/a"), "600 root"); result = await service.manage("admin", { action: "resize", path: "/swap/a", sizeMiB: 192, priority: 5, ...auth }); assert.ok(result.active.some((area) => area.path === "/swap/a" && Math.abs(area.size - 192 * 1024 * 1024) <= 65536), "resized and active again"); result = await service.manage("admin", { action: "priority", path: "/swap/a", priority: 7, ...auth }); assert.ok(result.active.some((area) => area.path === "/swap/a" && area.priority === 7)); assert.match(sh("cat /etc/fstab"), /pri=7/); if (xfs || btrfs) { result = await service.manage("admin", { action: "create", path: "/var/lib/swap/b", sizeMiB: 96, priority: 1, persist: false, ...auth }); assert.ok(result.active.some((area) => area.path === "/var/lib/swap/b"), `${xfs ? "XFS" : "Btrfs"} swap file works`); const state = await service.read("admin", auth); assert.equal(state.swapAreas.filter((area) => area.active).length >= 2, true, "several active swap areas are listed"); assert.equal(state.swapAreas.find((area) => area.path === "/var/lib/swap/b").fsType, xfs ? "xfs" : "btrfs"); assert.equal(state.swapAreas.find((area) => area.path === "/swap/a").persistent, true); await service.manage("admin", { action: "delete", path: "/var/lib/swap/b", ...auth }); } result = await service.manage("admin", { action: "deactivate", path: "/swap/a", ...auth }); assert.equal(result.active.some((area) => area.path === "/swap/a"), false); assert.match(sh("cat /etc/fstab"), /\/swap\/a/, "deactivate keeps persistence unless requested"); result = await service.manage("admin", { action: "activate", path: "/swap/a", priority: 3, ...auth }); assert.ok(result.active.some((area) => area.path === "/swap/a" && area.priority === 3)); result = await service.manage("admin", { action: "delete", path: "/swap/a", ...auth }); assert.equal(sh("test -e /swap/a && echo yes || echo no"), "no"); assert.equal(sh("cat /etc/fstab"), fstabBefore, "fstab back to the original content"); }); await t.test("concurrent requests are serialized per session and the server lock is released", async () => { const first = service.manage("admin", { action: "create", path: "/swap/c", sizeMiB: 64, persist: false, ...auth }); await assert.rejects(() => service.manage("admin", { action: "create", path: "/swap/d", sizeMiB: 64, persist: false, ...auth }), { code: "VM_BUSY" }); await first; await service.manage("admin", { action: "delete", path: "/swap/c", ...auth }); assert.equal(sh("test -d /run/chj-swap.lock && echo locked || echo free"), "free"); }); await t.test("a lost SSH connection during an operation is reported, not hidden", async () => { const pending = service.manage("admin", { action: "create", path: "/swap/e", sizeMiB: 1536, persist: false, ...auth }); setTimeout(() => manager.disconnect("admin", "test-drop"), 100); await assert.rejects(() => pending, { code: "SESSION_NOT_CONNECTED" }); sh("swapoff /swap/e 2>/dev/null; rm -f /swap/e /swap/.e.chj-new; rmdir /run/chj-swap.lock 2>/dev/null; true"); }); });