Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/chatSessions/claude/common/test/claudeToolPermissionRegistry.spec.ts
13406 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
7
import { ClaudeToolPermissionResult, IClaudeToolPermissionHandler } from '../claudeToolPermission';
8
import { getToolPermissionHandlerRegistry, registerToolPermissionHandler } from '../claudeToolPermissionRegistry';
9
import { ClaudeToolNames } from '../claudeTools';
10
11
// Import handlers to ensure they're registered
12
import '../toolPermissionHandlers/index';
13
14
describe('claudeToolPermissionRegistry', () => {
15
// Store original registry length to detect changes
16
let originalRegistryLength: number;
17
18
beforeEach(() => {
19
originalRegistryLength = getToolPermissionHandlerRegistry().length;
20
});
21
22
afterEach(() => {
23
// Note: Since the registry is a module-level array, registrations persist.
24
// Tests should account for this by checking relative changes.
25
});
26
27
describe('registerToolPermissionHandler', () => {
28
it('registers a handler for a single tool', () => {
29
class TestHandler implements IClaudeToolPermissionHandler<ClaudeToolNames.Glob> {
30
readonly toolNames = [ClaudeToolNames.Glob] as const;
31
}
32
33
registerToolPermissionHandler([ClaudeToolNames.Glob], TestHandler);
34
35
const registry = getToolPermissionHandlerRegistry();
36
expect(registry.length).toBe(originalRegistryLength + 1);
37
38
const lastRegistration = registry[registry.length - 1];
39
expect(lastRegistration.toolNames).toEqual([ClaudeToolNames.Glob]);
40
expect(lastRegistration.ctor).toBe(TestHandler);
41
});
42
43
it('registers a handler for multiple tools', () => {
44
class MultiToolHandler implements IClaudeToolPermissionHandler<ClaudeToolNames.Read | ClaudeToolNames.LS> {
45
readonly toolNames = [ClaudeToolNames.Read, ClaudeToolNames.LS] as const;
46
}
47
48
registerToolPermissionHandler([ClaudeToolNames.Read, ClaudeToolNames.LS], MultiToolHandler);
49
50
const registry = getToolPermissionHandlerRegistry();
51
const lastRegistration = registry[registry.length - 1];
52
expect(lastRegistration.toolNames).toEqual([ClaudeToolNames.Read, ClaudeToolNames.LS]);
53
});
54
55
it('allows registering handlers with custom methods', () => {
56
class CustomHandler implements IClaudeToolPermissionHandler<ClaudeToolNames.Grep> {
57
readonly toolNames = [ClaudeToolNames.Grep] as const;
58
59
async canAutoApprove(): Promise<boolean> {
60
return true;
61
}
62
63
getConfirmationParams() {
64
return {
65
title: 'Test',
66
message: 'Test message'
67
};
68
}
69
70
async handle(): Promise<ClaudeToolPermissionResult> {
71
return { behavior: 'allow', updatedInput: {} };
72
}
73
}
74
75
registerToolPermissionHandler([ClaudeToolNames.Grep], CustomHandler);
76
77
const registry = getToolPermissionHandlerRegistry();
78
const lastRegistration = registry[registry.length - 1];
79
expect(lastRegistration.ctor).toBe(CustomHandler);
80
});
81
});
82
83
describe('getToolPermissionHandlerRegistry', () => {
84
it('returns a readonly array', () => {
85
const registry = getToolPermissionHandlerRegistry();
86
expect(Array.isArray(registry)).toBe(true);
87
});
88
89
it('returns consistent results on multiple calls', () => {
90
const registry1 = getToolPermissionHandlerRegistry();
91
const registry2 = getToolPermissionHandlerRegistry();
92
expect(registry1).toBe(registry2);
93
});
94
95
it('contains registrations from imported handlers', () => {
96
// Handlers are imported at module level above
97
const registry = getToolPermissionHandlerRegistry();
98
99
// Should have at least the Bash and ExitPlanMode handlers
100
const hasBashHandler = registry.some(r => r.toolNames.includes(ClaudeToolNames.Bash));
101
const hasExitPlanModeHandler = registry.some(r => r.toolNames.includes(ClaudeToolNames.ExitPlanMode));
102
103
expect(hasBashHandler).toBe(true);
104
expect(hasExitPlanModeHandler).toBe(true);
105
});
106
});
107
108
describe('registry behavior', () => {
109
it('allows multiple handlers for different tools', () => {
110
class Handler1 implements IClaudeToolPermissionHandler<ClaudeToolNames.WebFetch> {
111
readonly toolNames = [ClaudeToolNames.WebFetch] as const;
112
}
113
114
class Handler2 implements IClaudeToolPermissionHandler<ClaudeToolNames.WebSearch> {
115
readonly toolNames = [ClaudeToolNames.WebSearch] as const;
116
}
117
118
const startLength = getToolPermissionHandlerRegistry().length;
119
120
registerToolPermissionHandler([ClaudeToolNames.WebFetch], Handler1);
121
registerToolPermissionHandler([ClaudeToolNames.WebSearch], Handler2);
122
123
const registry = getToolPermissionHandlerRegistry();
124
expect(registry.length).toBe(startLength + 2);
125
});
126
127
it('preserves registration order', () => {
128
class FirstHandler implements IClaudeToolPermissionHandler<ClaudeToolNames.Task> {
129
readonly toolNames = [ClaudeToolNames.Task] as const;
130
}
131
132
class SecondHandler implements IClaudeToolPermissionHandler<ClaudeToolNames.TodoWrite> {
133
readonly toolNames = [ClaudeToolNames.TodoWrite] as const;
134
}
135
136
const startLength = getToolPermissionHandlerRegistry().length;
137
138
registerToolPermissionHandler([ClaudeToolNames.Task], FirstHandler);
139
registerToolPermissionHandler([ClaudeToolNames.TodoWrite], SecondHandler);
140
141
const registry = getToolPermissionHandlerRegistry();
142
expect(registry[startLength].ctor).toBe(FirstHandler);
143
expect(registry[startLength + 1].ctor).toBe(SecondHandler);
144
});
145
});
146
});
147
148