mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-08-28 08:35:57 +00:00
248 lines
7.4 KiB
TypeScript
248 lines
7.4 KiB
TypeScript
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([]);
|
|
});
|
|
});
|
|
});
|