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,198 @@
|
||||
/**
|
||||
* Shared real-instance test fixtures for PermissionSession and
|
||||
* PermissionResolver.
|
||||
*
|
||||
* Use these instead of hand-rolling per-file mock intersection types.
|
||||
* Build a real PermissionSession from small per-collaborator fakes so tests
|
||||
* assert against actual behavior rather than mock contracts.
|
||||
*
|
||||
* Note: tests that exercise `resolveAgentName` must mock `active-agent` in
|
||||
* their own file (the vi.hoisted / vi.mock pattern from permission-session.test.ts)
|
||||
* since that mock is module-scoped.
|
||||
*/
|
||||
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
||||
import { vi } from "vitest";
|
||||
|
||||
import type { ResolvedAccessIntent } from "#src/access-intent/access-intent";
|
||||
import type { AuthorizerSelectionLifecycle } from "#src/authority/authorizer-selection";
|
||||
import type { ForwardingController } from "#src/authority/forwarding-manager";
|
||||
import type { SessionConfigStore } from "#src/config-store";
|
||||
import { DEFAULT_EXTENSION_CONFIG } from "#src/extension-config";
|
||||
import type { ExtensionPaths } from "#src/extension-paths";
|
||||
import { type PathFlavor, pathFlavorForPlatform } from "#src/path/path-flavor";
|
||||
import type { ScopedPermissionManager } from "#src/permission-manager";
|
||||
import { PermissionResolver } from "#src/permission-resolver";
|
||||
import { PermissionSession } from "#src/permission-session";
|
||||
import type { Ruleset } from "#src/rule";
|
||||
import type { SessionLogger } from "#src/session-logger";
|
||||
import { SessionRules } from "#src/session-rules";
|
||||
import type { PermissionCheckResult, PermissionState } from "#src/types";
|
||||
|
||||
// ── Per-collaborator fake factories ────────────────────────────────────────
|
||||
|
||||
export function makePaths(
|
||||
overrides: Partial<ExtensionPaths> = {},
|
||||
): ExtensionPaths {
|
||||
return {
|
||||
agentDir: "/test/agent",
|
||||
sessionsDir: "/test/agent/sessions",
|
||||
subagentSessionsDir: "/test/agent/subagent-sessions",
|
||||
forwardingDir: "/test/agent/sessions/permission-forwarding",
|
||||
globalLogsDir: "/test/agent/logs",
|
||||
piInfrastructureDirs: ["/test/agent", "/test/agent/git"],
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
export function makeLogger(): SessionLogger {
|
||||
return {
|
||||
debug: vi.fn(),
|
||||
review: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
};
|
||||
}
|
||||
|
||||
export function makeConfigStore(
|
||||
overrides: Partial<SessionConfigStore> = {},
|
||||
): SessionConfigStore {
|
||||
return {
|
||||
current:
|
||||
overrides.current ??
|
||||
vi
|
||||
.fn<() => typeof DEFAULT_EXTENSION_CONFIG>()
|
||||
.mockReturnValue({ ...DEFAULT_EXTENSION_CONFIG }),
|
||||
refresh:
|
||||
overrides.refresh ??
|
||||
vi.fn<
|
||||
(ctx: ExtensionContext | undefined, projectTrusted: boolean) => void
|
||||
>(),
|
||||
logResolvedPaths: overrides.logResolvedPaths ?? vi.fn<() => void>(),
|
||||
};
|
||||
}
|
||||
|
||||
export function makeAuthorizerSelection(): AuthorizerSelectionLifecycle {
|
||||
return {
|
||||
activate: vi.fn<AuthorizerSelectionLifecycle["activate"]>(),
|
||||
deactivate: vi.fn<AuthorizerSelectionLifecycle["deactivate"]>(),
|
||||
};
|
||||
}
|
||||
|
||||
export function makeForwarding(): ForwardingController {
|
||||
return {
|
||||
start: vi.fn(),
|
||||
stop: vi.fn(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Fake `ScopedPermissionManager` with vi.fn() stubs.
|
||||
*
|
||||
* Return type is intentionally unannotated so callers retain full `vi.fn()`
|
||||
* mock access (`mock.calls`, `toHaveBeenCalledWith`, `mockReturnValue`, etc.).
|
||||
*/
|
||||
export function makeFakePermissionManager() {
|
||||
return {
|
||||
configureForCwd: vi.fn<(cwd: string | undefined | null) => void>(),
|
||||
check: vi
|
||||
.fn<
|
||||
(
|
||||
intent: ResolvedAccessIntent,
|
||||
sessionRules?: Ruleset,
|
||||
) => PermissionCheckResult
|
||||
>()
|
||||
.mockReturnValue({
|
||||
state: "allow",
|
||||
toolName: "read",
|
||||
source: "tool",
|
||||
origin: "builtin",
|
||||
}),
|
||||
getToolPermission: vi
|
||||
.fn<(toolName: string, agentName?: string) => PermissionState>()
|
||||
.mockReturnValue("allow"),
|
||||
getConfigIssues: vi.fn((): string[] => []),
|
||||
};
|
||||
}
|
||||
|
||||
// ── Real-instance factories ────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Build a real PermissionSession from per-collaborator fakes.
|
||||
*
|
||||
* Returns the session and every collaborator so callers can destructure only
|
||||
* what they need and assert against collaborator spies directly.
|
||||
* The `permissionManager` is a `makeFakePermissionManager()` result unless
|
||||
* the caller passes an explicit `ScopedPermissionManager`.
|
||||
*/
|
||||
export function makeRealSession(overrides?: {
|
||||
paths?: Partial<ExtensionPaths>;
|
||||
logger?: SessionLogger;
|
||||
forwarding?: ForwardingController;
|
||||
permissionManager?: ScopedPermissionManager;
|
||||
sessionRules?: SessionRules;
|
||||
configStore?: SessionConfigStore;
|
||||
authorizerSelection?: AuthorizerSelectionLifecycle;
|
||||
flavor?: PathFlavor;
|
||||
}): {
|
||||
session: PermissionSession;
|
||||
paths: ExtensionPaths;
|
||||
logger: SessionLogger;
|
||||
forwarding: ForwardingController;
|
||||
permissionManager: ReturnType<typeof makeFakePermissionManager>;
|
||||
sessionRules: SessionRules;
|
||||
configStore: SessionConfigStore;
|
||||
authorizerSelection: AuthorizerSelectionLifecycle;
|
||||
} {
|
||||
const paths = makePaths(overrides?.paths);
|
||||
const logger = overrides?.logger ?? makeLogger();
|
||||
const forwarding = overrides?.forwarding ?? makeForwarding();
|
||||
const permissionManager =
|
||||
(overrides?.permissionManager as
|
||||
| ReturnType<typeof makeFakePermissionManager>
|
||||
| undefined) ?? makeFakePermissionManager();
|
||||
const sessionRules = overrides?.sessionRules ?? new SessionRules();
|
||||
const configStore = overrides?.configStore ?? makeConfigStore();
|
||||
const authorizerSelection =
|
||||
overrides?.authorizerSelection ?? makeAuthorizerSelection();
|
||||
const flavor = overrides?.flavor ?? pathFlavorForPlatform(process.platform);
|
||||
const session = new PermissionSession(
|
||||
paths,
|
||||
forwarding,
|
||||
permissionManager,
|
||||
sessionRules,
|
||||
configStore,
|
||||
authorizerSelection,
|
||||
flavor,
|
||||
);
|
||||
return {
|
||||
session,
|
||||
paths,
|
||||
logger,
|
||||
forwarding,
|
||||
permissionManager,
|
||||
sessionRules,
|
||||
configStore,
|
||||
authorizerSelection,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a real PermissionResolver from a fake manager and a SessionRules
|
||||
* instance.
|
||||
*
|
||||
* When called with no arguments, creates a fresh fake manager and fresh
|
||||
* SessionRules. Pass shared instances to connect the resolver to the same
|
||||
* manager/rules used by a real session.
|
||||
*/
|
||||
export function makeRealResolver(
|
||||
manager?: ReturnType<typeof makeFakePermissionManager>,
|
||||
sessionRules?: SessionRules,
|
||||
): {
|
||||
resolver: PermissionResolver;
|
||||
manager: ReturnType<typeof makeFakePermissionManager>;
|
||||
sessionRules: SessionRules;
|
||||
} {
|
||||
const resolvedManager = manager ?? makeFakePermissionManager();
|
||||
const resolvedRules = sessionRules ?? new SessionRules();
|
||||
const resolver = new PermissionResolver(resolvedManager, resolvedRules);
|
||||
return { resolver, manager: resolvedManager, sessionRules: resolvedRules };
|
||||
}
|
||||
Reference in New Issue
Block a user