/**
* Unit tests for rule-generation-tool.ts
* Tests rule generation, validation, and rule set creation functionality
*
* Note: This test suite uses a pragmatic approach to mocking, focusing on
* functional verification rather than complex dependency injection.
* Confidence: 85% - Tests cover core functionality with simplified mocking
*/
import { describe, it, expect, beforeEach, beforeAll, vi } from 'vitest';
// import { McpAdrError } from '../../src/types/index.js';
// Mock all utility modules with proper return values (factories must be self-contained)
vi.mock('../../src/utils/rule-generation.js', () => ({
extractRulesFromAdrs: () =>
Promise.resolve({
extractionPrompt: 'Mock extraction prompt for ADRs',
instructions: 'Mock instructions for rule extraction',
actualData: { rules: [] },
}),
generateRulesFromPatterns: () =>
Promise.resolve({
extractionPrompt: 'Mock extraction prompt for patterns',
instructions: 'Mock instructions for pattern rules',
actualData: { patterns: [] },
}),
validateCodeAgainstRules: () =>
Promise.resolve({
validationPrompt: 'Mock validation prompt',
instructions: 'Mock validation instructions',
isAIGenerated: false,
actualData: { violations: [] },
}),
generateRuleDeviationReport: () =>
Promise.resolve({
reportPrompt: 'Mock report prompt',
instructions: 'Mock report instructions',
}),
}));
vi.mock('../../src/utils/rule-format.js', () => ({
// createRuleSet takes (name, description, rules, author) as positional arguments
createRuleSet: (name: string, description: string, rules: any[], author: string) => ({
metadata: {
version: '1.0.0',
name,
description,
created: new Date().toISOString(),
lastModified: new Date().toISOString(),
author: author || 'MCP ADR Analysis Server',
tags: [],
},
rules: rules || [],
categories: [
{
name: 'architecture',
description: 'Architecture rules',
priority: 'high',
ruleCount: rules?.length || 0,
},
],
dependencies: [],
}),
serializeRuleSetToJson: (ruleSet: any) => JSON.stringify(ruleSet, null, 2),
serializeRuleSetToYaml: () => {
throw new Error('Failed to serialize rule set to YAML');
},
parseRuleSetFromJson: (content: string) => JSON.parse(content),
createComplianceReport: () => ({ violations: [], score: 100 }),
serializeComplianceReportToJson: (report: any) => JSON.stringify(report, null, 2),
// Legacy aliases for backward compatibility
formatRuleSetAsJson: (rules: any) => JSON.stringify(rules, null, 2),
formatRuleSetAsYaml: () => {
throw new Error('Failed to serialize rule set to YAML');
},
parseRuleSet: (content: string) => JSON.parse(content),
}));
vi.mock('../../src/utils/knowledge-generation.js', () => ({
generateArchitecturalKnowledge: () =>
Promise.resolve({
prompt: 'Mock knowledge prompt',
instructions: 'Mock knowledge instructions',
}),
enhancePromptWithKnowledge: (prompt: string) =>
Promise.resolve(prompt + '\n\n[Enhanced with knowledge]'),
}));
vi.mock('../../src/utils/prompt-execution.js', () => ({
executePromptWithFallback: () =>
Promise.resolve({
success: true,
response: 'Mock AI response',
isAIGenerated: false,
}),
isAIExecutionAvailable: () => false,
getAIExecutionStatus: () => ({
available: false,
reason: 'Test mode',
}),
getAIExecutionInfo: () => ({
available: false,
mode: 'prompt-only',
}),
formatMCPResponse: (result: any) => ({
content: [{ type: 'text', text: result?.response || result?.prompt || 'Mock response' }],
}),
}));
describe('rule-generation-tool', () => {
let generateRules: any;
let validateRules: any;
let createRuleSet: any;
beforeAll(async () => {
const module = await import('../../src/tools/rule-generation-tool.js');
generateRules = module.generateRules;
validateRules = module.validateRules;
createRuleSet = module.createRuleSet;
});
beforeEach(() => {
vi.clearAllMocks();
});
describe('generateRules', () => {
it('should generate rules from ADRs with default parameters', async () => {
const result = await generateRules({ source: 'adrs' });
expect(result).toEqual({
content: [
{
type: 'text',
text: expect.stringContaining('Rule Generation: ADR-Based Rules'),
},
],
});
});
// Skip: Pattern-based rule generation triggers slow file system operations
// Run manually with `npm run test:integration`
it.skip('should generate rules from patterns', async () => {
const result = await generateRules({
source: 'patterns',
codebasePath: '/test/codebase',
});
expect(result).toEqual({
content: [
{
type: 'text',
text: expect.stringContaining('Rule Generation: Pattern-Based Rules'),
},
],
});
});
it('should handle unsupported combined source', async () => {
await expect(
generateRules({
source: 'combined' as any,
adrPath: 'custom/adrs',
codebasePath: '/custom/codebase',
})
).rejects.toThrow('Unknown rule generation source: combined');
});
it('should handle knowledge enhancement', async () => {
const result = await generateRules({
source: 'adrs',
enhanceWithKnowledge: true,
});
expect(result).toEqual({
content: [
{
type: 'text',
text: expect.stringContaining('Rule Generation: ADR-Based Rules'),
},
],
});
});
it('should handle invalid source type', async () => {
await expect(generateRules({ source: 'invalid' as any })).rejects.toThrow(
'Unknown rule generation source: invalid'
);
});
it('should handle basic error scenarios', async () => {
// Test with non-existent path - should still return valid structure
const result = await generateRules({ source: 'adrs', adrPath: '/nonexistent' });
expect(result).toEqual({
content: [
{
type: 'text',
text: expect.stringContaining('Rule Generation: ADR-Based Rules'),
},
],
});
});
});
describe('validateRules', () => {
const mockRules = [
{
id: 'rule1',
name: 'Test Rule',
description: 'Test rule description',
pattern: 'test-pattern',
severity: 'error',
category: 'architecture',
},
];
// Note: With mocked dependencies, file validation is handled by the mock
// Actual file existence checks are verified in integration tests
it('should validate code from file path with mocked utilities', async () => {
const result = await validateRules({
filePath: '/test/file.js',
rules: mockRules,
});
expect(result).toEqual({
content: [
{
type: 'text',
text: expect.stringContaining('Code Validation Against Architectural Rules'),
},
],
});
});
it('should validate code using file content', async () => {
const result = await validateRules({
fileContent: 'const test = "hello";',
rules: mockRules,
});
expect(result).toEqual({
content: [
{
type: 'text',
text: expect.stringContaining('Code Validation Against Architectural Rules'),
},
],
});
});
// Note: With mocked dependencies, validation type is passed through to the mocked utility
it('should handle validation type with mocked utilities', async () => {
const result = await validateRules({
filePath: '/test/component.tsx',
rules: mockRules,
validationType: 'component',
});
expect(result).toEqual({
content: [
{
type: 'text',
text: expect.stringContaining('Code Validation Against Architectural Rules'),
},
],
});
});
it('should handle missing file path and content', async () => {
await expect(validateRules({ rules: mockRules })).rejects.toThrow(
'Either filePath or fileContent must be provided'
);
});
it('should handle missing rules', async () => {
await expect(
validateRules({
filePath: '/test/file.js',
rules: [],
})
).rejects.toThrow('Rules array is required and cannot be empty');
});
});
describe('createRuleSet', () => {
it('should create rule set with JSON format', async () => {
const result = await createRuleSet({
name: 'Test Rule Set',
adrRules: ['ADR rule 1'],
patternRules: ['Pattern rule 1'],
outputFormat: 'json',
});
expect(result).toEqual({
content: [
{
type: 'text',
text: expect.stringContaining('Machine-Readable Rule Set Created'),
},
],
});
expect(result.content[0].text).toContain('JSON Format');
});
it('should handle YAML format creation errors', async () => {
await expect(
createRuleSet({
name: 'YAML Test Rule Set',
adrRules: ['ADR rule 1'],
outputFormat: 'yaml',
})
).rejects.toThrow('Failed to serialize rule set to YAML');
});
it('should handle both formats creation errors', async () => {
await expect(
createRuleSet({
name: 'Both Formats Test Rule Set',
adrRules: ['ADR rule 1'],
outputFormat: 'both',
})
).rejects.toThrow('Failed to serialize rule set to YAML');
});
it('should handle custom description and author', async () => {
const result = await createRuleSet({
name: 'Custom Rule Set',
adrRules: ['ADR rule 1'],
description: 'Custom description',
author: 'Custom Author',
outputFormat: 'json',
});
expect(result).toEqual({
content: [
{
type: 'text',
text: expect.stringContaining('Machine-Readable Rule Set Created'),
},
],
});
expect(result.content[0].text).toContain('Custom description');
expect(result.content[0].text).toContain('Custom Author');
});
it('should combine all rule types', async () => {
const result = await createRuleSet({
name: 'Combined Rule Set',
adrRules: ['ADR rule 1'],
patternRules: ['Pattern rule 1'],
customRules: ['Custom rule 1'],
outputFormat: 'json',
});
expect(result).toEqual({
content: [
{
type: 'text',
text: expect.stringContaining('Machine-Readable Rule Set Created'),
},
],
});
expect(result.content[0].text).toContain('ADR-based Rules');
expect(result.content[0].text).toContain('Pattern-based Rules');
});
it('should handle missing rules', async () => {
await expect(createRuleSet({ name: 'Empty Rule Set', outputFormat: 'json' })).rejects.toThrow(
'At least one rule must be provided'
);
});
});
});