@gotgenes/pi-permission-system 10.4.0 → 10.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/package.json +1 -1
- package/src/handlers/before-agent-start.ts +11 -6
- package/src/handlers/gates/bash-command.ts +2 -2
- package/src/handlers/gates/bash-external-directory.ts +2 -2
- package/src/handlers/gates/bash-path.ts +2 -2
- package/src/handlers/gates/path.ts +2 -2
- package/src/handlers/gates/runner.ts +2 -2
- package/src/handlers/gates/tool-call-gate-pipeline.ts +10 -9
- package/src/handlers/lifecycle.ts +7 -4
- package/src/handlers/permission-gate-handler.ts +3 -3
- package/src/index.ts +13 -4
- package/src/permission-resolver.ts +66 -2
- package/src/permission-session.ts +8 -72
- package/src/session-rules.ts +3 -2
- package/src/skill-prompt-sanitizer.ts +1 -1
- package/test/handlers/before-agent-start.test.ts +56 -86
- package/test/handlers/external-directory-session-dedup.test.ts +80 -160
- package/test/handlers/gates/bash-external-directory.test.ts +2 -2
- package/test/handlers/gates/bash-path.test.ts +2 -2
- package/test/handlers/gates/tool-call-gate-pipeline.test.ts +30 -21
- package/test/handlers/input.test.ts +5 -4
- package/test/handlers/lifecycle.test.ts +79 -85
- package/test/handlers/tool-call.test.ts +3 -2
- package/test/helpers/gate-fixtures.ts +5 -9
- package/test/helpers/handler-fixtures.ts +100 -107
- package/test/helpers/session-fixtures.ts +192 -0
- package/test/permission-resolver.test.ts +196 -0
- package/test/permission-session.test.ts +14 -266
- package/test/session-rules.test.ts +13 -5
- package/src/agent-prep-session.ts +0 -28
- package/src/gate-handler-session.ts +0 -13
- package/src/session-lifecycle-session.ts +0 -24
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared handler-level test fixtures for PermissionGateHandler tests.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* `makeHandler` builds a real PermissionSession + PermissionResolver and wires
|
|
5
|
+
* them into the handler and pipelines exactly as `index.ts` does.
|
|
6
|
+
* Call-site overrides for permission results flow through
|
|
7
|
+
* `permissionManager.checkPermission`; session state overrides are applied
|
|
8
|
+
* via vi.spyOn on the real session instance.
|
|
6
9
|
*/
|
|
7
10
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
8
11
|
import { vi } from "vitest";
|
|
9
12
|
|
|
10
13
|
import { GateDecisionReporter } from "#src/decision-reporter";
|
|
11
|
-
import { DEFAULT_EXTENSION_CONFIG } from "#src/extension-config";
|
|
12
|
-
import type { GateHandlerSession } from "#src/gate-handler-session";
|
|
13
14
|
import type { GatePrompter } from "#src/gate-prompter";
|
|
14
15
|
import { GateRunner } from "#src/handlers/gates/runner";
|
|
15
16
|
import {
|
|
@@ -24,32 +25,33 @@ import { PermissionGateHandler } from "#src/handlers/permission-gate-handler";
|
|
|
24
25
|
import type { PermissionDecisionEvent } from "#src/permission-events";
|
|
25
26
|
import { PERMISSIONS_DECISION_CHANNEL } from "#src/permission-events";
|
|
26
27
|
import type { Rule } from "#src/rule";
|
|
27
|
-
import type { SessionApprovalRecorder } from "#src/session-approval-recorder";
|
|
28
28
|
import type { SessionLogger } from "#src/session-logger";
|
|
29
|
-
import {
|
|
29
|
+
import { SessionRules } from "#src/session-rules";
|
|
30
30
|
import type { ToolRegistry } from "#src/tool-registry";
|
|
31
31
|
import type { PermissionCheckResult, PermissionState } from "#src/types";
|
|
32
|
+
import {
|
|
33
|
+
makeRealResolver,
|
|
34
|
+
makeRealSession,
|
|
35
|
+
} from "#test/helpers/session-fixtures";
|
|
36
|
+
|
|
37
|
+
// ── MockGateHandlerSession ────────────────────────────────────────────────
|
|
32
38
|
|
|
33
39
|
/**
|
|
34
|
-
*
|
|
40
|
+
* Mock type for gate-pipeline inputs (ToolCallGateInputs + SkillInputGateInputs).
|
|
35
41
|
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
42
|
+
* Used by `makeSurfaceCheck`, `makeBashCommandCheck`, and the `session`
|
|
43
|
+
* override bag in `makeHandler`. The `GateHandlerSession` role (activate +
|
|
44
|
+
* resolveAgentName) is now satisfied by the real `PermissionSession`; this
|
|
45
|
+
* type covers only the pipeline input surface.
|
|
40
46
|
*
|
|
41
|
-
* The 4-arg `checkPermission`
|
|
42
|
-
*
|
|
47
|
+
* The 4-arg `checkPermission` is a superset of `SkillInputGateInputs` —
|
|
48
|
+
* it routes through `permissionManager.checkPermission` in production.
|
|
43
49
|
*/
|
|
44
50
|
export type MockGateHandlerSession = ToolCallGateInputs &
|
|
45
|
-
SkillInputGateInputs &
|
|
46
|
-
|
|
47
|
-
GateHandlerSession & {
|
|
48
|
-
/** Logger source for the reporter the fixture builds. */
|
|
51
|
+
SkillInputGateInputs & {
|
|
52
|
+
/** Logger shape expected by GateDecisionReporter. */
|
|
49
53
|
logger: SessionLogger;
|
|
50
|
-
/**
|
|
51
|
-
getSessionRuleset(): Rule[];
|
|
52
|
-
/** 4-arg form so the resolve delegation can pass rules. */
|
|
54
|
+
/** 4-arg form so surface-check mocks can receive optional rules. */
|
|
53
55
|
checkPermission(
|
|
54
56
|
surface: string,
|
|
55
57
|
input: unknown,
|
|
@@ -58,6 +60,8 @@ export type MockGateHandlerSession = ToolCallGateInputs &
|
|
|
58
60
|
): PermissionCheckResult;
|
|
59
61
|
};
|
|
60
62
|
|
|
63
|
+
// ── Small utility factories ───────────────────────────────────────────────
|
|
64
|
+
|
|
61
65
|
export function makeEvents() {
|
|
62
66
|
return {
|
|
63
67
|
emit: vi.fn(),
|
|
@@ -117,73 +121,6 @@ export function makeCheckResult(
|
|
|
117
121
|
};
|
|
118
122
|
}
|
|
119
123
|
|
|
120
|
-
/**
|
|
121
|
-
* Full-intersection session stub.
|
|
122
|
-
*
|
|
123
|
-
* Uses per-field `??` selection (no spread) so TypeScript verifies every
|
|
124
|
-
* field against `MockGateHandlerSession` individually — a missing field fails
|
|
125
|
-
* `pnpm run check` instead of failing silently at runtime.
|
|
126
|
-
*
|
|
127
|
-
* The `resolve` delegation is inlined as a closure that reads `session` at
|
|
128
|
-
* call time, so overriding `checkPermission` or `getSessionRuleset`
|
|
129
|
-
* automatically steers it without extra guards.
|
|
130
|
-
*
|
|
131
|
-
* Prompting is not part of this mock — pass `prompter` to `makeHandler`.
|
|
132
|
-
*/
|
|
133
|
-
export function makeSession(
|
|
134
|
-
overrides: Partial<MockGateHandlerSession> = {},
|
|
135
|
-
): MockGateHandlerSession {
|
|
136
|
-
const session: MockGateHandlerSession = {
|
|
137
|
-
logger: overrides.logger ?? {
|
|
138
|
-
debug: vi.fn(),
|
|
139
|
-
review: vi.fn(),
|
|
140
|
-
warn: vi.fn(),
|
|
141
|
-
},
|
|
142
|
-
activate: overrides.activate ?? vi.fn<MockGateHandlerSession["activate"]>(),
|
|
143
|
-
resolveAgentName:
|
|
144
|
-
overrides.resolveAgentName ??
|
|
145
|
-
vi.fn<MockGateHandlerSession["resolveAgentName"]>().mockReturnValue(null),
|
|
146
|
-
checkPermission:
|
|
147
|
-
overrides.checkPermission ??
|
|
148
|
-
vi
|
|
149
|
-
.fn<MockGateHandlerSession["checkPermission"]>()
|
|
150
|
-
.mockReturnValue(makeCheckResult()),
|
|
151
|
-
getSessionRuleset:
|
|
152
|
-
overrides.getSessionRuleset ??
|
|
153
|
-
vi.fn<MockGateHandlerSession["getSessionRuleset"]>().mockReturnValue([]),
|
|
154
|
-
recordSessionApproval:
|
|
155
|
-
overrides.recordSessionApproval ??
|
|
156
|
-
vi.fn<MockGateHandlerSession["recordSessionApproval"]>(),
|
|
157
|
-
getActiveSkillEntries:
|
|
158
|
-
overrides.getActiveSkillEntries ??
|
|
159
|
-
vi
|
|
160
|
-
.fn<MockGateHandlerSession["getActiveSkillEntries"]>()
|
|
161
|
-
.mockReturnValue([]),
|
|
162
|
-
getInfrastructureReadDirs:
|
|
163
|
-
overrides.getInfrastructureReadDirs ??
|
|
164
|
-
vi
|
|
165
|
-
.fn<MockGateHandlerSession["getInfrastructureReadDirs"]>()
|
|
166
|
-
.mockReturnValue(["/test/agent", "/test/agent/git"]),
|
|
167
|
-
getToolPreviewLimits:
|
|
168
|
-
overrides.getToolPreviewLimits ??
|
|
169
|
-
vi
|
|
170
|
-
.fn<MockGateHandlerSession["getToolPreviewLimits"]>()
|
|
171
|
-
.mockReturnValue(resolveToolPreviewLimits(DEFAULT_EXTENSION_CONFIG)),
|
|
172
|
-
// Resolve delegation — closure reads `session` at call time so overrides win.
|
|
173
|
-
resolve:
|
|
174
|
-
overrides.resolve ??
|
|
175
|
-
vi.fn<MockGateHandlerSession["resolve"]>((surface, input, agentName) =>
|
|
176
|
-
session.checkPermission(
|
|
177
|
-
surface,
|
|
178
|
-
input,
|
|
179
|
-
agentName,
|
|
180
|
-
session.getSessionRuleset(),
|
|
181
|
-
),
|
|
182
|
-
),
|
|
183
|
-
};
|
|
184
|
-
return session;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
124
|
export function makeToolRegistry(
|
|
188
125
|
overrides: Partial<ToolRegistry> = {},
|
|
189
126
|
): ToolRegistry {
|
|
@@ -194,14 +131,14 @@ export function makeToolRegistry(
|
|
|
194
131
|
};
|
|
195
132
|
}
|
|
196
133
|
|
|
134
|
+
// ── Surface-check factories ────────────────────────────────────────────────
|
|
135
|
+
|
|
197
136
|
/**
|
|
198
137
|
* Surface-dispatching `checkPermission` mock.
|
|
199
138
|
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
*
|
|
203
|
-
* `origin: "builtin"` — callers override by including the field in the
|
|
204
|
-
* per-surface or default partial (e.g. `{ path: { state: "allow", source: "special" } }`).
|
|
139
|
+
* Returns the matching per-surface result or `defaultResult`.
|
|
140
|
+
* Pass the returned function as `session.checkPermission` in a `makeHandler`
|
|
141
|
+
* override bag — it is applied to `permissionManager.checkPermission`.
|
|
205
142
|
*
|
|
206
143
|
* Return type is intentionally unannotated so callers retain full `vi.fn()`
|
|
207
144
|
* mock access (`mock.calls`, `toHaveBeenCalledWith`, etc.).
|
|
@@ -231,9 +168,8 @@ export function makeSurfaceCheck(
|
|
|
231
168
|
/**
|
|
232
169
|
* Bash-surface `checkPermission` mock that dispatches on a command regex.
|
|
233
170
|
*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
* plain allow result.
|
|
171
|
+
* Pass the returned function as `session.checkPermission` in a `makeHandler`
|
|
172
|
+
* override bag — it is applied to `permissionManager.checkPermission`.
|
|
237
173
|
*
|
|
238
174
|
* Return type is intentionally unannotated so callers retain full `vi.fn()`
|
|
239
175
|
* mock access.
|
|
@@ -266,24 +202,68 @@ export function makeBashCommandCheck(opts: {
|
|
|
266
202
|
});
|
|
267
203
|
}
|
|
268
204
|
|
|
205
|
+
// ── makeHandler ────────────────────────────────────────────────────────────
|
|
206
|
+
|
|
269
207
|
/**
|
|
270
|
-
* Constructs a PermissionGateHandler with
|
|
208
|
+
* Constructs a PermissionGateHandler wired with real collaborators.
|
|
271
209
|
*
|
|
272
|
-
*
|
|
273
|
-
*
|
|
210
|
+
* The `session` override bag maps to the real collaborators:
|
|
211
|
+
* - `checkPermission` → applied to `permissionManager.checkPermission`
|
|
212
|
+
* - `getActiveSkillEntries`, `getInfrastructureReadDirs`, `getToolPreviewLimits`
|
|
213
|
+
* → applied as vi.spyOn overrides on the real session
|
|
214
|
+
* - `resolveAgentName` → applied as a vi.spyOn override on the real session
|
|
274
215
|
*
|
|
275
|
-
*
|
|
276
|
-
*
|
|
216
|
+
* Returns `{ handler, events, session, toolRegistry, prompter, recorder,
|
|
217
|
+
* permissionManager, forwarding }` so each test file can destructure only
|
|
218
|
+
* what it needs.
|
|
219
|
+
* `session.activate` is not a mock — use `forwarding.start` to assert it
|
|
220
|
+
* was called.
|
|
277
221
|
*/
|
|
278
222
|
export function makeHandler(overrides?: {
|
|
279
|
-
session?: Partial<MockGateHandlerSession
|
|
223
|
+
session?: Partial<MockGateHandlerSession> & {
|
|
224
|
+
resolveAgentName?: (
|
|
225
|
+
ctx: ExtensionContext,
|
|
226
|
+
systemPrompt?: string,
|
|
227
|
+
) => string | null;
|
|
228
|
+
};
|
|
280
229
|
/** Override the GatePrompter passed to GateRunner. Defaults to an allow-all stub. */
|
|
281
230
|
prompter?: GatePrompter;
|
|
282
231
|
toolRegistry?: Partial<ToolRegistry>;
|
|
283
232
|
/** Sugar: builds the `getAll` mock from a list of tool names. */
|
|
284
233
|
tools?: string[];
|
|
285
234
|
}) {
|
|
286
|
-
const session =
|
|
235
|
+
const { session, permissionManager, sessionRules, forwarding, logger } =
|
|
236
|
+
makeRealSession();
|
|
237
|
+
const { resolver } = makeRealResolver(permissionManager, sessionRules);
|
|
238
|
+
|
|
239
|
+
// Apply session override bag to the real collaborators.
|
|
240
|
+
const so = overrides?.session;
|
|
241
|
+
if (so?.checkPermission) {
|
|
242
|
+
vi.mocked(permissionManager.checkPermission).mockImplementation(
|
|
243
|
+
so.checkPermission,
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
if (so?.getActiveSkillEntries) {
|
|
247
|
+
vi.spyOn(session, "getActiveSkillEntries").mockImplementation(
|
|
248
|
+
so.getActiveSkillEntries,
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
if (so?.getInfrastructureReadDirs) {
|
|
252
|
+
vi.spyOn(session, "getInfrastructureReadDirs").mockImplementation(
|
|
253
|
+
so.getInfrastructureReadDirs,
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
if (so?.getToolPreviewLimits) {
|
|
257
|
+
vi.spyOn(session, "getToolPreviewLimits").mockImplementation(
|
|
258
|
+
so.getToolPreviewLimits,
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
if (so?.resolveAgentName) {
|
|
262
|
+
vi.spyOn(session, "resolveAgentName").mockImplementation(
|
|
263
|
+
so.resolveAgentName,
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
287
267
|
const events = makeEvents();
|
|
288
268
|
const toolRegistry =
|
|
289
269
|
overrides?.tools !== undefined
|
|
@@ -293,16 +273,18 @@ export function makeHandler(overrides?: {
|
|
|
293
273
|
.mockReturnValue(overrides.tools.map((name) => ({ name }))),
|
|
294
274
|
})
|
|
295
275
|
: makeToolRegistry(overrides?.toolRegistry);
|
|
296
|
-
|
|
297
|
-
const
|
|
298
|
-
const
|
|
276
|
+
|
|
277
|
+
const recorder = new SessionRules();
|
|
278
|
+
const pipeline = new ToolCallGatePipeline(resolver, session);
|
|
279
|
+
const skillInputPipeline = new SkillInputGatePipeline(resolver);
|
|
280
|
+
const reporter = new GateDecisionReporter(logger, events);
|
|
299
281
|
const prompter: GatePrompter = overrides?.prompter ?? {
|
|
300
282
|
canConfirm: vi.fn().mockReturnValue(true),
|
|
301
283
|
prompt: vi
|
|
302
284
|
.fn<GatePrompter["prompt"]>()
|
|
303
285
|
.mockResolvedValue({ approved: true, state: "approved" }),
|
|
304
286
|
};
|
|
305
|
-
const runner = new GateRunner(
|
|
287
|
+
const runner = new GateRunner(resolver, recorder, prompter, reporter);
|
|
306
288
|
const handler = new PermissionGateHandler(
|
|
307
289
|
session,
|
|
308
290
|
toolRegistry,
|
|
@@ -310,9 +292,20 @@ export function makeHandler(overrides?: {
|
|
|
310
292
|
skillInputPipeline,
|
|
311
293
|
runner,
|
|
312
294
|
);
|
|
313
|
-
return {
|
|
295
|
+
return {
|
|
296
|
+
handler,
|
|
297
|
+
events,
|
|
298
|
+
session,
|
|
299
|
+
toolRegistry,
|
|
300
|
+
prompter,
|
|
301
|
+
recorder,
|
|
302
|
+
permissionManager,
|
|
303
|
+
forwarding,
|
|
304
|
+
};
|
|
314
305
|
}
|
|
315
306
|
|
|
307
|
+
// ── Decision-event helper ─────────────────────────────────────────────────
|
|
308
|
+
|
|
316
309
|
/** Extract all permissions:decision payloads from the events.emit mock. */
|
|
317
310
|
export function getDecisionEvents(
|
|
318
311
|
events: ReturnType<typeof makeEvents>,
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared real-instance test fixtures for PermissionSession and
|
|
3
|
+
* PermissionResolver.
|
|
4
|
+
*
|
|
5
|
+
* Use these instead of hand-rolling per-file mock intersection types.
|
|
6
|
+
* Build a real PermissionSession from small per-collaborator fakes so tests
|
|
7
|
+
* assert against actual behavior rather than mock contracts.
|
|
8
|
+
*
|
|
9
|
+
* Note: tests that exercise `resolveAgentName` must mock `active-agent` in
|
|
10
|
+
* their own file (the vi.hoisted / vi.mock pattern from permission-session.test.ts)
|
|
11
|
+
* since that mock is module-scoped.
|
|
12
|
+
*/
|
|
13
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
14
|
+
import { vi } from "vitest";
|
|
15
|
+
|
|
16
|
+
import type { SessionConfigStore } from "#src/config-store";
|
|
17
|
+
import { DEFAULT_EXTENSION_CONFIG } from "#src/extension-config";
|
|
18
|
+
import type { ExtensionPaths } from "#src/extension-paths";
|
|
19
|
+
import type { ForwardingController } from "#src/forwarding-manager";
|
|
20
|
+
import type { ScopedPermissionManager } from "#src/permission-manager";
|
|
21
|
+
import { PermissionResolver } from "#src/permission-resolver";
|
|
22
|
+
import { PermissionSession } from "#src/permission-session";
|
|
23
|
+
import type { PromptingGatewayLifecycle } from "#src/prompting-gateway";
|
|
24
|
+
import type { Ruleset } from "#src/rule";
|
|
25
|
+
import type { SessionLogger } from "#src/session-logger";
|
|
26
|
+
import { SessionRules } from "#src/session-rules";
|
|
27
|
+
import type { PermissionCheckResult, PermissionState } from "#src/types";
|
|
28
|
+
|
|
29
|
+
// ── Per-collaborator fake factories ────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
export function makePaths(
|
|
32
|
+
overrides: Partial<ExtensionPaths> = {},
|
|
33
|
+
): ExtensionPaths {
|
|
34
|
+
return {
|
|
35
|
+
agentDir: "/test/agent",
|
|
36
|
+
sessionsDir: "/test/agent/sessions",
|
|
37
|
+
subagentSessionsDir: "/test/agent/subagent-sessions",
|
|
38
|
+
forwardingDir: "/test/agent/sessions/permission-forwarding",
|
|
39
|
+
globalLogsDir: "/test/agent/logs",
|
|
40
|
+
piInfrastructureDirs: ["/test/agent", "/test/agent/git"],
|
|
41
|
+
...overrides,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function makeLogger(): SessionLogger {
|
|
46
|
+
return {
|
|
47
|
+
debug: vi.fn(),
|
|
48
|
+
review: vi.fn(),
|
|
49
|
+
warn: vi.fn(),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function makeConfigStore(
|
|
54
|
+
overrides: Partial<SessionConfigStore> = {},
|
|
55
|
+
): SessionConfigStore {
|
|
56
|
+
return {
|
|
57
|
+
current:
|
|
58
|
+
overrides.current ??
|
|
59
|
+
vi
|
|
60
|
+
.fn<() => typeof DEFAULT_EXTENSION_CONFIG>()
|
|
61
|
+
.mockReturnValue({ ...DEFAULT_EXTENSION_CONFIG }),
|
|
62
|
+
refresh: overrides.refresh ?? vi.fn<(ctx?: ExtensionContext) => void>(),
|
|
63
|
+
logResolvedPaths: overrides.logResolvedPaths ?? vi.fn<() => void>(),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function makeGateway(): PromptingGatewayLifecycle {
|
|
68
|
+
return {
|
|
69
|
+
activate: vi.fn<PromptingGatewayLifecycle["activate"]>(),
|
|
70
|
+
deactivate: vi.fn<PromptingGatewayLifecycle["deactivate"]>(),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function makeForwarding(): ForwardingController {
|
|
75
|
+
return {
|
|
76
|
+
start: vi.fn(),
|
|
77
|
+
stop: vi.fn(),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Fake `ScopedPermissionManager` with vi.fn() stubs.
|
|
83
|
+
*
|
|
84
|
+
* Return type is intentionally unannotated so callers retain full `vi.fn()`
|
|
85
|
+
* mock access (`mock.calls`, `toHaveBeenCalledWith`, `mockReturnValue`, etc.).
|
|
86
|
+
*/
|
|
87
|
+
export function makeFakePermissionManager() {
|
|
88
|
+
return {
|
|
89
|
+
configureForCwd: vi.fn<(cwd: string | undefined | null) => void>(),
|
|
90
|
+
checkPermission: vi
|
|
91
|
+
.fn<
|
|
92
|
+
(
|
|
93
|
+
toolName: string,
|
|
94
|
+
input: unknown,
|
|
95
|
+
agentName?: string,
|
|
96
|
+
sessionRules?: Ruleset,
|
|
97
|
+
) => PermissionCheckResult
|
|
98
|
+
>()
|
|
99
|
+
.mockReturnValue({
|
|
100
|
+
state: "allow",
|
|
101
|
+
toolName: "read",
|
|
102
|
+
source: "tool",
|
|
103
|
+
origin: "builtin",
|
|
104
|
+
}),
|
|
105
|
+
getToolPermission: vi
|
|
106
|
+
.fn<(toolName: string, agentName?: string) => PermissionState>()
|
|
107
|
+
.mockReturnValue("allow"),
|
|
108
|
+
getConfigIssues: vi.fn((): string[] => []),
|
|
109
|
+
getPolicyCacheStamp: vi.fn((): string => "stamp-1"),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ── Real-instance factories ────────────────────────────────────────────────
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Build a real PermissionSession from per-collaborator fakes.
|
|
117
|
+
*
|
|
118
|
+
* Returns the session and every collaborator so callers can destructure only
|
|
119
|
+
* what they need and assert against collaborator spies directly.
|
|
120
|
+
* The `permissionManager` is a `makeFakePermissionManager()` result unless
|
|
121
|
+
* the caller passes an explicit `ScopedPermissionManager`.
|
|
122
|
+
*/
|
|
123
|
+
export function makeRealSession(overrides?: {
|
|
124
|
+
paths?: Partial<ExtensionPaths>;
|
|
125
|
+
logger?: SessionLogger;
|
|
126
|
+
forwarding?: ForwardingController;
|
|
127
|
+
permissionManager?: ScopedPermissionManager;
|
|
128
|
+
sessionRules?: SessionRules;
|
|
129
|
+
configStore?: SessionConfigStore;
|
|
130
|
+
gateway?: PromptingGatewayLifecycle;
|
|
131
|
+
}): {
|
|
132
|
+
session: PermissionSession;
|
|
133
|
+
paths: ExtensionPaths;
|
|
134
|
+
logger: SessionLogger;
|
|
135
|
+
forwarding: ForwardingController;
|
|
136
|
+
permissionManager: ReturnType<typeof makeFakePermissionManager>;
|
|
137
|
+
sessionRules: SessionRules;
|
|
138
|
+
configStore: SessionConfigStore;
|
|
139
|
+
gateway: PromptingGatewayLifecycle;
|
|
140
|
+
} {
|
|
141
|
+
const paths = makePaths(overrides?.paths);
|
|
142
|
+
const logger = overrides?.logger ?? makeLogger();
|
|
143
|
+
const forwarding = overrides?.forwarding ?? makeForwarding();
|
|
144
|
+
const permissionManager =
|
|
145
|
+
(overrides?.permissionManager as
|
|
146
|
+
| ReturnType<typeof makeFakePermissionManager>
|
|
147
|
+
| undefined) ?? makeFakePermissionManager();
|
|
148
|
+
const sessionRules = overrides?.sessionRules ?? new SessionRules();
|
|
149
|
+
const configStore = overrides?.configStore ?? makeConfigStore();
|
|
150
|
+
const gateway = overrides?.gateway ?? makeGateway();
|
|
151
|
+
const session = new PermissionSession(
|
|
152
|
+
paths,
|
|
153
|
+
logger,
|
|
154
|
+
forwarding,
|
|
155
|
+
permissionManager,
|
|
156
|
+
sessionRules,
|
|
157
|
+
configStore,
|
|
158
|
+
gateway,
|
|
159
|
+
);
|
|
160
|
+
return {
|
|
161
|
+
session,
|
|
162
|
+
paths,
|
|
163
|
+
logger,
|
|
164
|
+
forwarding,
|
|
165
|
+
permissionManager,
|
|
166
|
+
sessionRules,
|
|
167
|
+
configStore,
|
|
168
|
+
gateway,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Build a real PermissionResolver from a fake manager and a SessionRules
|
|
174
|
+
* instance.
|
|
175
|
+
*
|
|
176
|
+
* When called with no arguments, creates a fresh fake manager and fresh
|
|
177
|
+
* SessionRules. Pass shared instances to connect the resolver to the same
|
|
178
|
+
* manager/rules used by a real session.
|
|
179
|
+
*/
|
|
180
|
+
export function makeRealResolver(
|
|
181
|
+
manager?: ReturnType<typeof makeFakePermissionManager>,
|
|
182
|
+
sessionRules?: SessionRules,
|
|
183
|
+
): {
|
|
184
|
+
resolver: PermissionResolver;
|
|
185
|
+
manager: ReturnType<typeof makeFakePermissionManager>;
|
|
186
|
+
sessionRules: SessionRules;
|
|
187
|
+
} {
|
|
188
|
+
const resolvedManager = manager ?? makeFakePermissionManager();
|
|
189
|
+
const resolvedRules = sessionRules ?? new SessionRules();
|
|
190
|
+
const resolver = new PermissionResolver(resolvedManager, resolvedRules);
|
|
191
|
+
return { resolver, manager: resolvedManager, sessionRules: resolvedRules };
|
|
192
|
+
}
|