// THE STORAGE BLOCK, and the things its sanitizer refuses to do. // // Run with: node_modules/.bin/tsx --test common/lib/storageSettings.test.ts // // Its own file rather than a `settings.test.ts`: getSettings() memoizes paths at // module scope (see controller/laneForOperation.test.ts), and sanitizeStorage is // a pure function that needs none of that seam. Naming the file after the block // keeps it that way. // // The migration's own rules live in storageLocations.test.ts; what is asserted // here is the pair — migrate, then sanitize — which is the composition // getSettings performs on every read. import { test } from "node:test"; import assert from "node:assert/strict"; import { defaultStorage, sanitizeStorage } from "./settings"; import { migrateMediaRootToLocations } from "./storageLocations"; const NONE = { locations: [], defaultLocationId: "" }; test("absent, blank and ill-typed all mean no locations", () => { assert.deepEqual(sanitizeStorage(undefined), NONE); assert.deepEqual(sanitizeStorage(null), NONE); assert.deepEqual(sanitizeStorage("/mnt/platter"), NONE); assert.deepEqual(sanitizeStorage({}), NONE); assert.deepEqual(sanitizeStorage({ locations: "nope" }), NONE); assert.deepEqual(sanitizeStorage({ locations: [null, 7, "x"] }), NONE); assert.deepEqual(defaultStorage(), NONE); }); test("a location keeps its id, label, root and autoRepoint", () => { const one = { id: "platter", label: "Platter", root: "/mnt/platter/archilyzer-media", autoRepoint: true, }; assert.deepEqual( sanitizeStorage({ locations: [one], defaultLocationId: "platter" }), { locations: [one], defaultLocationId: "platter" }, ); }); test("the id is a slug, unique, and a bad one drops the location", () => { const ids = (v: unknown) => sanitizeStorage(v).locations.map((l) => l.id); assert.deepEqual( ids({ locations: [ { id: "Platter", root: "/a" }, // uppercase { id: "-platter", root: "/b" }, // leading dash { id: "cold drive", root: "/c" }, // space { id: "", root: "/d" }, { id: 7, root: "/e" }, { id: "a".repeat(65), root: "/f" }, { id: "platter2", root: "/g" }, ], }), ["platter2"], ); // First wins; the duplicate is dropped rather than overwriting it. assert.deepEqual( sanitizeStorage({ locations: [ { id: "cold", root: "/first" }, { id: "cold", root: "/second" }, ], }).locations.map((l) => l.root), ["/first"], ); }); test("a blank label falls back to the id", () => { const [l] = sanitizeStorage({ locations: [{ id: "cold", label: " ", root: "/mnt/cold" }], }).locations; assert.equal(l.label, "cold"); }); // ABSOLUTENESS IS CHECKED and a relative root is DROPPED, never resolved — // resolving would anchor the location to whatever cwd the reader booted in. test("a relative root drops the whole location", () => { assert.deepEqual( sanitizeStorage({ locations: [ { id: "rel", root: "platter/media" }, { id: "up", root: "../platter" }, { id: "dot", root: "./platter" }, { id: "blank", root: "" }, { id: "num", root: 7 }, ], }), NONE, ); }); test("a trailing slash is stripped, and '/' survives as '/'", () => { assert.deepEqual( sanitizeStorage({ locations: [ { id: "a", root: " /mnt/platter/media/ " }, { id: "b", root: "/mnt/other///" }, { id: "c", root: "/" }, ], }).locations.map((l) => l.root), ["/mnt/platter/media", "/mnt/other", "/"], ); }); // EXISTENCE IS NEVER CHECKED: the whole point of a cold location is a drive // that may be unmounted when settings are read, and a sanitizer that dropped it // then would erase the operator's choice on the next save. test("a root that does not exist is kept", () => { assert.equal( sanitizeStorage({ locations: [{ id: "cold", root: "/definitely/not/mounted/anywhere" }], }).locations[0].root, "/definitely/not/mounted/anywhere", ); }); // NESTED ROOTS ARE ALLOWED — locationOfDataDir resolves a channel to the // longest match. The sanitizer does not police the arrangement. test("nested roots are both kept", () => { assert.deepEqual( sanitizeStorage({ locations: [ { id: "platter", root: "/mnt/platter" }, { id: "archive", root: "/mnt/platter/archive" }, ], }).locations.map((l) => l.id), ["platter", "archive"], ); }); test("the volume identity is shape-checked, and half a record is no record", () => { const vol = (v: unknown) => sanitizeStorage({ locations: [{ id: "c", root: "/mnt/c", volume: v }] }) .locations[0].volume; assert.deepEqual( vol({ uuid: "u-1", fstype: "ext4", label: "PLATTER", mountpoint: "/mnt/c", relPath: "", bogus: 1, }), { uuid: "u-1", fstype: "ext4", label: "PLATTER", mountpoint: "/mnt/c", relPath: "", }, ); // No uuid = no stable name to find the volume by; no mountpoint = the // root === join(mountpoint, relPath) invariant cannot hold. Either drops it. assert.equal(vol({ mountpoint: "/mnt/c", relPath: "" }), undefined); assert.equal(vol({ uuid: "u-1", relPath: "" }), undefined); assert.equal(vol("nope"), undefined); assert.equal(vol(undefined), undefined); }); test("defaultLocationId falls back to the first location, or blank", () => { const two = [ { id: "hot", root: "/mnt/hot" }, { id: "cold", root: "/mnt/cold" }, ]; assert.equal( sanitizeStorage({ locations: two, defaultLocationId: "cold" }) .defaultLocationId, "cold", ); // Names a location that is gone (or names nothing): with locations // configured, "no default" is never what the operator wanted, and a blank // default silently disables every prefill. assert.equal( sanitizeStorage({ locations: two, defaultLocationId: "deleted" }) .defaultLocationId, "hot", ); assert.equal(sanitizeStorage({ locations: two }).defaultLocationId, "hot"); // Nothing to fall back to. assert.equal( sanitizeStorage({ locations: [], defaultLocationId: "hot" }) .defaultLocationId, "", ); }); // Unknown keys are not carried through: the block is a closed shape, so a // settings.json written by a newer build cannot smuggle a field back onto disk. test("only the known keys survive, and a stale mediaRoot is ignored", () => { assert.deepEqual( sanitizeStorage({ locations: [{ id: "cold", root: "/mnt/a", tier: "cold", extra: 1 }], defaultLocationId: "cold", mediaRoot: "/mnt/deleted", }), { locations: [ { id: "cold", label: "cold", root: "/mnt/a", autoRepoint: false }, ], defaultLocationId: "cold", }, ); }); // THE PAIR getSettings RUNS. Rules 1-3 are asserted on the migration itself in // storageLocations.test.ts; here they are asserted through the sanitizer, which // is the only form any reader ever sees. test("migrate-then-sanitize: an old mediaRoot becomes the default location", () => { assert.deepEqual( sanitizeStorage( migrateMediaRootToLocations({ mediaRoot: "/mnt/platter/" }), ), { locations: [ { id: "default", label: "Default", root: "/mnt/platter", autoRepoint: false, }, ], defaultLocationId: "default", }, ); }); test("migrate-then-sanitize: blank, relative and absent mean no locations", () => { for (const raw of [ { mediaRoot: "" }, { mediaRoot: " " }, { mediaRoot: "platter" }, {}, undefined, ]) { assert.deepEqual(sanitizeStorage(migrateMediaRootToLocations(raw)), NONE); } }); test("migrate-then-sanitize is idempotent, and never resurrects mediaRoot", () => { const once = sanitizeStorage( migrateMediaRootToLocations({ mediaRoot: "/mnt/platter" }), ); const twice = sanitizeStorage(migrateMediaRootToLocations(once)); assert.deepEqual(twice, once); // The stored block still carrying the retired key does NOT gain a second // location on a later read: `locations` present means the migration is done. const withStale = { ...once, mediaRoot: "/mnt/deleted" }; assert.deepEqual( sanitizeStorage(migrateMediaRootToLocations(withStale)), once, ); });