Files
openclaw/src/commands/doctor-state-integrity.linux-storage.test.ts
Vincent Koc f696b64b51 Doctor: warn when Linux state dir is on SD/eMMC mounts (#31033)
* Doctor state: warn on Linux SD or eMMC state mounts

* Doctor tests: cover Linux SD or eMMC state mount detection

* Docs doctor: document Linux SD or eMMC state warning

* Changelog: add Linux SD or eMMC doctor warning

* Update CHANGELOG.md

* Doctor: escape mountinfo control chars in SD warning

* Doctor tests: cover escaped mountinfo control chars
2026-03-01 16:36:01 -08:00

126 lines
3.7 KiB
TypeScript

import path from "node:path";
import { describe, expect, it } from "vitest";
import {
detectLinuxSdBackedStateDir,
formatLinuxSdBackedStateDirWarning,
} from "./doctor-state-integrity.js";
function encodeMountInfoPath(value: string): string {
return value
.replace(/\\/g, "\\134")
.replace(/\n/g, "\\012")
.replace(/\t/g, "\\011")
.replace(/ /g, "\\040");
}
describe("detectLinuxSdBackedStateDir", () => {
it("detects state dir on mmc-backed mount", () => {
const mountInfo = [
"24 19 179:2 / / rw,relatime - ext4 /dev/mmcblk0p2 rw",
"25 24 0:22 / /proc rw,nosuid,nodev,noexec,relatime - proc proc rw",
].join("\n");
const result = detectLinuxSdBackedStateDir("/home/pi/.openclaw", {
platform: "linux",
mountInfo,
});
expect(result).toEqual({
path: "/home/pi/.openclaw",
mountPoint: "/",
fsType: "ext4",
source: "/dev/mmcblk0p2",
});
});
it("returns null for non-mmc devices", () => {
const mountInfo = "24 19 259:2 / / rw,relatime - ext4 /dev/nvme0n1p2 rw";
const result = detectLinuxSdBackedStateDir("/home/user/.openclaw", {
platform: "linux",
mountInfo,
});
expect(result).toBeNull();
});
it("resolves /dev/disk aliases to mmc devices", () => {
const mountInfo = "24 19 179:2 / / rw,relatime - ext4 /dev/disk/by-uuid/abcd-1234 rw";
const result = detectLinuxSdBackedStateDir("/home/user/.openclaw", {
platform: "linux",
mountInfo,
resolveDeviceRealPath: (devicePath) => {
if (devicePath === "/dev/disk/by-uuid/abcd-1234") {
return "/dev/mmcblk0p2";
}
return null;
},
});
expect(result).toEqual({
path: "/home/user/.openclaw",
mountPoint: "/",
fsType: "ext4",
source: "/dev/disk/by-uuid/abcd-1234",
});
});
it("uses resolved state path to select mount", () => {
const mountInfo = [
"24 19 259:2 / / rw,relatime - ext4 /dev/nvme0n1p2 rw",
"30 24 179:5 / /mnt/slow rw,relatime - ext4 /dev/mmcblk1p1 rw",
].join("\n");
const result = detectLinuxSdBackedStateDir("/tmp/openclaw-state", {
platform: "linux",
mountInfo,
resolveRealPath: () => "/mnt/slow/openclaw/.openclaw",
});
expect(result).toEqual({
path: "/mnt/slow/openclaw/.openclaw",
mountPoint: "/mnt/slow",
fsType: "ext4",
source: "/dev/mmcblk1p1",
});
});
it("returns null outside linux", () => {
const mountInfo = "24 19 179:2 / / rw,relatime - ext4 /dev/mmcblk0p2 rw";
const result = detectLinuxSdBackedStateDir(path.join("/Users", "tester", ".openclaw"), {
platform: "darwin",
mountInfo,
});
expect(result).toBeNull();
});
it("escapes decoded mountinfo control characters in warning output", () => {
const mountRoot = "/home/pi/mnt\nspoofed";
const stateDir = `${mountRoot}/.openclaw`;
const encodedSource = "/dev/disk/by-uuid/mmc\\012source";
const mountInfo = `30 24 179:2 / ${encodeMountInfoPath(mountRoot)} rw,relatime - ext4 ${encodedSource} rw`;
const result = detectLinuxSdBackedStateDir(stateDir, {
platform: "linux",
mountInfo,
resolveRealPath: () => stateDir,
resolveDeviceRealPath: (devicePath) => {
if (devicePath === "/dev/disk/by-uuid/mmc\nsource") {
return "/dev/mmcblk0p2";
}
return null;
},
});
expect(result).not.toBeNull();
const warning = formatLinuxSdBackedStateDirWarning(stateDir, result!);
expect(warning).toContain("device /dev/disk/by-uuid/mmc\\nsource");
expect(warning).toContain("mount /home/pi/mnt\\nspoofed");
expect(warning).not.toContain("device /dev/disk/by-uuid/mmc\nsource");
expect(warning).not.toContain("mount /home/pi/mnt\nspoofed");
});
});