mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-08-28 08:35:57 +00:00
feat: vendor permission system source
This commit is contained in:
@@ -0,0 +1,247 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { normalizeFlatConfig } from "#src/normalize";
|
||||
|
||||
describe("normalizeFlatConfig", () => {
|
||||
describe("string shorthand", () => {
|
||||
test("string value produces a single catch-all rule for the surface", () => {
|
||||
const result = normalizeFlatConfig({ read: "allow" });
|
||||
expect(result).toEqual([
|
||||
{ surface: "read", pattern: "*", action: "allow", origin: "builtin" },
|
||||
]);
|
||||
});
|
||||
|
||||
test("string shorthand works for multiple surfaces", () => {
|
||||
const result = normalizeFlatConfig({ read: "allow", write: "deny" });
|
||||
expect(result).toEqual([
|
||||
{ surface: "read", pattern: "*", action: "allow", origin: "builtin" },
|
||||
{ surface: "write", pattern: "*", action: "deny", origin: "builtin" },
|
||||
]);
|
||||
});
|
||||
|
||||
test("universal fallback '*' becomes a catch-all rule with surface '*'", () => {
|
||||
const result = normalizeFlatConfig({ "*": "ask" });
|
||||
expect(result).toEqual([
|
||||
{ surface: "*", pattern: "*", action: "ask", origin: "builtin" },
|
||||
]);
|
||||
});
|
||||
|
||||
test("external_directory string shorthand maps directly to its surface", () => {
|
||||
const result = normalizeFlatConfig({ external_directory: "ask" });
|
||||
expect(result).toEqual([
|
||||
{
|
||||
surface: "external_directory",
|
||||
pattern: "*",
|
||||
action: "ask",
|
||||
origin: "builtin",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("invalid string values (non-PermissionState) are ignored", () => {
|
||||
const result = normalizeFlatConfig({
|
||||
read: "allow",
|
||||
write: "invalid" as never,
|
||||
});
|
||||
expect(result).toEqual([
|
||||
{ surface: "read", pattern: "*", action: "allow", origin: "builtin" },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("object pattern map", () => {
|
||||
test("object value produces one rule per pattern", () => {
|
||||
const result = normalizeFlatConfig({
|
||||
bash: { "*": "ask", "git *": "allow" },
|
||||
});
|
||||
expect(result).toEqual([
|
||||
{ surface: "bash", pattern: "*", action: "ask", origin: "builtin" },
|
||||
{
|
||||
surface: "bash",
|
||||
pattern: "git *",
|
||||
action: "allow",
|
||||
origin: "builtin",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("mcp object map produces rules with surface 'mcp'", () => {
|
||||
const result = normalizeFlatConfig({
|
||||
mcp: { "*": "ask", mcp_status: "allow" },
|
||||
});
|
||||
expect(result).toEqual([
|
||||
{ surface: "mcp", pattern: "*", action: "ask", origin: "builtin" },
|
||||
{
|
||||
surface: "mcp",
|
||||
pattern: "mcp_status",
|
||||
action: "allow",
|
||||
origin: "builtin",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("skill object map produces rules with surface 'skill'", () => {
|
||||
const result = normalizeFlatConfig({
|
||||
skill: { "*": "ask", librarian: "allow" },
|
||||
});
|
||||
expect(result).toEqual([
|
||||
{ surface: "skill", pattern: "*", action: "ask", origin: "builtin" },
|
||||
{
|
||||
surface: "skill",
|
||||
pattern: "librarian",
|
||||
action: "allow",
|
||||
origin: "builtin",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("invalid action values in object map are ignored", () => {
|
||||
const result = normalizeFlatConfig({
|
||||
bash: { "git *": "allow", "rm -rf *": "bad" as never },
|
||||
});
|
||||
expect(result).toEqual([
|
||||
{
|
||||
surface: "bash",
|
||||
pattern: "git *",
|
||||
action: "allow",
|
||||
origin: "builtin",
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("mixed surfaces", () => {
|
||||
test("full mixed config produces rules in insertion order", () => {
|
||||
const result = normalizeFlatConfig({
|
||||
"*": "ask",
|
||||
read: "allow",
|
||||
write: "deny",
|
||||
bash: { "*": "ask", "git *": "allow" },
|
||||
mcp: { mcp_status: "allow" },
|
||||
skill: { "*": "ask" },
|
||||
external_directory: "ask",
|
||||
});
|
||||
expect(result).toEqual([
|
||||
{ surface: "*", pattern: "*", action: "ask", origin: "builtin" },
|
||||
{ surface: "read", pattern: "*", action: "allow", origin: "builtin" },
|
||||
{ surface: "write", pattern: "*", action: "deny", origin: "builtin" },
|
||||
{ surface: "bash", pattern: "*", action: "ask", origin: "builtin" },
|
||||
{
|
||||
surface: "bash",
|
||||
pattern: "git *",
|
||||
action: "allow",
|
||||
origin: "builtin",
|
||||
},
|
||||
{
|
||||
surface: "mcp",
|
||||
pattern: "mcp_status",
|
||||
action: "allow",
|
||||
origin: "builtin",
|
||||
},
|
||||
{ surface: "skill", pattern: "*", action: "ask", origin: "builtin" },
|
||||
{
|
||||
surface: "external_directory",
|
||||
pattern: "*",
|
||||
action: "ask",
|
||||
origin: "builtin",
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("empty and edge cases", () => {
|
||||
test("empty permission object produces empty ruleset", () => {
|
||||
expect(normalizeFlatConfig({})).toEqual([]);
|
||||
});
|
||||
|
||||
test("non-object values (null, array) nested in map are skipped", () => {
|
||||
const result = normalizeFlatConfig({
|
||||
bash: null as never,
|
||||
read: "allow",
|
||||
});
|
||||
expect(result).toEqual([
|
||||
{ surface: "read", pattern: "*", action: "allow", origin: "builtin" },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("deny with reason", () => {
|
||||
test("{ action: 'deny', reason } produces a deny rule carrying the reason", () => {
|
||||
const result = normalizeFlatConfig({
|
||||
bash: { "npm *": { action: "deny", reason: "Use pnpm instead" } },
|
||||
});
|
||||
expect(result).toEqual([
|
||||
{
|
||||
surface: "bash",
|
||||
pattern: "npm *",
|
||||
action: "deny",
|
||||
reason: "Use pnpm instead",
|
||||
origin: "builtin",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("{ action: 'deny' } without a reason produces a deny rule without reason", () => {
|
||||
const result = normalizeFlatConfig({
|
||||
bash: { "rm -rf *": { action: "deny" } },
|
||||
});
|
||||
expect(result).toEqual([
|
||||
{
|
||||
surface: "bash",
|
||||
pattern: "rm -rf *",
|
||||
action: "deny",
|
||||
origin: "builtin",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("deny-with-reason and plain strings coexist in the same surface", () => {
|
||||
const result = normalizeFlatConfig({
|
||||
bash: {
|
||||
"git *": "allow",
|
||||
"npm *": { action: "deny", reason: "Use pnpm" },
|
||||
"*": "ask",
|
||||
},
|
||||
});
|
||||
expect(result).toEqual([
|
||||
{
|
||||
surface: "bash",
|
||||
pattern: "git *",
|
||||
action: "allow",
|
||||
origin: "builtin",
|
||||
},
|
||||
{
|
||||
surface: "bash",
|
||||
pattern: "npm *",
|
||||
action: "deny",
|
||||
reason: "Use pnpm",
|
||||
origin: "builtin",
|
||||
},
|
||||
{ surface: "bash", pattern: "*", action: "ask", origin: "builtin" },
|
||||
]);
|
||||
});
|
||||
|
||||
test("top-level deny-with-reason object is treated as a pattern map", () => {
|
||||
// At the surface level, { action: "deny", reason: "..." } is parsed as a
|
||||
// pattern→action map: "action" is a pattern key with action "deny", and
|
||||
// "reason" maps to a non-PermissionState string that is dropped.
|
||||
const result = normalizeFlatConfig({
|
||||
bash: { action: "deny", reason: "Not allowed" } as never,
|
||||
});
|
||||
expect(result).toEqual([
|
||||
{
|
||||
surface: "bash",
|
||||
pattern: "action",
|
||||
action: "deny",
|
||||
origin: "builtin",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("non-string reason is rejected (malformed config)", () => {
|
||||
const result = normalizeFlatConfig({
|
||||
bash: { "npm *": { action: "deny", reason: 42 } as never },
|
||||
});
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user