mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-08-28 16:45:22 +00:00
253 lines
7.7 KiB
TypeScript
253 lines
7.7 KiB
TypeScript
import { chmodSync, mkdirSync, mkdtempSync, rmSync, statSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { afterEach, beforeEach, describe, expect, it, test } from "vitest";
|
|
|
|
import type { PermissionSystemExtensionConfig } from "#src/extension-config";
|
|
import {
|
|
detectMisplacedPermissionKeys,
|
|
ensurePermissionSystemLogsDirectory,
|
|
isYoloModeEnabled,
|
|
normalizePermissionSystemConfig,
|
|
} from "#src/extension-config";
|
|
|
|
function makeConfig(
|
|
yoloMode: boolean | undefined,
|
|
): PermissionSystemExtensionConfig {
|
|
return { yoloMode } as PermissionSystemExtensionConfig;
|
|
}
|
|
|
|
describe("detectMisplacedPermissionKeys", () => {
|
|
it("returns an empty array for a record with only valid extension keys", () => {
|
|
const result = detectMisplacedPermissionKeys({
|
|
debugLog: true,
|
|
permissionReviewLog: true,
|
|
yoloMode: false,
|
|
});
|
|
expect(result).toEqual([]);
|
|
});
|
|
|
|
it("returns an empty array for an empty record", () => {
|
|
const result = detectMisplacedPermissionKeys({});
|
|
expect(result).toEqual([]);
|
|
});
|
|
|
|
it("returns misplaced key names when legacy permission-rule keys are present", () => {
|
|
const result = detectMisplacedPermissionKeys({
|
|
debugLog: true,
|
|
defaultPolicy: { tools: "ask" },
|
|
bash: { "git status": "allow" },
|
|
});
|
|
expect(result).toEqual(["defaultPolicy", "bash"]);
|
|
});
|
|
|
|
it("detects all known legacy permission-rule keys", () => {
|
|
const result = detectMisplacedPermissionKeys({
|
|
defaultPolicy: {},
|
|
tools: {},
|
|
bash: {},
|
|
mcp: {},
|
|
skills: {},
|
|
special: {},
|
|
external_directory: {},
|
|
});
|
|
expect(result).toEqual([
|
|
"defaultPolicy",
|
|
"tools",
|
|
"bash",
|
|
"mcp",
|
|
"skills",
|
|
"special",
|
|
"external_directory",
|
|
]);
|
|
});
|
|
|
|
it("does not detect doom_loop as a misplaced permission key", () => {
|
|
const result = detectMisplacedPermissionKeys({
|
|
doom_loop: {},
|
|
});
|
|
expect(result).toEqual([]);
|
|
});
|
|
|
|
it("does not flag the new flat-format permission key as misplaced", () => {
|
|
const result = detectMisplacedPermissionKeys({
|
|
debugLog: false,
|
|
permission: { "*": "ask" },
|
|
});
|
|
expect(result).toEqual([]);
|
|
});
|
|
|
|
it("ignores unknown keys that are not permission-rule keys", () => {
|
|
const result = detectMisplacedPermissionKeys({
|
|
debugLog: true,
|
|
someRandomKey: "value",
|
|
});
|
|
expect(result).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe("normalizePermissionSystemConfig", () => {
|
|
it("normalizes a valid config object", () => {
|
|
const result = normalizePermissionSystemConfig({
|
|
debugLog: true,
|
|
permissionReviewLog: false,
|
|
yoloMode: true,
|
|
});
|
|
expect(result).toEqual({
|
|
debugLog: true,
|
|
permissionReviewLog: false,
|
|
yoloMode: true,
|
|
doublePressToConfirm: true,
|
|
});
|
|
});
|
|
|
|
it("defaults debugLog to false when missing", () => {
|
|
const result = normalizePermissionSystemConfig({});
|
|
expect(result.debugLog).toBe(false);
|
|
});
|
|
|
|
it("defaults permissionReviewLog to true when missing", () => {
|
|
const result = normalizePermissionSystemConfig({});
|
|
expect(result.permissionReviewLog).toBe(true);
|
|
});
|
|
|
|
it("defaults yoloMode to false when missing", () => {
|
|
const result = normalizePermissionSystemConfig({});
|
|
expect(result.yoloMode).toBe(false);
|
|
});
|
|
|
|
it("defaults doublePressToConfirm to true when missing", () => {
|
|
const result = normalizePermissionSystemConfig({});
|
|
expect(result.doublePressToConfirm).toBe(true);
|
|
});
|
|
|
|
it("sets doublePressToConfirm false when explicitly disabled", () => {
|
|
const result = normalizePermissionSystemConfig({
|
|
doublePressToConfirm: false,
|
|
});
|
|
expect(result.doublePressToConfirm).toBe(false);
|
|
});
|
|
|
|
it("includes forwardingTimeoutMs when a valid positive integer is provided", () => {
|
|
const result = normalizePermissionSystemConfig({
|
|
forwardingTimeoutMs: 120_000,
|
|
});
|
|
expect(result.forwardingTimeoutMs).toBe(120_000);
|
|
});
|
|
|
|
it("omits forwardingTimeoutMs when absent", () => {
|
|
const result = normalizePermissionSystemConfig({});
|
|
expect("forwardingTimeoutMs" in result).toBe(false);
|
|
});
|
|
|
|
it("includes the prompt-budget knobs when provided", () => {
|
|
const result = normalizePermissionSystemConfig({
|
|
promptMaxRows: 12,
|
|
promptFieldMaxWidth: 80,
|
|
});
|
|
expect(result.promptMaxRows).toBe(12);
|
|
expect(result.promptFieldMaxWidth).toBe(80);
|
|
});
|
|
|
|
it("omits the prompt-budget knobs when absent, leaving the renderer's defaults", () => {
|
|
const result = normalizePermissionSystemConfig({});
|
|
expect("promptMaxRows" in result).toBe(false);
|
|
expect("promptFieldMaxWidth" in result).toBe(false);
|
|
});
|
|
|
|
it("includes the review-log field width when provided", () => {
|
|
expect(
|
|
normalizePermissionSystemConfig({ reviewLogFieldMaxWidth: 200 })
|
|
.reviewLogFieldMaxWidth,
|
|
).toBe(200);
|
|
});
|
|
|
|
it("omits the review-log field width when absent, leaving the writer's default", () => {
|
|
expect(
|
|
"reviewLogFieldMaxWidth" in normalizePermissionSystemConfig({}),
|
|
).toBe(false);
|
|
});
|
|
|
|
// Deliberately dropped rather than carried: a declared config field no
|
|
// runtime consumer reads is a maintenance trap, so the deprecated caps stop
|
|
// at the merge intermediate the detector reads (#745).
|
|
it("drops the deprecated preview caps even when the config sets them", () => {
|
|
const result = normalizePermissionSystemConfig({
|
|
toolInputPreviewMaxLength: 400,
|
|
toolTextSummaryMaxLength: 120,
|
|
});
|
|
expect("toolInputPreviewMaxLength" in result).toBe(false);
|
|
expect("toolTextSummaryMaxLength" in result).toBe(false);
|
|
});
|
|
|
|
it("includes shellTools when provided", () => {
|
|
const result = normalizePermissionSystemConfig({
|
|
shellTools: {
|
|
exec_command: { commandArgument: "cmd", workdirArgument: "workdir" },
|
|
},
|
|
});
|
|
expect(result.shellTools).toEqual({
|
|
exec_command: { commandArgument: "cmd", workdirArgument: "workdir" },
|
|
});
|
|
});
|
|
|
|
it("omits shellTools when absent", () => {
|
|
const result = normalizePermissionSystemConfig({});
|
|
expect("shellTools" in result).toBe(false);
|
|
});
|
|
|
|
it("includes authorizerChain when provided", () => {
|
|
const result = normalizePermissionSystemConfig({
|
|
authorizerChain: ["model-judge", "typo-reviewer"],
|
|
});
|
|
expect(result.authorizerChain).toEqual(["model-judge", "typo-reviewer"]);
|
|
});
|
|
|
|
it("omits authorizerChain when absent", () => {
|
|
const result = normalizePermissionSystemConfig({});
|
|
expect("authorizerChain" in result).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("ensurePermissionSystemLogsDirectory", () => {
|
|
let baseDir: string;
|
|
|
|
beforeEach(() => {
|
|
baseDir = mkdtempSync(join(tmpdir(), "pi-permission-system-logsdir-"));
|
|
});
|
|
|
|
afterEach(() => {
|
|
rmSync(baseDir, { recursive: true, force: true });
|
|
});
|
|
|
|
test("creates the logs directory owner-only", () => {
|
|
const logsDir = join(baseDir, "extensions", "pi-permission-system", "logs");
|
|
|
|
expect(ensurePermissionSystemLogsDirectory(logsDir)).toBe(undefined);
|
|
expect(statSync(logsDir).mode & 0o777).toBe(0o700);
|
|
});
|
|
|
|
test("tightens a directory inherited from an earlier version", () => {
|
|
const logsDir = join(baseDir, "logs");
|
|
mkdirSync(logsDir);
|
|
chmodSync(logsDir, 0o755);
|
|
|
|
expect(ensurePermissionSystemLogsDirectory(logsDir)).toBe(undefined);
|
|
expect(statSync(logsDir).mode & 0o777).toBe(0o700);
|
|
});
|
|
});
|
|
|
|
describe("isYoloModeEnabled", () => {
|
|
it("returns true when yoloMode is true", () => {
|
|
expect(isYoloModeEnabled(makeConfig(true))).toBe(true);
|
|
});
|
|
|
|
it("returns false when yoloMode is false", () => {
|
|
expect(isYoloModeEnabled(makeConfig(false))).toBe(false);
|
|
});
|
|
|
|
it("returns false when yoloMode is undefined", () => {
|
|
expect(isYoloModeEnabled(makeConfig(undefined))).toBe(false);
|
|
});
|
|
});
|