mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-10-26 10:45:22 +00:00
refactor(permission-system): simplify policy and autoreview flow
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
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);
|
||||
});
|
||||
@@ -137,6 +137,14 @@ async function updateFixture(t: test.TestContext, piExitStatus = 0) {
|
||||
join(repositoryRoot, "scripts", "migrate-tool-search-settings.mjs"),
|
||||
join(updatedBundle, "scripts", "migrate-tool-search-settings.mjs"),
|
||||
);
|
||||
await copyFile(
|
||||
join(repositoryRoot, "config", "pi-permission-policy.jsonc"),
|
||||
join(updatedBundle, "config", "pi-permission-policy.jsonc"),
|
||||
);
|
||||
await copyFile(
|
||||
join(repositoryRoot, "scripts", "migrate-permission-policy.mjs"),
|
||||
join(updatedBundle, "scripts", "migrate-permission-policy.mjs"),
|
||||
);
|
||||
await writeFile(
|
||||
join(bin, "pi"),
|
||||
`#!/bin/sh\nprintf 'pi %s\\n' "$*" >> "$MOCK_PI_LOG"\nexit ${piExitStatus}\n`,
|
||||
|
||||
Reference in New Issue
Block a user