Files
yamadashy--repomix/tests/mcp/tools/mcpToolRuntime.test.ts
T
wehub-resource-sync 719032b19f
Update Schema / Update configuration json schema (push) Has been cancelled
Memory Benchmark / Memory Test (Full Analysis) (push) Has been cancelled
CI Quality / Lint GitHub Actions (actionlint) (push) Has been cancelled
CI Quality / Lint GitHub Actions (zizmor) (push) Has been cancelled
CI Quality / Check typos (push) Has been cancelled
CI / Test coverage (push) Has been cancelled
CI / Test (22.x, windows-latest) (push) Has been cancelled
CI / Test (24.x, macos-latest) (push) Has been cancelled
CI / Test (24.x, ubuntu-latest) (push) Has been cancelled
CI / Test (24.x, windows-latest) (push) Has been cancelled
CI / Test (26.x, macos-latest) (push) Has been cancelled
CI / Test (26.x, ubuntu-latest) (push) Has been cancelled
CI / Test (26.x, windows-latest) (push) Has been cancelled
CI / Test with Bun (latest, macos-latest) (push) Has been cancelled
CI / Test with Bun (latest, ubuntu-latest) (push) Has been cancelled
CI / Test with Bun (latest, windows-latest) (push) Has been cancelled
CI / Build and run (22.x, macos-latest) (push) Has been cancelled
CI / Build and run (22.x, ubuntu-latest) (push) Has been cancelled
CI / Build and run (22.x, windows-latest) (push) Has been cancelled
autofix.ci / autofix (push) Has been cancelled
CI Browser Extension / Lint Browser Extension (push) Has been cancelled
CI Browser Extension / Test Browser Extension (push) Has been cancelled
Memory Benchmark / Memory Test (push) Has been cancelled
CI Website / Lint Website Client (push) Has been cancelled
CI Website / Lint Website Server (push) Has been cancelled
CI Website / Test Website Server (push) Has been cancelled
CI Website / Bundle Website Server (push) Has been cancelled
CI / Lint Biome (push) Has been cancelled
CI / Lint oxlint (push) Has been cancelled
CI / Lint TypeScript (push) Has been cancelled
CI / Lint Secretlint (push) Has been cancelled
CI / Test (22.x, macos-latest) (push) Has been cancelled
CI / Test (22.x, ubuntu-latest) (push) Has been cancelled
CI / Build and run (24.x, macos-latest) (push) Has been cancelled
CI / Build and run (24.x, ubuntu-latest) (push) Has been cancelled
CI / Build and run (24.x, windows-latest) (push) Has been cancelled
CI / Build and run (26.x, macos-latest) (push) Has been cancelled
CI / Build and run (26.x, ubuntu-latest) (push) Has been cancelled
CI / Build and run (26.x, windows-latest) (push) Has been cancelled
CI / Build and run with Bun (latest, macos-latest) (push) Has been cancelled
CI / Build and run with Bun (latest, ubuntu-latest) (push) Has been cancelled
CI / Build and run with Bun (latest, windows-latest) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Docker / build (linux/amd64, ubuntu-latest) (push) Has been cancelled
Docker / build (linux/arm/v7, ubuntu-24.04-arm) (push) Has been cancelled
Docker / build (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Docker / merge (push) Has been cancelled
Pack repository with Repomix / pack-repo (push) Has been cancelled
Performance Benchmark History / Benchmark (macos-latest) (push) Has been cancelled
Performance Benchmark History / Benchmark (ubuntu-latest) (push) Has been cancelled
Performance Benchmark History / Benchmark (windows-latest) (push) Has been cancelled
Performance Benchmark History / Store Results (push) Has been cancelled
Test Repomix Action / Test Node.js 22 (push) Has been cancelled
Test Repomix Action / Test Node.js 24 (push) Has been cancelled
Test Repomix Action / Test Node.js 26 (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:39:37 +08:00

342 lines
11 KiB
TypeScript

import crypto from 'node:crypto';
import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import { beforeEach, describe, expect, it, vi } from 'vitest';
// Type guard for structured content with result property
function hasResult(obj: unknown): obj is { result: string } {
return (
typeof obj === 'object' &&
obj !== null &&
'result' in obj &&
typeof (obj as Record<string, unknown>).result === 'string'
);
}
import {
buildMcpToolErrorResponse,
buildMcpToolSuccessResponse,
createToolWorkspace,
formatPackToolResponse,
generateOutputId,
getOutputFilePath,
registerOutputFile,
} from '../../../src/mcp/tools/mcpToolRuntime.js';
vi.mock('node:fs/promises');
vi.mock('node:path');
vi.mock('node:os');
vi.mock('node:crypto');
describe('mcpToolRuntime', () => {
beforeEach(() => {
vi.resetAllMocks();
});
describe('registerOutputFile and getOutputFilePath', () => {
it('should register and retrieve output file paths', () => {
const id = 'test-id';
const filePath = '/path/to/file.txt';
registerOutputFile(id, filePath);
const retrievedPath = getOutputFilePath(id);
expect(retrievedPath).toBe(filePath);
});
it('should return undefined for non-existent output ID', () => {
const retrievedPath = getOutputFilePath('non-existent-id');
expect(retrievedPath).toBeUndefined();
});
});
describe('createToolWorkspace', () => {
beforeEach(() => {
vi.mocked(os.tmpdir).mockReturnValue('/tmp');
vi.mocked(path.join).mockImplementation((...args) => args.join('/'));
vi.mocked(fs.mkdir).mockResolvedValue(undefined);
vi.mocked(fs.mkdtemp).mockResolvedValue('/tmp/repomix/mcp-outputs/temp-dir');
});
it('should create a temporary directory for tool operations', async () => {
const tempDir = await createToolWorkspace();
expect(os.tmpdir).toHaveBeenCalled();
// path.join is now invoked twice: once inside shared/tmpDir.getRepomixTmpDir
// to build the umbrella, then again here to append the mcp-outputs subdir.
expect(path.join).toHaveBeenCalledWith('/tmp', 'repomix');
expect(path.join).toHaveBeenCalledWith('/tmp/repomix', 'mcp-outputs');
expect(fs.mkdir).toHaveBeenCalledWith('/tmp/repomix/mcp-outputs', { recursive: true });
expect(fs.mkdtemp).toHaveBeenCalledWith('/tmp/repomix/mcp-outputs/');
expect(tempDir).toBe('/tmp/repomix/mcp-outputs/temp-dir');
});
it('should throw an error if directory creation fails', async () => {
const error = new Error('Failed to create directory');
vi.mocked(fs.mkdir).mockRejectedValue(error);
await expect(createToolWorkspace()).rejects.toThrow(
'Failed to create temporary directory: Failed to create directory',
);
});
});
describe('generateOutputId', () => {
it('should generate a unique output ID', () => {
vi.mocked(crypto.randomBytes).mockImplementation(() => ({
toString: () => 'abcdef1234567890',
}));
const outputId = generateOutputId();
expect(crypto.randomBytes).toHaveBeenCalledWith(8);
expect(outputId).toBe('abcdef1234567890');
});
});
describe('formatToolResponse', () => {
beforeEach(() => {
vi.mocked(crypto.randomBytes).mockImplementation(() => ({
toString: () => 'abcdef1234567890',
}));
vi.mocked(fs.readFile).mockResolvedValue('Line 1\nLine 2\nLine 3\nLine 4\nLine 5');
});
it('should format a tool response with directory context', async () => {
const context = { directory: '/path/to/dir' };
const metrics = {
totalFiles: 10,
totalCharacters: 1000,
totalTokens: 200,
totalLines: 0, // Will be calculated in formatToolResponse
fileCharCounts: {
'file1.js': 500,
'file2.js': 300,
'file3.js': 200,
},
fileTokenCounts: {
'file1.js': 100,
'file2.js': 60,
'file3.js': 40,
},
processedFiles: [],
safeFilePaths: [],
};
const outputFilePath = '/path/to/output.xml';
const response = await formatPackToolResponse(context, metrics, outputFilePath);
expect(response).toHaveProperty('content');
expect(response.content).toHaveLength(1);
expect(response.content[0].type).toBe('text');
// Check that the structured content contains the expected result JSON
const structuredContent = response.structuredContent;
expect(structuredContent).toHaveProperty('result');
if (!hasResult(structuredContent)) {
throw new Error('Expected structuredContent to have a result property of type string');
}
const resultJson = JSON.parse(structuredContent.result);
expect(resultJson.directory).toBe('/path/to/dir');
expect(resultJson.outputId).toBe('abcdef1234567890');
expect(resultJson.metrics.totalFiles).toBe(10);
expect(resultJson.metrics.totalLines).toBe(5);
expect(response).toHaveProperty('structuredContent');
expect(response.structuredContent).toHaveProperty('result');
expect(response.structuredContent).toHaveProperty('description');
expect(response.structuredContent).toHaveProperty('directoryStructure');
});
it('should format a tool response with repository context', async () => {
const context = { repository: 'user/repo' };
const metrics = {
totalFiles: 5,
totalCharacters: 500,
totalTokens: 100,
totalLines: 0, // Will be calculated in formatToolResponse
fileCharCounts: {
'file1.js': 300,
'file2.js': 200,
},
fileTokenCounts: {
'file1.js': 60,
'file2.js': 40,
},
processedFiles: [],
safeFilePaths: [],
};
const outputFilePath = '/path/to/output.xml';
const response = await formatPackToolResponse(context, metrics, outputFilePath);
// Check that the structured content contains the expected result JSON
const structuredContent = response.structuredContent;
expect(structuredContent).toHaveProperty('result');
if (!hasResult(structuredContent)) {
throw new Error('Expected structuredContent to have a result property of type string');
}
const resultJson = JSON.parse(structuredContent.result);
expect(resultJson.repository).toBe('user/repo');
expect(resultJson.directory).toBeUndefined();
expect(resultJson.metrics.totalLines).toBe(5);
});
it('should limit the number of top files based on the parameter', async () => {
const context = {};
const metrics = {
totalFiles: 10,
totalCharacters: 1000,
totalTokens: 200,
totalLines: 0, // Will be calculated in formatToolResponse
fileCharCounts: {
'file1.js': 500,
'file2.js': 300,
'file3.js': 200,
'file4.js': 100,
'file5.js': 50,
'file6.js': 25,
},
fileTokenCounts: {
'file1.js': 100,
'file2.js': 60,
'file3.js': 40,
'file4.js': 20,
'file5.js': 10,
'file6.js': 5,
},
processedFiles: [],
safeFilePaths: [],
};
const outputFilePath = '/path/to/output.xml';
const topFilesLen = 3;
const response = await formatPackToolResponse(context, metrics, outputFilePath, topFilesLen);
// Check that the structured content contains the expected result JSON
const structuredContent = response.structuredContent;
expect(structuredContent).toHaveProperty('result');
if (!hasResult(structuredContent)) {
throw new Error('Expected structuredContent to have a result property of type string');
}
const result = JSON.parse(structuredContent.result);
expect(result.metrics.topFiles).toHaveLength(3);
expect(result.metrics.topFiles[0].path).toBe('file1.js');
expect(result.metrics.topFiles[1].path).toBe('file2.js');
expect(result.metrics.topFiles[2].path).toBe('file3.js');
expect(result.metrics.totalLines).toBe(5);
});
});
describe('buildMcpToolSuccessResponse', () => {
it('should create a successful response with structured content', () => {
const structuredContent = { description: 'Operation completed successfully' };
const response = buildMcpToolSuccessResponse(structuredContent);
expect(response).toEqual({
content: [
{
type: 'text',
text: JSON.stringify(structuredContent, null, 2),
},
],
structuredContent: structuredContent,
});
expect(response.isError).toBeUndefined();
});
it('should create a successful response with complex structured content', () => {
const structuredContent = {
description: 'Operation completed successfully',
results: ['First result', 'Second result', 'Third result'],
count: 3,
};
const response = buildMcpToolSuccessResponse(structuredContent);
expect(response).toEqual({
content: [
{
type: 'text',
text: JSON.stringify(structuredContent, null, 2),
},
],
structuredContent: structuredContent,
});
expect(response.isError).toBeUndefined();
});
it('should create a successful response with undefined structured content', () => {
const structuredContent = undefined;
const response = buildMcpToolSuccessResponse(structuredContent);
expect(response).toEqual({
content: [
{
type: 'text',
text: 'null',
},
],
structuredContent: structuredContent,
});
expect(response.isError).toBeUndefined();
});
});
describe('buildMcpToolErrorResponse', () => {
it('should create an error response with structured content', () => {
const errorContent = { message: 'Something went wrong' };
const response = buildMcpToolErrorResponse(errorContent);
expect(response).toEqual({
isError: true,
content: [
{
type: 'text',
text: JSON.stringify(errorContent, null, 2),
},
],
});
});
it('should create an error response with complex structured content', () => {
const errorContent = {
message: 'Multiple errors occurred',
errors: ['Error 1', 'Error 2', 'Error 3'],
code: 'MULTIPLE_ERRORS',
};
const response = buildMcpToolErrorResponse(errorContent);
expect(response).toEqual({
isError: true,
content: [
{
type: 'text',
text: JSON.stringify(errorContent, null, 2),
},
],
});
});
it('should create an error response with undefined structured content', () => {
const errorContent = undefined;
const response = buildMcpToolErrorResponse(errorContent);
expect(response).toEqual({
isError: true,
content: [
{
type: 'text',
text: 'null',
},
],
structuredContent: errorContent,
});
});
});
});