Files
ai-inference/__tests__/mcp.test.ts
google-labs-jules[bot] c6c19e0fb7 🔒 [security fix] Fix sensitive data exposure in logs
- Change core.info to core.debug for model responses in src/inference.ts
- Change core.info to core.debug for tool execution details in src/mcp.ts
- Change core.info to core.debug for custom header logging in src/helpers.ts
- Remove sensitive response previews from error messages in src/inference.ts
- Update tests to reflect changes from core.info to core.debug
2026-02-24 17:42:20 +00:00

296 lines
9.1 KiB
TypeScript

import {vi, type MockedFunction, describe, it, expect, beforeEach} from 'vitest'
import * as core from '../__fixtures__/core.js'
// Mock MCP SDK
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const mockConnect = vi.fn() as MockedFunction<any>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const mockListTools = vi.fn() as MockedFunction<any>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const mockCallTool = vi.fn() as MockedFunction<any>
const mockClient = {
connect: mockConnect,
listTools: mockListTools,
callTool: mockCallTool,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any
vi.mock('@modelcontextprotocol/sdk/client/index.js', () => ({
Client: vi.fn(() => mockClient),
}))
vi.mock('@modelcontextprotocol/sdk/client/streamableHttp.js', () => ({
StreamableHTTPClientTransport: vi.fn(),
}))
vi.mock('@actions/core', () => core)
// Import the module being tested
const {connectToGitHubMCP, executeToolCall, executeToolCalls} = await import('../src/mcp.js')
describe('mcp.ts', () => {
beforeEach(() => {
vi.clearAllMocks()
})
describe('connectToGitHubMCP', () => {
it('successfully connects to MCP server and retrieves tools', async () => {
const token = 'test-token'
const mockTools = [
{
name: 'test-tool-1',
description: 'Test tool 1',
inputSchema: {type: 'object', properties: {}},
},
{
name: 'test-tool-2',
description: 'Test tool 2',
inputSchema: {
type: 'object',
properties: {param: {type: 'string'}},
},
},
]
mockConnect.mockResolvedValue(undefined)
mockListTools.mockResolvedValue({tools: mockTools})
const result = await connectToGitHubMCP(token)
expect(result).not.toBeNull()
expect(result?.client).toBe(mockClient)
expect(result?.tools).toHaveLength(2)
expect(result?.tools[0]).toEqual({
type: 'function',
function: {
name: 'test-tool-1',
description: 'Test tool 1',
parameters: {type: 'object', properties: {}},
},
})
expect(core.info).toHaveBeenCalledWith('Connecting to GitHub MCP server...')
expect(core.info).toHaveBeenCalledWith('Successfully connected to GitHub MCP server')
expect(core.info).toHaveBeenCalledWith('Retrieved 2 tools from GitHub MCP server')
expect(core.info).toHaveBeenCalledWith('Mapped 2 GitHub MCP tools for Azure AI Inference')
})
it('returns null when connection fails', async () => {
const token = 'test-token'
const connectionError = new Error('Connection failed')
mockConnect.mockRejectedValue(connectionError)
const result = await connectToGitHubMCP(token)
expect(result).toBeNull()
expect(core.warning).toHaveBeenCalledWith('Failed to connect to GitHub MCP server: Error: Connection failed')
})
it('handles empty tools list', async () => {
const token = 'test-token'
mockConnect.mockResolvedValue(undefined)
mockListTools.mockResolvedValue({tools: []})
const result = await connectToGitHubMCP(token)
expect(result).not.toBeNull()
expect(result?.tools).toHaveLength(0)
expect(core.info).toHaveBeenCalledWith('Retrieved 0 tools from GitHub MCP server')
expect(core.info).toHaveBeenCalledWith('Mapped 0 GitHub MCP tools for Azure AI Inference')
})
it('handles undefined tools list', async () => {
const token = 'test-token'
mockConnect.mockResolvedValue(undefined)
mockListTools.mockResolvedValue({})
const result = await connectToGitHubMCP(token)
expect(result).not.toBeNull()
expect(result?.tools).toHaveLength(0)
expect(core.info).toHaveBeenCalledWith('Retrieved 0 tools from GitHub MCP server')
})
it('uses default toolsets when toolsets parameter is not provided', async () => {
const token = 'test-token'
mockConnect.mockResolvedValue(undefined)
mockListTools.mockResolvedValue({tools: []})
await connectToGitHubMCP(token)
expect(core.info).toHaveBeenCalledWith('Using default GitHub MCP toolsets')
})
it('uses custom toolsets when toolsets parameter is provided', async () => {
const token = 'test-token'
const toolsets = 'repos,issues,pull_requests,actions'
mockConnect.mockResolvedValue(undefined)
mockListTools.mockResolvedValue({tools: []})
await connectToGitHubMCP(token, toolsets)
expect(core.info).toHaveBeenCalledWith('Using GitHub MCP toolsets: repos,issues,pull_requests,actions')
})
it('ignores empty toolsets parameter', async () => {
const token = 'test-token'
mockConnect.mockResolvedValue(undefined)
mockListTools.mockResolvedValue({tools: []})
await connectToGitHubMCP(token, ' ')
expect(core.info).toHaveBeenCalledWith('Using default GitHub MCP toolsets')
})
})
describe('executeToolCall', () => {
it('successfully executes a tool call', async () => {
const toolCall = {
id: 'call-123',
type: 'function',
function: {
name: 'test-tool',
arguments: '{"param": "value"}',
},
}
const toolResult = {
content: [{type: 'text', text: 'Tool execution result'}],
}
mockCallTool.mockResolvedValue(toolResult)
const result = await executeToolCall(mockClient, toolCall)
expect(mockCallTool).toHaveBeenCalledWith({
name: 'test-tool',
arguments: {param: 'value'},
})
expect(result).toEqual({
tool_call_id: 'call-123',
role: 'tool',
name: 'test-tool',
content: JSON.stringify(toolResult.content),
})
expect(core.debug).toHaveBeenCalledWith('Executing GitHub MCP tool: test-tool with args: {"param": "value"}')
expect(core.debug).toHaveBeenCalledWith('GitHub MCP tool test-tool executed successfully')
})
it('handles tool execution errors gracefully', async () => {
const toolCall = {
id: 'call-456',
type: 'function',
function: {
name: 'failing-tool',
arguments: '{"param": "value"}',
},
}
const toolError = new Error('Tool execution failed')
mockCallTool.mockRejectedValue(toolError)
const result = await executeToolCall(mockClient, toolCall)
expect(result).toEqual({
tool_call_id: 'call-456',
role: 'tool',
name: 'failing-tool',
content: 'Error: Error: Tool execution failed',
})
expect(core.warning).toHaveBeenCalledWith(
'Failed to execute GitHub MCP tool failing-tool: Error: Tool execution failed',
)
})
it('handles invalid JSON arguments', async () => {
const toolCall = {
id: 'call-789',
type: 'function',
function: {
name: 'test-tool',
arguments: 'invalid-json',
},
}
const result = await executeToolCall(mockClient, toolCall)
expect(result.tool_call_id).toBe('call-789')
expect(result.role).toBe('tool')
expect(result.name).toBe('test-tool')
expect(result.content).toContain('Error:')
expect(core.warning).toHaveBeenCalledWith(expect.stringContaining('Failed to execute GitHub MCP tool test-tool:'))
})
})
describe('executeToolCalls', () => {
it('executes multiple tool calls successfully', async () => {
const toolCalls = [
{
id: 'call-1',
type: 'function',
function: {name: 'tool-1', arguments: '{}'},
},
{
id: 'call-2',
type: 'function',
function: {name: 'tool-2', arguments: '{"param": "value"}'},
},
]
mockCallTool
.mockResolvedValueOnce({
content: [{type: 'text', text: 'Result 1'}],
})
.mockResolvedValueOnce({
content: [{type: 'text', text: 'Result 2'}],
})
const results = await executeToolCalls(mockClient, toolCalls)
expect(results).toHaveLength(2)
expect(results[0].tool_call_id).toBe('call-1')
expect(results[1].tool_call_id).toBe('call-2')
expect(mockCallTool).toHaveBeenCalledTimes(2)
})
it('handles empty tool calls array', async () => {
const results = await executeToolCalls(mockClient, [])
expect(results).toHaveLength(0)
expect(mockCallTool).not.toHaveBeenCalled()
})
it('continues execution even if some tools fail', async () => {
const toolCalls = [
{
id: 'call-1',
type: 'function',
function: {name: 'tool-1', arguments: '{}'},
},
{
id: 'call-2',
type: 'function',
function: {name: 'tool-2', arguments: '{}'},
},
]
mockCallTool
.mockResolvedValueOnce({
content: [{type: 'text', text: 'Result 1'}],
})
.mockRejectedValueOnce(new Error('Tool 2 failed'))
const results = await executeToolCalls(mockClient, toolCalls)
expect(results).toHaveLength(2)
expect(results[0].content).toContain('Result 1')
expect(results[1].content).toContain('Error:')
})
})
})