Files
my-pi/pi-permission-system/test/helpers/session-fixtures.ts
T

199 lines
6.7 KiB
TypeScript

/**
* 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 };
}