Path: blob/main/extensions/copilot/src/extension/chatSessions/claude/common/test/claudeToolPermissionRegistry.spec.ts
13406 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { afterEach, beforeEach, describe, expect, it } from 'vitest';6import { ClaudeToolPermissionResult, IClaudeToolPermissionHandler } from '../claudeToolPermission';7import { getToolPermissionHandlerRegistry, registerToolPermissionHandler } from '../claudeToolPermissionRegistry';8import { ClaudeToolNames } from '../claudeTools';910// Import handlers to ensure they're registered11import '../toolPermissionHandlers/index';1213describe('claudeToolPermissionRegistry', () => {14// Store original registry length to detect changes15let originalRegistryLength: number;1617beforeEach(() => {18originalRegistryLength = getToolPermissionHandlerRegistry().length;19});2021afterEach(() => {22// Note: Since the registry is a module-level array, registrations persist.23// Tests should account for this by checking relative changes.24});2526describe('registerToolPermissionHandler', () => {27it('registers a handler for a single tool', () => {28class TestHandler implements IClaudeToolPermissionHandler<ClaudeToolNames.Glob> {29readonly toolNames = [ClaudeToolNames.Glob] as const;30}3132registerToolPermissionHandler([ClaudeToolNames.Glob], TestHandler);3334const registry = getToolPermissionHandlerRegistry();35expect(registry.length).toBe(originalRegistryLength + 1);3637const lastRegistration = registry[registry.length - 1];38expect(lastRegistration.toolNames).toEqual([ClaudeToolNames.Glob]);39expect(lastRegistration.ctor).toBe(TestHandler);40});4142it('registers a handler for multiple tools', () => {43class MultiToolHandler implements IClaudeToolPermissionHandler<ClaudeToolNames.Read | ClaudeToolNames.LS> {44readonly toolNames = [ClaudeToolNames.Read, ClaudeToolNames.LS] as const;45}4647registerToolPermissionHandler([ClaudeToolNames.Read, ClaudeToolNames.LS], MultiToolHandler);4849const registry = getToolPermissionHandlerRegistry();50const lastRegistration = registry[registry.length - 1];51expect(lastRegistration.toolNames).toEqual([ClaudeToolNames.Read, ClaudeToolNames.LS]);52});5354it('allows registering handlers with custom methods', () => {55class CustomHandler implements IClaudeToolPermissionHandler<ClaudeToolNames.Grep> {56readonly toolNames = [ClaudeToolNames.Grep] as const;5758async canAutoApprove(): Promise<boolean> {59return true;60}6162getConfirmationParams() {63return {64title: 'Test',65message: 'Test message'66};67}6869async handle(): Promise<ClaudeToolPermissionResult> {70return { behavior: 'allow', updatedInput: {} };71}72}7374registerToolPermissionHandler([ClaudeToolNames.Grep], CustomHandler);7576const registry = getToolPermissionHandlerRegistry();77const lastRegistration = registry[registry.length - 1];78expect(lastRegistration.ctor).toBe(CustomHandler);79});80});8182describe('getToolPermissionHandlerRegistry', () => {83it('returns a readonly array', () => {84const registry = getToolPermissionHandlerRegistry();85expect(Array.isArray(registry)).toBe(true);86});8788it('returns consistent results on multiple calls', () => {89const registry1 = getToolPermissionHandlerRegistry();90const registry2 = getToolPermissionHandlerRegistry();91expect(registry1).toBe(registry2);92});9394it('contains registrations from imported handlers', () => {95// Handlers are imported at module level above96const registry = getToolPermissionHandlerRegistry();9798// Should have at least the Bash and ExitPlanMode handlers99const hasBashHandler = registry.some(r => r.toolNames.includes(ClaudeToolNames.Bash));100const hasExitPlanModeHandler = registry.some(r => r.toolNames.includes(ClaudeToolNames.ExitPlanMode));101102expect(hasBashHandler).toBe(true);103expect(hasExitPlanModeHandler).toBe(true);104});105});106107describe('registry behavior', () => {108it('allows multiple handlers for different tools', () => {109class Handler1 implements IClaudeToolPermissionHandler<ClaudeToolNames.WebFetch> {110readonly toolNames = [ClaudeToolNames.WebFetch] as const;111}112113class Handler2 implements IClaudeToolPermissionHandler<ClaudeToolNames.WebSearch> {114readonly toolNames = [ClaudeToolNames.WebSearch] as const;115}116117const startLength = getToolPermissionHandlerRegistry().length;118119registerToolPermissionHandler([ClaudeToolNames.WebFetch], Handler1);120registerToolPermissionHandler([ClaudeToolNames.WebSearch], Handler2);121122const registry = getToolPermissionHandlerRegistry();123expect(registry.length).toBe(startLength + 2);124});125126it('preserves registration order', () => {127class FirstHandler implements IClaudeToolPermissionHandler<ClaudeToolNames.Task> {128readonly toolNames = [ClaudeToolNames.Task] as const;129}130131class SecondHandler implements IClaudeToolPermissionHandler<ClaudeToolNames.TodoWrite> {132readonly toolNames = [ClaudeToolNames.TodoWrite] as const;133}134135const startLength = getToolPermissionHandlerRegistry().length;136137registerToolPermissionHandler([ClaudeToolNames.Task], FirstHandler);138registerToolPermissionHandler([ClaudeToolNames.TodoWrite], SecondHandler);139140const registry = getToolPermissionHandlerRegistry();141expect(registry[startLength].ctor).toBe(FirstHandler);142expect(registry[startLength + 1].ctor).toBe(SecondHandler);143});144});145});146147148