Skip to main content
Glama

CTS MCP Server

by EricA1019
checkers.test.tsโ€ข14 kB
/** * Audit Compliance Checkers Tests * * Tests for rule validation and metrics collection. */ import { describe, it, expect, beforeEach, afterEach } from '@jest/globals'; import { ALL_RULES, getRulesByCategory, type AuditContext, } from '../checkers'; import { collectMetrics, getComplexityBreakdown, getTopComplexFiles, getTopLargestFiles } from '../metrics'; import { mkdir, writeFile, rm } from 'fs/promises'; import { join } from 'path'; import { existsSync } from 'fs'; const TEST_PROJECT = '/tmp/audit-test-project'; describe('Audit Compliance Checkers', () => { beforeEach(async () => { if (existsSync(TEST_PROJECT)) { await rm(TEST_PROJECT, { recursive: true, force: true }); } await mkdir(TEST_PROJECT, { recursive: true }); }); afterEach(async () => { if (existsSync(TEST_PROJECT)) { await rm(TEST_PROJECT, { recursive: true, force: true }); } }); describe('Rule Registry', () => { it('should have 10 compliance rules', () => { expect(ALL_RULES.length).toBe(10); }); it('should have 4 CTS rules', () => { const ctsRules = getRulesByCategory('cts'); expect(ctsRules.length).toBe(4); }); it('should have 4 code quality rules', () => { const qualityRules = getRulesByCategory('code_quality'); expect(qualityRules.length).toBe(4); }); it('should have 2 project structure rules', () => { const structureRules = getRulesByCategory('project_structure'); expect(structureRules.length).toBe(2); }); it('should have unique rule IDs', () => { const ids = ALL_RULES.map((r) => r.id); const uniqueIds = new Set(ids); expect(uniqueIds.size).toBe(ids.length); }); }); describe('CTS File Size Rule', () => { it('should pass for files under 500 lines', async () => { const file = join(TEST_PROJECT, 'small.gd'); await writeFile(file, 'extends Node\n'.repeat(100)); const ctx: AuditContext = { projectPath: TEST_PROJECT, files: ['small.gd'], }; const rule = ALL_RULES.find((r) => r.id === 'cts_file_size')!; const result = await rule.check(ctx); expect(result.passed).toBe(true); expect(result.violations.length).toBe(0); expect(result.score).toBe(100); }); it('should fail for files over 500 lines', async () => { const file = join(TEST_PROJECT, 'large.gd'); await writeFile(file, 'extends Node\n'.repeat(600)); const ctx: AuditContext = { projectPath: TEST_PROJECT, files: ['large.gd'], }; const rule = ALL_RULES.find((r) => r.id === 'cts_file_size')!; const result = await rule.check(ctx); expect(result.passed).toBe(false); expect(result.violations.length).toBe(1); expect(result.violations[0].severity).toBe('error'); expect(result.score).toBeLessThan(100); }); }); describe('CTS Signal-First Rule', () => { it('should pass for classes with signals', async () => { const file = join(TEST_PROJECT, 'signaled.gd'); await writeFile( file, `extends Node signal health_changed(value) signal died() func take_damage(amount): health -= amount health_changed.emit(health) if health <= 0: died.emit() ` ); const ctx: AuditContext = { projectPath: TEST_PROJECT, files: ['signaled.gd'], }; const rule = ALL_RULES.find((r) => r.id === 'cts_signal_first')!; const result = await rule.check(ctx); expect(result.passed).toBe(true); }); it('should warn for classes without signals', async () => { const file = join(TEST_PROJECT, 'no_signals.gd'); await writeFile( file, `extends Node func func1(): pass func func2(): pass func func3(): pass func func4(): pass ` ); const ctx: AuditContext = { projectPath: TEST_PROJECT, files: ['no_signals.gd'], }; const rule = ALL_RULES.find((r) => r.id === 'cts_signal_first')!; const result = await rule.check(ctx); expect(result.passed).toBe(false); expect(result.violations.length).toBeGreaterThan(0); }); }); describe('Type Hints Rule', () => { it('should pass for functions with type hints', async () => { const file = join(TEST_PROJECT, 'typed.gd'); await writeFile( file, `extends Node func calculate(x: int, y: int) -> int: return x + y ` ); const ctx: AuditContext = { projectPath: TEST_PROJECT, files: ['typed.gd'], }; const rule = ALL_RULES.find((r) => r.id === 'type_hints')!; const result = await rule.check(ctx); expect(result.passed).toBe(true); }); it('should warn for functions without type hints', async () => { const file = join(TEST_PROJECT, 'untyped.gd'); await writeFile( file, `extends Node func calculate(x, y): return x + y ` ); const ctx: AuditContext = { projectPath: TEST_PROJECT, files: ['untyped.gd'], }; const rule = ALL_RULES.find((r) => r.id === 'type_hints')!; const result = await rule.check(ctx); expect(result.passed).toBe(false); expect(result.violations.length).toBeGreaterThan(0); expect(result.violations[0].message).toContain('calculate'); }); }); describe('Error Handling Rule', () => { it('should pass for files with error checking', async () => { const file = join(TEST_PROJECT, 'checked.gd'); await writeFile( file, `extends Node func process_data(data): if not data: return null assert(data.size() > 0) return data[0] ` ); const ctx: AuditContext = { projectPath: TEST_PROJECT, files: ['checked.gd'], }; const rule = ALL_RULES.find((r) => r.id === 'error_handling')!; const result = await rule.check(ctx); expect(result.passed).toBe(true); }); }); describe('Complexity Rule', () => { it('should pass for simple functions', async () => { const file = join(TEST_PROJECT, 'simple.gd'); await writeFile( file, `extends Node func simple_func(x): if x > 0: return x * 2 return 0 ` ); const ctx: AuditContext = { projectPath: TEST_PROJECT, files: ['simple.gd'], }; const rule = ALL_RULES.find((r) => r.id === 'complexity')!; const result = await rule.check(ctx); expect(result.passed).toBe(true); }); it('should warn for complex functions', async () => { const file = join(TEST_PROJECT, 'complex.gd'); const complexCode = `extends Node func complex_func(data): if data.size() > 0: for item in data: if item.valid: while item.processing: if item.status == "ready" and item.ready: for child in item.children: if child.active and child.ready or child.forced: if child.enabled: match child.type: "A": if true: pass "B": if false: pass `; await writeFile(file, complexCode); const ctx: AuditContext = { projectPath: TEST_PROJECT, files: ['complex.gd'], }; const rule = ALL_RULES.find((r) => r.id === 'complexity')!; const result = await rule.check(ctx); expect(result.passed).toBe(false); expect(result.violations.length).toBeGreaterThan(0); expect(result.violations[0].message).toContain('complexity'); }); }); describe('Naming Conventions Rule', () => { it('should pass for snake_case names', async () => { const file = join(TEST_PROJECT, 'snake_case.gd'); await writeFile( file, `extends Node var player_health = 100 func update_health(new_value): player_health = new_value ` ); const ctx: AuditContext = { projectPath: TEST_PROJECT, files: ['snake_case.gd'], }; const rule = ALL_RULES.find((r) => r.id === 'naming_conventions')!; const result = await rule.check(ctx); expect(result.passed).toBe(true); }); it('should warn for camelCase names', async () => { const file = join(TEST_PROJECT, 'camelCase.gd'); await writeFile( file, `extends Node var playerHealth = 100 func updateHealth(newValue): playerHealth = newValue ` ); const ctx: AuditContext = { projectPath: TEST_PROJECT, files: ['camelCase.gd'], }; const rule = ALL_RULES.find((r) => r.id === 'naming_conventions')!; const result = await rule.check(ctx); expect(result.passed).toBe(false); expect(result.violations.length).toBeGreaterThan(0); }); }); describe('Addon Integration Rule', () => { it('should pass for properly configured addons', async () => { await mkdir(join(TEST_PROJECT, 'addons', 'my_addon'), { recursive: true, }); await writeFile(join(TEST_PROJECT, 'addons/my_addon/plugin.cfg'), ''); const ctx: AuditContext = { projectPath: TEST_PROJECT, files: ['addons/my_addon/plugin.cfg', 'addons/my_addon/plugin.gd'], }; const rule = ALL_RULES.find((r) => r.id === 'addon_integration')!; const result = await rule.check(ctx); expect(result.passed).toBe(true); }); it('should warn for addons without plugin.cfg', async () => { await mkdir(join(TEST_PROJECT, 'addons', 'bad_addon'), { recursive: true, }); const ctx: AuditContext = { projectPath: TEST_PROJECT, files: ['addons/bad_addon/plugin.gd'], }; const rule = ALL_RULES.find((r) => r.id === 'addon_integration')!; const result = await rule.check(ctx); expect(result.passed).toBe(false); expect(result.violations.length).toBeGreaterThan(0); }); }); describe('Directory Organization Rule', () => { it('should pass for projects with standard structure', async () => { const ctx: AuditContext = { projectPath: TEST_PROJECT, files: ['scripts/player.gd', 'scenes/main.tscn', 'assets/icon.png'], }; const rule = ALL_RULES.find((r) => r.id === 'directory_organization')!; const result = await rule.check(ctx); expect(result.passed).toBe(true); }); }); describe('Metrics Collection', () => { it('should collect LOC metrics', async () => { const file1 = join(TEST_PROJECT, 'file1.gd'); const file2 = join(TEST_PROJECT, 'file2.gd'); await writeFile(file1, 'extends Node\n'.repeat(100)); await writeFile(file2, 'extends Node\n'.repeat(200)); const metrics = await collectMetrics(TEST_PROJECT, [ 'file1.gd', 'file2.gd', ]); expect(metrics.loc.total).toBeGreaterThanOrEqual(300); expect(metrics.loc.byFile['file1.gd']).toBeGreaterThanOrEqual(100); expect(metrics.loc.byFile['file2.gd']).toBeGreaterThanOrEqual(200); expect(metrics.loc.average).toBeGreaterThanOrEqual(150); }); it('should calculate complexity', async () => { const file = join(TEST_PROJECT, 'complex.gd'); await writeFile( file, `extends Node func test(): if true: for i in range(10): while i > 0: pass ` ); const metrics = await collectMetrics(TEST_PROJECT, ['complex.gd']); expect(metrics.complexity.total).toBeGreaterThan(0); expect(metrics.complexity.byFile['complex.gd']).toBeGreaterThan(0); }); it('should count file types', async () => { const metrics = await collectMetrics(TEST_PROJECT, [ 'file1.gd', 'file2.gd', 'scene.tscn', 'resource.tres', ]); expect(metrics.files.total).toBe(4); expect(metrics.files.gdscript).toBe(2); expect(metrics.files.scenes).toBe(1); expect(metrics.files.resources).toBe(1); }); it('should get complexity breakdown', async () => { const metrics = { loc: { total: 0, byFile: {}, average: 0 }, complexity: { total: 0, byFile: { 'low.gd': 5, 'medium.gd': 15, 'high.gd': 25, }, average: 15, max: 25, maxFile: 'high.gd', }, files: { total: 3, gdscript: 3, scenes: 0, resources: 0 }, testCoverage: 0, }; const breakdown = getComplexityBreakdown(metrics); expect(breakdown.low).toBe(1); expect(breakdown.medium).toBe(1); expect(breakdown.high).toBe(1); }); it('should get top complex files', async () => { const metrics = { loc: { total: 0, byFile: {}, average: 0 }, complexity: { total: 0, byFile: { 'a.gd': 10, 'b.gd': 30, 'c.gd': 20, }, average: 20, max: 30, maxFile: 'b.gd', }, files: { total: 3, gdscript: 3, scenes: 0, resources: 0 }, testCoverage: 0, }; const top = getTopComplexFiles(metrics, 2); expect(top.length).toBe(2); expect(top[0].file).toBe('b.gd'); expect(top[0].complexity).toBe(30); expect(top[1].file).toBe('c.gd'); expect(top[1].complexity).toBe(20); }); it('should get top largest files', async () => { const metrics = { loc: { total: 0, byFile: { 'a.gd': 100, 'b.gd': 300, 'c.gd': 200, }, average: 200, }, complexity: { total: 0, byFile: {}, average: 0, max: 0, maxFile: '' }, files: { total: 3, gdscript: 3, scenes: 0, resources: 0 }, testCoverage: 0, }; const top = getTopLargestFiles(metrics, 2); expect(top.length).toBe(2); expect(top[0].file).toBe('b.gd'); expect(top[0].loc).toBe(300); }); }); });

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