Files
mesh-catalog/modules/zsh/test/manifest.test.ts
T
mesh-tools 652b00b25b
mesh/merge-gate checking again: the gate refused `unknown` because the controller judged with a node-engine validator pinned before mesh-host #117; mesh-co…
mesh/repo-check checking again: the gate refused `unknown` because the controller judged with a node-engine validator pinned before mesh-host #117; mesh-co…
mesh/delivery-group group feat/owned-paths checking: 0 of 2 member(s) ready
mesh/delivery superseded: a newer head of the same pull request
Say on every directory how it treats files it does not know, and check it (issue 1024, ADR 0422)
Every directory and archive resource carries `unknown`: move-out for a home folder whose program writes nothing there (the node-engine, delivered in mesh-host #117, moves the rest to ~/.local/state/mesh-moved/<module>/ with a list), module for ~/.ssh and ~/.claude, program elsewhere; `knows` keeps what a program or the operator relies on (powerlevel10k's compiled files, i3's scripts and the asus/g14 drop-ins, dunst's slack rule, betterbird's records, node-env's forge credential, issue 1033). Two shared folders are no longer declared. Each module's README says it. checks/owned holds every manifest to it; checks/sshdir stays while main's merge check runs it (issue 1035). Tests that pin a manifest follow the new key only. Reviewed in three rounds.
2026-10-17 16:23:03 +00:00

123 lines
7.1 KiB
TypeScript

// The zsh module's shape (novox/hq ADR 0203, ADR 0204, to-be 41 WP3): it claims the mesh's
// node-login-shell and declares no seat; its ~/.zshrc block goes at the start and holds the three
// slots in order; its ~/.zshenv block sources the path node-environment fixes and nothing else; its
// environment is a contribution, never an export in its own lines.
import { test } from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
const m = JSON.parse(readFileSync(new URL("../module.json", import.meta.url), "utf8"));
const resource = (id: string) => m.resources.find((r: { id: string }) => r.id === id);
// A file's text is its template's, in the module's files/ (novox/hq ADR 0349 §2).
const text = (r: { id: string; template?: string }): string => {
assert.ok(r.template, `${r.id} names no template`);
return readFileSync(new URL(`../${r.template}`, import.meta.url), "utf8");
};
test("no file carries its text inline: each names its template, and the build packs files/ into one archive", () => {
for (const r of m.resources) {
assert.equal(r.content, undefined, `${r.id} keeps its text inline`);
if (r.type === "file") text(r);
}
const packs = m.build.artifacts.filter((a: { kind: string; from?: string }) => a.kind === "archive" && a.from === "files");
assert.deepEqual(packs.map((a: { name: string }) => a.name), ["files"]);
});
test("it claims node-login-shell serving execute, and declares no seat", () => {
assert.equal(m.seats, undefined);
assert.deepEqual(m.claims, [{ name: "node-login-shell", scope: "node", serves: ["execute"] }]);
});
test("the .zshrc block is at the start and holds the three slots in order, after the defaults' header", () => {
const rc = resource("rc");
assert.equal(rc.path, "${machine:account-home}/.zshrc");
assert.equal(rc.into, "block");
assert.equal(rc.at, "start");
const c = text(rc);
const first = c.indexOf("${shell:zsh:first}");
const normal = c.indexOf("${shell:zsh:normal}");
const last = c.indexOf("${shell:zsh:last}");
assert.ok(c.startsWith("# The mesh's block"), "it opens by saying whose it is");
assert.ok(first > 0 && first < normal && normal < last, "first, normal, last, in that order");
assert.ok(c.indexOf("set_terminal_title") > first && c.indexOf("sudo-disksize") < normal, "the defaults sit between first and normal");
for (const slot of ["first", "normal", "last"]) assert.equal(c.split(`\${shell:zsh:${slot}}`).length, 2, `${slot} appears once`);
});
test("the .zshrc block carries the shared defaults and none of the operator's own lines", () => {
const c = text(resource("rc"));
for (const want of ["precmd_functions+=(set_terminal_title)", "alias egrep='egrep --color=auto'", "alias ll='ls -alhF'", "alias drun=", "disksize()", "sudo-disksize()"]) {
assert.ok(c.includes(want), want);
}
assert.equal((c.match(/^bindkey /gm) ?? []).length, 7);
for (const not of ["export ", "p10k", "powerlevel10k", "nvm", ".dotnet", "MY_KV_PATH", "MY_LIB_PATH", "CLAUDE_CODE_DISABLE_TERMINAL_TITLE", "killport", "findport", ".zshrc.local", "zsh-autosuggestions", "zsh-syntax-highlighting"]) {
assert.ok(!c.includes(not), `the block holds no ${not}`);
}
});
test("the .zshenv block sources exactly the environment file node-environment fixes", () => {
const env = resource("env");
assert.equal(env.path, "${machine:account-home}/.zshenv");
assert.equal(env.into, "block");
assert.equal(env.at, "start");
const sourced = [...text(env).matchAll(/^\s*(?:source|\.)\s+"?([^"\s]+)"?/gm)].map((x) => x[1]);
assert.deepEqual(sourced, ["${machine:account-home}/.config/mesh/environment.sh"]);
});
test("its environment is a contribution: the editor, the configuration home, and three PATH entries at the start", () => {
// The contribution is typed items of the environment collection, each with its value in a template (ADR 0348, 0349).
const items = m.items.filter((i: { collection: string }) => i.collection === "environment");
const value = (i: { template: string }) => readFileSync(new URL(`../${i.template}`, import.meta.url), "utf8").trim();
const variables = Object.fromEntries(
items.filter((i: { attributes: { kind: string } }) => i.attributes.kind === "variable").map((i: { attributes: { name: string }; template: string }) => [i.attributes.name, value(i)]),
);
assert.deepEqual(variables, { EDITOR: "vim", VISUAL: "vim", XDG_CONFIG_HOME: "${machine:account-home}/.config" });
const path = items
.filter((i: { attributes: { kind: string } }) => i.attributes.kind === "path")
.sort((a: { attributes: { order: string } }, b: { attributes: { order: string } }) => Number(a.attributes.order) - Number(b.attributes.order))
.map((i: { attributes: { at: string }; template: string }) => ({ entry: value(i), at: i.attributes.at }));
assert.deepEqual(path, [
{ entry: "${machine:account-home}/.local/bin", at: "start" },
{ entry: "${machine:account-home}/scripts", at: "start" },
{ entry: "${machine:account-home}/scripts/bin", at: "start" },
]);
assert.equal(m.environment, undefined);
});
test("it keeps the package and sets the login shell through the user shape", () => {
assert.deepEqual(resource("package"), { id: "package", type: "package", package: "zsh" });
assert.deepEqual(resource("login"), { id: "login", type: "user", name: "${machine:account}", shell: "/usr/bin/zsh" });
assert.ok(m.capabilities.includes("package-manager"));
});
test("the tools bundle registers the seat's verb under node-login-shell", () => {
const src = readFileSync(new URL("../tools/index.ts", import.meta.url), "utf8");
assert.match(src, /registerModuleTools\("node-login-shell", seatVerbs\)/);
assert.doesNotMatch(src, /runuser|"login-shell"/);
});
test("execute is a per-machine config value, served unless a machine withholds it (ADR 0268, ADR 0348 §4)", () => {
assert.equal(m.settings, undefined);
assert.deepEqual(Object.keys(m.config), ["execute"]);
assert.equal(m.config.execute.type, "string");
assert.deepEqual(m.config.execute.enum, ["serve", "withhold"]);
assert.equal(m.config.execute.trusted, true, "it decides what runs, so it is set at the terminal or on an approval");
assert.equal(m.config.execute.default, "serve");
assert.match(m.config.execute.description, /withhold/);
const dir = resource("state");
assert.deepEqual(dir, { id: "state", type: "directory", place: ".", owner: "root", mode: "0755", unknown: "program" });
const conf = resource("tools-conf");
assert.equal(conf.type, "file");
assert.equal(conf.path, "${dir:state}/tools.conf");
assert.equal(conf.owner, "root", "the account the runtime runs as may read it, never change it");
assert.equal(conf.mode, "0644");
assert.match(text(conf), /^execute=\$\{config:execute\}$/m);
const [bundle] = m.build.artifacts;
assert.deepEqual(bundle.env, { ZSH_TOOLS_CONF: conf.path }, "the word names the file exactly, so its change restarts the runtime");
});
test("the tools bundle registers execute only where the machine's setting serves it", () => {
const src = readFileSync(new URL("../tools/index.ts", import.meta.url), "utf8");
assert.match(src, /readExecuteSetting\(env\)/);
assert.match(src, /if \(!setting\.served\)[\s\S]{0,200}return \[\]/);
});