Skip to main content
Glama

CTS MCP Server

by EricA1019
integration.test.tsโ€ข15.5 kB
/** * Integration Tests for Audit Tool */ import { describe, it, expect, beforeEach, afterEach } from '@jest/globals'; import { auditTool, createAuditHandler, type AuditInput } from '../index.js'; import { mkdir, writeFile, rm } from 'fs/promises'; import { join } from 'path'; import { tmpdir } from 'os'; describe('Audit Tool - Integration Tests', () => { let testProjectPath: string; let handler: ReturnType<typeof createAuditHandler>; beforeEach(async () => { // Create temporary test project testProjectPath = join(tmpdir(), `audit-test-${Date.now()}`); await mkdir(testProjectPath, { recursive: true }); await mkdir(join(testProjectPath, 'scripts'), { recursive: true }); await mkdir(join(testProjectPath, 'scenes'), { recursive: true }); await mkdir(join(testProjectPath, 'test'), { recursive: true }); handler = createAuditHandler(); }); afterEach(async () => { // Cleanup await rm(testProjectPath, { recursive: true, force: true }); }); describe('Tool Definition', () => { it('should have correct tool name', () => { expect(auditTool.name).toBe('cts_audit'); }); it('should have description', () => { expect(auditTool.description).toBeDefined(); expect(auditTool.description.length).toBeGreaterThan(50); }); it('should require projectPath', () => { expect(auditTool.inputSchema.required).toContain('projectPath'); }); it('should have optional category filter', () => { const schema = auditTool.inputSchema.properties?.categories as any; expect(schema).toBeDefined(); expect(schema.type).toBe('array'); }); }); describe('Category Filtering', () => { it('should audit all categories by default', async () => { // Create a simple file with violations await writeFile( join(testProjectPath, 'scripts', 'test.gd'), 'func test():\n pass\n', 'utf-8' ); const input: AuditInput = { projectPath: testProjectPath, format: 'json', }; const result = await handler(input); expect(result.isError).toBeFalsy(); const text = result.content[0]?.text; expect(text).toBeDefined(); const data = JSON.parse(text as string); // Should check all categories expect(data.report.categoryScores).toHaveProperty('cts'); expect(data.report.categoryScores).toHaveProperty('code_quality'); expect(data.report.categoryScores).toHaveProperty('project_structure'); }); it('should audit only CTS category when specified', async () => { // Create a large file (CTS violation) const largeFile = 'func test():\n' + ' pass\n'.repeat(501); await writeFile( join(testProjectPath, 'scripts', 'large.gd'), largeFile, 'utf-8' ); const input: AuditInput = { projectPath: testProjectPath, categories: ['cts'], format: 'json', }; const result = await handler(input); const text = result.content[0]?.text as string; const data = JSON.parse(text); // Should have CTS violations expect(data.report.categoryScores.cts).toBeLessThan(100); }); it('should audit only code_quality category when specified', async () => { // Create file without type hints (code quality violation) await writeFile( join(testProjectPath, 'scripts', 'no_types.gd'), 'func calculate(x, y):\n return x + y\n', 'utf-8' ); const input: AuditInput = { projectPath: testProjectPath, categories: ['code_quality'], format: 'json', }; const result = await handler(input); const text = result.content[0]?.text as string; const data = JSON.parse(text); // Should check code quality expect(data.report.categoryScores).toHaveProperty('code_quality'); }); }); describe('Scoring Algorithm', () => { it('should return perfect score for compliant project', async () => { // Create a small, well-typed file await writeFile( join(testProjectPath, 'scripts', 'good.gd'), `extends Node signal my_signal func typed_function(x: int) -> int: assert(x > 0, "x must be positive") return x * 2 `, 'utf-8' ); const input: AuditInput = { projectPath: testProjectPath, format: 'json', }; const result = await handler(input); const text = result.content[0]?.text as string; const data = JSON.parse(text); // Should have high scores expect(data.report.overallScore).toBeGreaterThanOrEqual(80); expect(data.report.categoryScores.cts).toBeGreaterThanOrEqual(80); expect(data.report.categoryScores.code_quality).toBeGreaterThanOrEqual(80); }); it('should calculate weighted overall score', async () => { // Create file with violations const largeFile = 'func test():\n' + ' pass\n'.repeat(501); await writeFile( join(testProjectPath, 'scripts', 'violations.gd'), largeFile, 'utf-8' ); const input: AuditInput = { projectPath: testProjectPath, format: 'json', }; const result = await handler(input); const text = result.content[0]?.text as string; const data = JSON.parse(text); // Overall score should be weighted average of category scores // CTS: 40%, code_quality: 40%, project_structure: 20% const expectedScore = data.report.categoryScores.cts * 0.4 + data.report.categoryScores.code_quality * 0.4 + data.report.categoryScores.project_structure * 0.2; expect(data.report.overallScore).toBeCloseTo(expectedScore, 1); }); it('should penalize multiple violations', async () => { // Create multiple files with different violations await writeFile( join(testProjectPath, 'scripts', 'large.gd'), 'func test():\n' + ' pass\n'.repeat(501), 'utf-8' ); await writeFile( join(testProjectPath, 'scripts', 'no_types.gd'), 'func calculate(x, y):\n return x + y\n', 'utf-8' ); await writeFile( join(testProjectPath, 'scripts', 'complex.gd'), `func complex(): if true: if true: if true: if true: if true: if true: if true: if true: if true: if true: if true: pass `, 'utf-8' ); const input: AuditInput = { projectPath: testProjectPath, format: 'json', }; const result = await handler(input); const text = result.content[0]?.text as string; const data = JSON.parse(text); // Should have violations (score could still be high if many rules pass) expect(data.report.summary.totalViolations).toBeGreaterThan(0); expect(data.report.violations.length).toBeGreaterThan(0); }); }); describe('Recommendation Generation', () => { it('should generate actionable recommendations', async () => { // Create file with violations const largeFile = 'func test():\n' + ' pass\n'.repeat(501); await writeFile( join(testProjectPath, 'scripts', 'large.gd'), largeFile, 'utf-8' ); const input: AuditInput = { projectPath: testProjectPath, format: 'json', }; const result = await handler(input); const text = result.content[0]?.text as string; const data = JSON.parse(text); // Should have recommendations expect(data.report.recommendations.length).toBeGreaterThan(0); const rec = data.report.recommendations[0]; expect(rec).toHaveProperty('priority'); expect(rec).toHaveProperty('action'); expect(rec).toHaveProperty('estimatedEffort'); expect(rec).toHaveProperty('ruleId'); }); it('should prioritize recommendations by severity', async () => { // Create files with different severities await writeFile( join(testProjectPath, 'scripts', 'large.gd'), 'func test():\n' + ' pass\n'.repeat(501), 'utf-8' ); await writeFile( join(testProjectPath, 'scripts', 'complex.gd'), `func complex(): if true: if true: if true: if true: if true: if true: if true: if true: if true: if true: if true: pass `, 'utf-8' ); await writeFile( join(testProjectPath, 'scripts', 'bad_name.gd'), 'var myVariable = 0\n', 'utf-8' ); const input: AuditInput = { projectPath: testProjectPath, format: 'json', }; const result = await handler(input); const text = result.content[0]?.text as string; const data = JSON.parse(text); // High priority should come before low priority const priorities = data.report.recommendations.map((r: any) => r.priority); const highIndex = priorities.indexOf('high'); const lowIndex = priorities.indexOf('low'); if (highIndex !== -1 && lowIndex !== -1) { expect(highIndex).toBeLessThan(lowIndex); } }); it('should include affected files in recommendations', async () => { // Create file with type hint violations await writeFile( join(testProjectPath, 'scripts', 'no_types.gd'), 'func calculate(x, y):\n return x + y\n', 'utf-8' ); const input: AuditInput = { projectPath: testProjectPath, categories: ['code_quality'], format: 'json', }; const result = await handler(input); const text = result.content[0]?.text as string; const data = JSON.parse(text); const typeHintRec = data.report.recommendations.find( (r: any) => r.ruleId === 'type_hints' ); if (typeHintRec) { expect(typeHintRec.affectedFiles.length).toBeGreaterThan(0); } }); }); describe('Minimum Score Threshold', () => { it('should pass when score meets threshold', async () => { // Create good file await writeFile( join(testProjectPath, 'scripts', 'good.gd'), `extends Node func typed_function(x: int) -> int: return x * 2 `, 'utf-8' ); const input: AuditInput = { projectPath: testProjectPath, minScore: 70, format: 'json', }; const result = await handler(input); expect(result.isError).toBeFalsy(); }); it('should fail when score below threshold', async () => { // Create multiple files with severe violations to drop score below 95 // Large file (CTS violation) const largeFile = 'func test():\n' + ' pass\n'.repeat(501); await writeFile( join(testProjectPath, 'scripts', 'bad.gd'), largeFile, 'utf-8' ); // No type hints (code quality violation) await writeFile( join(testProjectPath, 'scripts', 'no_types.gd'), 'func calc(x, y):\n return x + y\n', 'utf-8' ); // Complex function (code quality violation) await writeFile( join(testProjectPath, 'scripts', 'complex.gd'), `func complex(): if true: if true: if true: if true: if true: if true: if true: if true: if true: if true: if true: pass `, 'utf-8' ); const input: AuditInput = { projectPath: testProjectPath, minScore: 100, // Set very high threshold to force failure format: 'json', }; const result = await handler(input); const text = result.content[0]?.text as string; const data = JSON.parse(text); // Check that it failed due to threshold expect(data.error).toBe('Audit failed'); expect(data.message).toContain('below threshold'); expect(result.isError).toBe(true); }); }); describe('Performance', () => { it('should complete audit in under 5 seconds', async () => { // Create multiple files for (let i = 0; i < 10; i++) { await writeFile( join(testProjectPath, 'scripts', `file${i}.gd`), `func test${i}():\n pass\n`, 'utf-8' ); } const startTime = performance.now(); const input: AuditInput = { projectPath: testProjectPath, format: 'json', }; const result = await handler(input); const endTime = performance.now(); const duration = endTime - startTime; expect(duration).toBeLessThan(5000); // 5 seconds const text = result.content[0]?.text as string; const data = JSON.parse(text); expect(data.performance.durationMs).toBeLessThan(5000); }); }); describe('Output Formats', () => { it('should return JSON format by default', async () => { await writeFile( join(testProjectPath, 'scripts', 'test.gd'), 'func test():\n pass\n', 'utf-8' ); const input: AuditInput = { projectPath: testProjectPath, }; const result = await handler(input); const text = result.content[0]?.text as string; expect(() => JSON.parse(text)).not.toThrow(); }); it('should return markdown format when requested', async () => { await writeFile( join(testProjectPath, 'scripts', 'test.gd'), 'func test():\n pass\n', 'utf-8' ); const input: AuditInput = { projectPath: testProjectPath, format: 'markdown', }; const result = await handler(input); const text = result.content[0]?.text as string; expect(text).toContain('# ๐Ÿ“Š CTS Audit Report'); expect(text).toContain('Overall Score:'); expect(text).toContain('Category Scores'); }); }); describe('Error Handling', () => { it('should handle invalid input gracefully', async () => { const result = await handler({ invalid: 'input' }); expect(result.isError).toBeTruthy(); const text = result.content[0]?.text as string; const data = JSON.parse(text); expect(data.error).toBe('Invalid input'); }); it('should handle missing project path', async () => { const result = await handler({}); expect(result.isError).toBeTruthy(); }); it('should handle non-existent directory', async () => { const input: AuditInput = { projectPath: '/nonexistent/path/12345', format: 'json', }; const result = await handler(input); expect(result.isError).toBeTruthy(); const text = result.content[0]?.text as string; const data = JSON.parse(text); expect(data.error).toBe('Audit failed'); }); }); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/EricA1019/CTS_MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server