mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-07 08:26:11 +00:00
Wire extractors into the sync pipeline with service boundary detection. GroupService provides high-level API for all group operations. - Sync pipeline: orchestrates extraction (HTTP, gRPC, topics) with service boundary assignment and exact matching - GroupService: groupList, groupSync, groupContracts, groupQuery, groupStatus (groupImpact deferred to cross-repo follow-up PR) - CLI: group create/add/remove/list/sync/contracts/query/status - MCP tools: group_list, group_sync, group_contracts, group_query, group_status - Monorepo fixture: 3 services (auth/orders/gateway) connected via gRPC + Kafka + HTTP — all intra-repo cross-links discovered - Documentation: CLI commands and MCP tools added to both READMEs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
156 lines
6 KiB
TypeScript
156 lines
6 KiB
TypeScript
/**
|
|
* Unit Tests: MCP Tool Definitions
|
|
*
|
|
* Tests: GITNEXUS_TOOLS from tools.ts
|
|
* - All 16 tools are defined (per-repo + group_*)
|
|
* - Each tool has valid name, description, inputSchema
|
|
* - Required fields are correct
|
|
* - Optional repo parameter is present on tools that need it
|
|
*/
|
|
import { describe, it, expect } from 'vitest';
|
|
import { GITNEXUS_TOOLS } from '../../src/mcp/tools.js';
|
|
|
|
const GROUP_TOOLS = new Set([
|
|
'group_list',
|
|
'group_sync',
|
|
'group_contracts',
|
|
'group_query',
|
|
'group_status',
|
|
]);
|
|
|
|
describe('GITNEXUS_TOOLS', () => {
|
|
it('exports all tools (7 base + 3 route/tool/shape + 1 api_impact + 5 group)', () => {
|
|
expect(GITNEXUS_TOOLS).toHaveLength(16);
|
|
});
|
|
|
|
it('contains all expected tool names', () => {
|
|
const names = GITNEXUS_TOOLS.map((t) => t.name);
|
|
expect(names).toEqual(
|
|
expect.arrayContaining([
|
|
'list_repos',
|
|
'query',
|
|
'cypher',
|
|
'context',
|
|
'detect_changes',
|
|
'rename',
|
|
'impact',
|
|
'api_impact',
|
|
]),
|
|
);
|
|
});
|
|
|
|
it('each tool has name, description, and inputSchema', () => {
|
|
for (const tool of GITNEXUS_TOOLS) {
|
|
expect(tool.name).toBeTruthy();
|
|
expect(typeof tool.name).toBe('string');
|
|
expect(tool.description).toBeTruthy();
|
|
expect(typeof tool.description).toBe('string');
|
|
expect(tool.inputSchema).toBeDefined();
|
|
expect(tool.inputSchema.type).toBe('object');
|
|
expect(tool.inputSchema.properties).toBeDefined();
|
|
expect(Array.isArray(tool.inputSchema.required)).toBe(true);
|
|
}
|
|
});
|
|
|
|
it('query tool requires "query" parameter', () => {
|
|
const queryTool = GITNEXUS_TOOLS.find((t) => t.name === 'query')!;
|
|
expect(queryTool.inputSchema.required).toContain('query');
|
|
expect(queryTool.inputSchema.properties.query).toBeDefined();
|
|
expect(queryTool.inputSchema.properties.query.type).toBe('string');
|
|
});
|
|
|
|
it('cypher tool requires "query" parameter', () => {
|
|
const cypherTool = GITNEXUS_TOOLS.find((t) => t.name === 'cypher')!;
|
|
expect(cypherTool.inputSchema.required).toContain('query');
|
|
});
|
|
|
|
it('context tool has no required parameters', () => {
|
|
const contextTool = GITNEXUS_TOOLS.find((t) => t.name === 'context')!;
|
|
expect(contextTool.inputSchema.required).toEqual([]);
|
|
});
|
|
|
|
it('impact tool requires target and direction', () => {
|
|
const impactTool = GITNEXUS_TOOLS.find((t) => t.name === 'impact')!;
|
|
expect(impactTool.inputSchema.required).toContain('target');
|
|
expect(impactTool.inputSchema.required).toContain('direction');
|
|
});
|
|
|
|
it('rename tool requires new_name', () => {
|
|
const renameTool = GITNEXUS_TOOLS.find((t) => t.name === 'rename')!;
|
|
expect(renameTool.inputSchema.required).toContain('new_name');
|
|
});
|
|
|
|
it('detect_changes tool has no required parameters', () => {
|
|
const detectTool = GITNEXUS_TOOLS.find((t) => t.name === 'detect_changes')!;
|
|
expect(detectTool.inputSchema.required).toEqual([]);
|
|
});
|
|
|
|
it('list_repos tool has no parameters', () => {
|
|
const listTool = GITNEXUS_TOOLS.find((t) => t.name === 'list_repos')!;
|
|
expect(Object.keys(listTool.inputSchema.properties)).toHaveLength(0);
|
|
expect(listTool.inputSchema.required).toEqual([]);
|
|
});
|
|
|
|
it('per-repo tools have optional repo parameter for backend selection', () => {
|
|
for (const tool of GITNEXUS_TOOLS) {
|
|
if (tool.name === 'list_repos') continue;
|
|
if (GROUP_TOOLS.has(tool.name)) continue;
|
|
expect(tool.inputSchema.properties.repo).toBeDefined();
|
|
expect(tool.inputSchema.properties.repo.type).toBe('string');
|
|
expect(tool.inputSchema.required).not.toContain('repo');
|
|
}
|
|
});
|
|
|
|
it('group_contracts has optional repo filter', () => {
|
|
const groupContracts = GITNEXUS_TOOLS.find((t) => t.name === 'group_contracts')!;
|
|
expect(groupContracts.inputSchema.properties).toHaveProperty('repo');
|
|
expect(groupContracts.inputSchema.required).not.toContain('repo');
|
|
});
|
|
|
|
it('group tools without backend repo param omit repo property', () => {
|
|
for (const name of ['group_list', 'group_status', 'group_sync', 'group_query'] as const) {
|
|
const tool = GITNEXUS_TOOLS.find((t) => t.name === name)!;
|
|
expect(tool.inputSchema.properties).not.toHaveProperty('repo');
|
|
}
|
|
});
|
|
|
|
it('group_query requires name and query', () => {
|
|
const groupQuery = GITNEXUS_TOOLS.find((t) => t.name === 'group_query')!;
|
|
expect(groupQuery.inputSchema.required).toContain('name');
|
|
expect(groupQuery.inputSchema.required).toContain('query');
|
|
});
|
|
|
|
it('detect_changes scope has correct enum values', () => {
|
|
const detectTool = GITNEXUS_TOOLS.find((t) => t.name === 'detect_changes')!;
|
|
const scopeProp = detectTool.inputSchema.properties.scope;
|
|
expect(scopeProp.enum).toEqual(['unstaged', 'staged', 'all', 'compare']);
|
|
});
|
|
|
|
it('api_impact tool has no required parameters', () => {
|
|
const apiImpactTool = GITNEXUS_TOOLS.find((t) => t.name === 'api_impact')!;
|
|
expect(apiImpactTool).toBeDefined();
|
|
expect(apiImpactTool.inputSchema.required).toEqual([]);
|
|
expect(apiImpactTool.inputSchema.properties.route).toBeDefined();
|
|
expect(apiImpactTool.inputSchema.properties.file).toBeDefined();
|
|
expect(apiImpactTool.inputSchema.properties.repo).toBeDefined();
|
|
});
|
|
|
|
it('impact relationTypes is array of strings', () => {
|
|
const impactTool = GITNEXUS_TOOLS.find((t) => t.name === 'impact')!;
|
|
const relProp = impactTool.inputSchema.properties.relationTypes;
|
|
expect(relProp.type).toBe('array');
|
|
expect(relProp.items).toEqual({ type: 'string' });
|
|
});
|
|
|
|
it('route_map description defers to api_impact for pre-change analysis', () => {
|
|
const routeMapTool = GITNEXUS_TOOLS.find((t) => t.name === 'route_map')!;
|
|
expect(routeMapTool.description).toContain('api_impact');
|
|
expect(routeMapTool.description).toContain('pre-change analysis');
|
|
});
|
|
|
|
it('shape_check description defers to api_impact for pre-change analysis', () => {
|
|
const shapeCheckTool = GITNEXUS_TOOLS.find((t) => t.name === 'shape_check')!;
|
|
expect(shapeCheckTool.description).toContain('api_impact');
|
|
expect(shapeCheckTool.description).toContain('pre-change analysis');
|
|
});
|
|
});
|