git mv packages/business -> packages/organization (@mosaic/organization). The old package stays as one-line re-export shims of each moved module, with src/business.mjs re-exporting src/organization-config.mjs and src/cli.mjs forwarding to the moved executable. BusinessError is OrganizationConfigError (one constructor, name kept BusinessError); OrganizationError stays distinct. The bus adapter gains organization-context.mjs (busOrganization), with business.mjs (busBusiness) as the same function. Import consumers, manifests, the lockfile, scripts/mosaic, stack-copy.sh COPY_PATHS (both roots), seat sandbox RUNNER_FILES and the copy closure walker (organization-imports.mjs, both roots required) move to the new path. v1 serialization, v1 writers and the stage A fixtures are unchanged; the stage A contract tests stay where they were and pass as before.
115 lines
6.5 KiB
JavaScript
115 lines
6.5 KiB
JavaScript
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { chmodSync, mkdirSync, symlinkSync, writeFileSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import { BusinessError, checkCredentialRef, parseCredentialRef } from "../src/index.mjs";
|
|
import { FAR_DATE, tmp, tokenFile } from "./helpers.mjs";
|
|
|
|
function refuses(fn, pattern) {
|
|
assert.throws(fn, (error) => {
|
|
assert.ok(error instanceof BusinessError, `expected BusinessError, got ${error}`);
|
|
assert.equal(error.exitCode, 2);
|
|
assert.match(error.message, pattern);
|
|
return true;
|
|
});
|
|
}
|
|
|
|
const NOW = new Date("2026-10-04T12:00:00Z");
|
|
|
|
test("parse: exactly one of file or env, plus the service's date", () => {
|
|
assert.deepEqual({ ...parseCredentialRef({ file: "/s/pm-gitea.token", rotateBy: FAR_DATE }, "gitea", "r") },
|
|
{ service: "gitea", file: "/s/pm-gitea.token", rotateBy: FAR_DATE });
|
|
assert.deepEqual({ ...parseCredentialRef({ env: "PM_VIKUNJA_TOKEN", expires: FAR_DATE }, "vikunja", "r") },
|
|
{ service: "vikunja", env: "PM_VIKUNJA_TOKEN", expires: FAR_DATE });
|
|
refuses(() => parseCredentialRef({ file: "/a", env: "A", rotateBy: FAR_DATE }, "gitea", "r"), /exactly one/);
|
|
refuses(() => parseCredentialRef({ rotateBy: FAR_DATE }, "gitea", "r"), /exactly one/);
|
|
refuses(() => parseCredentialRef({ file: "/a" }, "gitea", "r"), /needs "rotateBy"/);
|
|
refuses(() => parseCredentialRef({ file: "/a", expires: FAR_DATE }, "gitea", "r"), /unsupported r key: "expires"/);
|
|
refuses(() => parseCredentialRef({ file: "/a", rotateBy: FAR_DATE }, "vikunja", "r"), /unsupported r key: "rotateBy"/);
|
|
refuses(() => parseCredentialRef({ file: "/a", expires: FAR_DATE, token: "x" }, "vikunja", "r"), /unsupported r key: "token"/);
|
|
refuses(() => parseCredentialRef({ file: "a/b", rotateBy: FAR_DATE }, "gitea", "r"), /normalized absolute path/);
|
|
refuses(() => parseCredentialRef({ file: "/a/../b", rotateBy: FAR_DATE }, "gitea", "r"), /normalized absolute path/);
|
|
refuses(() => parseCredentialRef({ file: "/a//b", rotateBy: FAR_DATE }, "gitea", "r"), /normalized absolute path/);
|
|
refuses(() => parseCredentialRef({ env: "pm_token", rotateBy: FAR_DATE }, "gitea", "r"), /\.env must match/);
|
|
refuses(() => parseCredentialRef({ env: "A", rotateBy: "2026-02-30" }, "gitea", "r"), /not a real date/);
|
|
refuses(() => parseCredentialRef({ env: "A", expires: "2026-10-04T00:00:00Z" }, "vikunja", "r"), /YYYY-MM-DD/);
|
|
refuses(() => parseCredentialRef({ env: "A" }, "github", "r"), /unknown credential service/);
|
|
});
|
|
|
|
test("check: a good file has no problems", () => {
|
|
const dir = tmp();
|
|
const ref = parseCredentialRef({ file: tokenFile(join(dir, "s"), "t"), expires: FAR_DATE }, "vikunja", "r");
|
|
assert.deepEqual(checkCredentialRef(ref, { now: NOW, forbiddenRoots: [join(dir, "repo")] }), { problems: [], warnings: [] });
|
|
});
|
|
|
|
test("check never opens the file: a write-only token passes", () => {
|
|
const dir = tmp();
|
|
const file = tokenFile(join(dir, "s"), "t");
|
|
chmodSync(file, 0o200);
|
|
const ref = parseCredentialRef({ file, rotateBy: FAR_DATE }, "gitea", "r");
|
|
assert.deepEqual(checkCredentialRef(ref, { now: NOW }).problems, []);
|
|
});
|
|
|
|
test("check: file problems", () => {
|
|
const dir = tmp();
|
|
const s = join(dir, "s");
|
|
const problems = (file, opts = {}) => checkCredentialRef(parseCredentialRef({ file, rotateBy: FAR_DATE }, "gitea", "r"), { now: NOW, ...opts }).problems;
|
|
assert.match(problems(join(s, "absent")).join(), /not found/);
|
|
|
|
const loose = tokenFile(s, "loose");
|
|
chmodSync(loose, 0o640);
|
|
assert.match(problems(loose).join(), /mode 640 gives group or other access/);
|
|
|
|
const empty = join(s, "empty");
|
|
writeFileSync(empty, "");
|
|
chmodSync(empty, 0o600);
|
|
assert.match(problems(empty).join(), /empty/);
|
|
|
|
const link = join(s, "link");
|
|
symlinkSync(tokenFile(s, "real"), link);
|
|
assert.match(problems(link).join(), /not a symbolic link/);
|
|
|
|
mkdirSync(join(s, "dir"));
|
|
assert.match(problems(join(s, "dir")).join(), /regular file/);
|
|
|
|
assert.deepEqual(problems(tokenFile(s, "other")), []);
|
|
assert.match(problems(tokenFile(s, "uid"), { uid: process.getuid() + 1 }).join(), /owned by uid/);
|
|
});
|
|
|
|
test("check: token files can't live in the repository or dataRoot, even through a linked directory", () => {
|
|
const dir = tmp();
|
|
const repo = join(dir, "repo");
|
|
const inRepo = tokenFile(join(repo, "secrets"), "t");
|
|
const ref = (file) => parseCredentialRef({ file, rotateBy: FAR_DATE }, "gitea", "r");
|
|
assert.match(checkCredentialRef(ref(inRepo), { now: NOW, forbiddenRoots: [repo] }).problems.join(), /inside .*repo/);
|
|
symlinkSync(join(repo, "secrets"), join(dir, "elsewhere"));
|
|
const viaLink = join(dir, "elsewhere", "t");
|
|
assert.match(checkCredentialRef(ref(viaLink), { now: NOW, forbiddenRoots: [repo] }).problems.join(), /inside/);
|
|
const repoLink = join(dir, "repo-link");
|
|
symlinkSync(repo, repoLink);
|
|
assert.match(checkCredentialRef(ref(inRepo), { now: NOW, forbiddenRoots: [repoLink] }).problems.join(), /inside/);
|
|
const sibling = tokenFile(join(dir, "repo-secrets"), "t");
|
|
assert.deepEqual(checkCredentialRef(ref(sibling), { now: NOW, forbiddenRoots: [repo, join(dir, "absent-root")] }).problems, []);
|
|
});
|
|
|
|
test("check: dates and environment references", () => {
|
|
const vikunja = (expires) => checkCredentialRef(parseCredentialRef({ env: "TOK", expires }, "vikunja", "r"), { now: NOW, env: { TOK: "set" } });
|
|
assert.match(vikunja("2026-10-04").problems.join(), /expired on 2026-10-04/);
|
|
assert.match(vikunja("2026-10-01").problems.join(), /expired/);
|
|
assert.deepEqual(vikunja("2026-10-05").problems, []);
|
|
assert.match(vikunja("2026-10-05").warnings.join(), /expires on 2026-10-05/);
|
|
assert.match(vikunja("2026-10-10").warnings.join(), /expires on/);
|
|
assert.deepEqual(vikunja("2026-10-12"), { problems: [], warnings: [] });
|
|
|
|
const gitea = (rotateBy) => checkCredentialRef(parseCredentialRef({ env: "TOK", rotateBy }, "gitea", "r"), { now: NOW, env: { TOK: "set" } });
|
|
assert.deepEqual(gitea("2026-10-01").problems, []);
|
|
assert.match(gitea("2026-10-01").warnings.join(), /rotation was due on 2026-10-01/);
|
|
assert.deepEqual(gitea(FAR_DATE), { problems: [], warnings: [] });
|
|
|
|
const unset = checkCredentialRef(parseCredentialRef({ env: "TOK", rotateBy: FAR_DATE }, "gitea", "r"), { now: NOW, env: {} });
|
|
assert.deepEqual(unset.problems, []);
|
|
assert.match(unset.warnings.join(), /not set in this environment/);
|
|
const blank = checkCredentialRef(parseCredentialRef({ env: "TOK", rotateBy: FAR_DATE }, "gitea", "r"), { now: NOW, env: { TOK: "" } });
|
|
assert.match(blank.warnings.join(), /not set/);
|
|
});
|