mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-10-26 18:55:22 +00:00
130 lines
4.6 KiB
TypeScript
130 lines
4.6 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { mkdtemp, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import test from "node:test";
|
|
|
|
import {
|
|
LEGACY_PERMISSION_POLICY_HASHES,
|
|
migratePermissionPolicy,
|
|
permissionPolicySemanticHash,
|
|
} from "../scripts/migrate-permission-policy.mjs";
|
|
|
|
const knownLegacyHash =
|
|
"9abf020e80fceae8c52de3e8b758822dfc86cb90e9083381e2569dde1e29400a";
|
|
|
|
async function fixture(t: test.TestContext) {
|
|
const directory = await mkdtemp(join(tmpdir(), "permission-policy-migration-"));
|
|
t.after(() => rm(directory, { recursive: true, force: true }));
|
|
const policyPath = join(directory, "policy.jsonc");
|
|
const seedPath = join(directory, "seed.jsonc");
|
|
const seed = `{
|
|
// bundled policy
|
|
"permission": {
|
|
"*": "ask",
|
|
"read": "allow"
|
|
}
|
|
}\n`;
|
|
await writeFile(seedPath, seed, { mode: 0o600 });
|
|
return { directory, policyPath, seedPath, seed };
|
|
}
|
|
|
|
test("recognizes the exact previously installed policy snapshot", () => {
|
|
assert.equal(LEGACY_PERMISSION_POLICY_HASHES.has(knownLegacyHash), true);
|
|
});
|
|
|
|
test("semantic hashes ignore JSONC comments, whitespace, and object key order", () => {
|
|
const left = `{
|
|
// comment
|
|
"permission": { "read": "allow", "*": "ask" },
|
|
"analysis": { "enabled": true }
|
|
}`;
|
|
const right = `{"analysis":{"enabled":true},"permission":{"*":"ask","read":"allow"}}`;
|
|
assert.equal(permissionPolicySemanticHash(left), permissionPolicySemanticHash(right));
|
|
});
|
|
|
|
test("migrates only an exact legacy semantic snapshot and creates private files", async (t) => {
|
|
const { directory, policyPath, seedPath, seed } = await fixture(t);
|
|
const legacy = `{
|
|
// formatting does not matter
|
|
"permission": { "*": "allow", "path": { "**/.env": "deny" } }
|
|
}\n`;
|
|
await writeFile(policyPath, legacy, { mode: 0o644 });
|
|
|
|
const result = await migratePermissionPolicy(policyPath, seedPath, {
|
|
legacyHashes: new Set([permissionPolicySemanticHash(legacy)]),
|
|
now: new Date("2026-09-02T03:04:05.000Z"),
|
|
});
|
|
|
|
assert.equal(result.status, "migrated");
|
|
assert.equal(await readFile(policyPath, "utf8"), seed);
|
|
assert.equal((await stat(policyPath)).mode & 0o777, 0o600);
|
|
assert.ok(result.backupPath);
|
|
assert.equal(await readFile(result.backupPath!, "utf8"), legacy);
|
|
assert.equal((await stat(result.backupPath!)).mode & 0o777, 0o600);
|
|
assert.equal(
|
|
(await readdir(directory)).some((name) => name.includes(".tmp.")),
|
|
false,
|
|
);
|
|
});
|
|
|
|
test("preserves customized policy content", async (t) => {
|
|
const { policyPath, seedPath } = await fixture(t);
|
|
const custom = `{"permission":{"*":"deny"}}\n`;
|
|
await writeFile(policyPath, custom, { mode: 0o600 });
|
|
|
|
const result = await migratePermissionPolicy(policyPath, seedPath);
|
|
|
|
assert.deepEqual(result, { status: "custom" });
|
|
assert.equal(await readFile(policyPath, "utf8"), custom);
|
|
});
|
|
|
|
test("treats the bundled seed as current and repairs its mode", async (t) => {
|
|
const { policyPath, seedPath, seed } = await fixture(t);
|
|
await writeFile(policyPath, seed, { mode: 0o644 });
|
|
|
|
const result = await migratePermissionPolicy(policyPath, seedPath);
|
|
|
|
assert.deepEqual(result, { status: "current" });
|
|
assert.equal((await stat(policyPath)).mode & 0o777, 0o600);
|
|
});
|
|
|
|
test("returns missing without creating a policy", async (t) => {
|
|
const { policyPath, seedPath } = await fixture(t);
|
|
assert.deepEqual(await migratePermissionPolicy(policyPath, seedPath), {
|
|
status: "missing",
|
|
});
|
|
});
|
|
|
|
test("rejects invalid current JSONC without replacing it", async (t) => {
|
|
const { policyPath, seedPath } = await fixture(t);
|
|
const invalid = "{ invalid\n";
|
|
await writeFile(policyPath, invalid, { mode: 0o600 });
|
|
|
|
await assert.rejects(
|
|
migratePermissionPolicy(policyPath, seedPath),
|
|
/invalid JSONC/,
|
|
);
|
|
assert.equal(await readFile(policyPath, "utf8"), invalid);
|
|
});
|
|
|
|
test("aborts if the policy changes before commit", async (t) => {
|
|
const { directory, policyPath, seedPath } = await fixture(t);
|
|
const legacy = `{"permission":{"*":"allow"}}\n`;
|
|
const concurrent = `{"permission":{"*":"deny"}}\n`;
|
|
await writeFile(policyPath, legacy, { mode: 0o600 });
|
|
|
|
await assert.rejects(
|
|
migratePermissionPolicy(policyPath, seedPath, {
|
|
legacyHashes: new Set([permissionPolicySemanticHash(legacy)]),
|
|
beforeCommit: () => writeFile(policyPath, concurrent, "utf8"),
|
|
}),
|
|
/changed during migration/,
|
|
);
|
|
|
|
assert.equal(await readFile(policyPath, "utf8"), concurrent);
|
|
const entries = await readdir(directory);
|
|
assert.equal(entries.some((name) => name.includes(".bak.")), true);
|
|
assert.equal(entries.some((name) => name.includes(".tmp.")), false);
|
|
});
|