Skip to main content
Glama

mcp-server-circleci

Official
handler.test.ts12.6 kB
import { describe, it, expect, vi, beforeEach } from 'vitest'; import { FilterBy } from '../shared/constants.js'; import { analyzeDiff } from './handler.js'; import { analyzeDiffInputSchema } from './inputSchema.js'; import { CircletClient } from '../../clients/circlet/index.js'; import { RuleReview } from '../../clients/schemas.js'; // Mock the CircletClient vi.mock('../../clients/circlet/index.js'); describe('analyzeDiff', () => { beforeEach(() => { vi.clearAllMocks(); }); it('should return no rules message when rules is an empty string', async () => { const mockCircletInstance = { circlet: { ruleReview: vi.fn(), }, }; vi.mocked(CircletClient).mockImplementation( () => mockCircletInstance as any, ); const mockArgs = { params: { speedMode: false, filterBy: FilterBy.none, diff: 'diff --git a/test.ts b/test.ts\n+console.log("test");', rules: '', }, }; const controller = new AbortController(); const result = await analyzeDiff(mockArgs, { signal: controller.signal }); expect(result).toEqual({ content: [ { type: 'text', text: 'No rules found. Please add rules to your repository.', }, ], }); }); it('should return no diff message when diff is an empty string', async () => { const mockCircletInstance = { circlet: { ruleReview: vi.fn(), }, }; vi.mocked(CircletClient).mockImplementation( () => mockCircletInstance as any, ); const mockArgs = { params: { speedMode: false, filterBy: FilterBy.none, diff: '', rules: '', }, }; const controller = new AbortController(); const result = await analyzeDiff(mockArgs, { signal: controller.signal }); expect(result).toEqual({ content: [ { type: 'text', text: 'No diff found. Please provide a diff to analyze.', }, ], }); }); it('should handle complex diff content with multiple rules', async () => { const mockRuleReview: RuleReview = { isRuleCompliant: true, relatedRules: { compliant: [ { rule: 'Rule 1: No console.log statements', reason: 'No console.log statements found', confidenceScore: 0.95, }, ], violations: [], requiresHumanReview: [], }, unrelatedRules: [], }; const mockCircletInstance = { circlet: { ruleReview: vi.fn().mockResolvedValue(mockRuleReview), }, }; vi.mocked(CircletClient).mockImplementation( () => mockCircletInstance as any, ); const mockArgs = { params: { speedMode: false, filterBy: FilterBy.none, diff: `diff --git a/src/component.ts b/src/component.ts index 1234567..abcdefg 100644 --- a/src/component.ts +++ b/src/component.ts @@ -1,5 +1,8 @@ export class Component { + private data: any = {}; + constructor() { + console.log("Component created"); } }`, rules: `Rule 1: No console.log statements Rule 2: Avoid using 'any' type Rule 3: Use proper TypeScript types --- Rule 4: All functions must have JSDoc comments`, }, }; const controller = new AbortController(); const result = await analyzeDiff(mockArgs, { signal: controller.signal }); expect(mockCircletInstance.circlet.ruleReview).toHaveBeenCalledWith({ speedMode: false, filterBy: FilterBy.none, diff: mockArgs.params.diff, rules: mockArgs.params.rules, }); expect(result).toEqual({ content: [ { type: 'text', text: 'All rules are compliant.', }, ], }); }); it('should handle multiline rules and preserve formatting', async () => { const mockRuleReview: RuleReview = { isRuleCompliant: false, relatedRules: { compliant: [], violations: [ { rule: 'No Console Logs', reason: 'Console.log statements found in code', confidenceScore: 0.98, violationInstances: [ { file: 'src/component.ts', lineNumbersInDiff: ['2'], violatingCodeSnippet: 'console.log(x);', explanationOfViolation: 'Direct console.log usage', }, ], }, ], requiresHumanReview: [], }, unrelatedRules: [], }; const mockCircletInstance = { circlet: { ruleReview: vi.fn().mockResolvedValue(mockRuleReview), }, }; vi.mocked(CircletClient).mockImplementation( () => mockCircletInstance as any, ); const mockArgs = { params: { speedMode: false, filterBy: FilterBy.none, diff: '+const x = 5;\n+console.log(x);', rules: `# IDE Rules Example ## Rule: No Console Logs Description: Remove all console.log statements before committing code. ## Rule: TypeScript Safety Description: Avoid using 'any' type.`, }, }; const controller = new AbortController(); const result = await analyzeDiff(mockArgs, { signal: controller.signal }); expect(mockCircletInstance.circlet.ruleReview).toHaveBeenCalledWith({ speedMode: false, filterBy: FilterBy.none, diff: mockArgs.params.diff, rules: mockArgs.params.rules, }); expect(result.content[0].type).toBe('text'); expect(result.content[0].text).toContain('Rule: No Console Logs'); expect(result.content[0].text).toContain( 'Reason: Console.log statements found in code', ); expect(result.content[0].text).toContain('Confidence Score: 0.98'); }); it('should return compliant message when all rules are followed', async () => { const mockRuleReview: RuleReview = { isRuleCompliant: true, relatedRules: { compliant: [ { rule: 'No console.log statements', reason: 'Code follows proper logging practices', confidenceScore: 0.95, }, ], violations: [], requiresHumanReview: [], }, unrelatedRules: [], }; const mockCircletInstance = { circlet: { ruleReview: vi.fn().mockResolvedValue(mockRuleReview), }, }; vi.mocked(CircletClient).mockImplementation( () => mockCircletInstance as any, ); const mockArgs = { params: { speedMode: false, filterBy: FilterBy.none, diff: 'diff --git a/test.ts b/test.ts\n+const logger = new Logger();', rules: 'Rule 1: No console.log statements\nRule 2: Use proper logging', }, }; const controller = new AbortController(); const result = await analyzeDiff(mockArgs, { signal: controller.signal }); expect(mockCircletInstance.circlet.ruleReview).toHaveBeenCalledWith({ speedMode: false, filterBy: FilterBy.none, diff: mockArgs.params.diff, rules: mockArgs.params.rules, }); expect(result).toEqual({ content: [ { type: 'text', text: 'All rules are compliant.', }, ], }); }); it('should return formatted violations when rules are violated', async () => { const mockRuleReview: RuleReview = { isRuleCompliant: false, relatedRules: { compliant: [], violations: [ { rule: 'No console.log statements', reason: 'Console.log statements found in the code', confidenceScore: 0.98, violationInstances: [ { file: 'src/component.ts', lineNumbersInDiff: ['5'], violatingCodeSnippet: 'console.log("test");', explanationOfViolation: 'Direct console.log usage', }, ], }, { rule: 'Avoid using any type', reason: 'Any type usage reduces type safety', confidenceScore: 0.92, violationInstances: [ { file: 'src/component.ts', lineNumbersInDiff: ['3'], violatingCodeSnippet: 'private data: any = {};', explanationOfViolation: 'Variable declared with any type', }, ], }, ], requiresHumanReview: [], }, unrelatedRules: [], }; const mockCircletInstance = { circlet: { ruleReview: vi.fn().mockResolvedValue(mockRuleReview), }, }; vi.mocked(CircletClient).mockImplementation( () => mockCircletInstance as any, ); const mockArgs = { params: { speedMode: false, filterBy: FilterBy.none, diff: `diff --git a/src/component.ts b/src/component.ts index 1234567..abcdefg 100644 --- a/src/component.ts +++ b/src/component.ts @@ -1,5 +1,8 @@ export class Component { + private data: any = {}; + constructor() { + console.log("Component created"); } }`, rules: `Rule 1: No console.log statements Rule 2: Avoid using 'any' type Rule 3: Use proper TypeScript types`, }, }; const controller = new AbortController(); const result = await analyzeDiff(mockArgs, { signal: controller.signal }); expect(mockCircletInstance.circlet.ruleReview).toHaveBeenCalledWith({ filterBy: FilterBy.none, speedMode: false, diff: mockArgs.params.diff, rules: mockArgs.params.rules, }); expect(result).toEqual({ content: [ { type: 'text', text: `Rule: No console.log statements Reason: Console.log statements found in the code Confidence Score: 0.98 Rule: Avoid using any type Reason: Any type usage reduces type safety Confidence Score: 0.92`, }, ], }); }); it('should handle single violation correctly', async () => { const mockRuleReview: RuleReview = { isRuleCompliant: false, relatedRules: { compliant: [], violations: [ { rule: 'No magic numbers', reason: 'Magic numbers make code less maintainable', confidenceScore: 0.85, violationInstances: [ { file: 'src/component.ts', lineNumbersInDiff: ['2'], violatingCodeSnippet: 'const timeout = 5000;', explanationOfViolation: 'Hardcoded timeout value', }, ], }, ], requiresHumanReview: [], }, unrelatedRules: [], }; const mockCircletInstance = { circlet: { ruleReview: vi.fn().mockResolvedValue(mockRuleReview), }, }; vi.mocked(CircletClient).mockImplementation( () => mockCircletInstance as any, ); const mockArgs = { params: { speedMode: false, filterBy: FilterBy.none, diff: '+const timeout = 5000;', rules: 'Rule: No magic numbers', }, }; const controller = new AbortController(); const result = await analyzeDiff(mockArgs, { signal: controller.signal }); expect(result).toEqual({ content: [ { type: 'text', text: `Rule: No magic numbers Reason: Magic numbers make code less maintainable Confidence Score: 0.85`, }, ], }); }); it('should set default values for speedMode and filterBy when not provided', async () => { const mockRuleReview: RuleReview = { isRuleCompliant: true, relatedRules: { compliant: [], violations: [], requiresHumanReview: [], }, unrelatedRules: [], }; const mockCircletInstance = { circlet: { ruleReview: vi.fn().mockResolvedValue(mockRuleReview), }, }; vi.mocked(CircletClient).mockImplementation( () => mockCircletInstance as any, ); const rawParams = { diff: '+const timeout = 5000;', rules: 'Rule: No magic numbers', }; const parsedParams = analyzeDiffInputSchema.parse(rawParams); const mockArgs = { params: parsedParams, }; const controller = new AbortController(); await analyzeDiff(mockArgs, { signal: controller.signal }); // Verify default values (filterBy: FilterBy.none & speedMode: false) are applied when not explictly stated expect(mockCircletInstance.circlet.ruleReview).toHaveBeenCalledWith({ diff: rawParams.diff, rules: rawParams.rules, filterBy: FilterBy.none, speedMode: false, }); }); });

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/CircleCI-Public/mcp-server-circleci'

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