memory-mcp-tools.test.ts•18 kB
/**
* Memory MCP Tools Integration Tests
* Tests integration between memory system and MCP tools
* Part of Issue #56 - Memory MCP Tools Integration Tests
*/
import { promises as fs } from 'fs';
import path from 'path';
import os from 'os';
import {
initializeMemory,
rememberAnalysis,
rememberRecommendation,
rememberDeployment,
rememberConfiguration,
recallProjectHistory,
getProjectInsights,
getSimilarProjects,
getMemoryStatistics,
} from '../../src/memory/integration.js';
import { analyzeRepository } from '../../src/tools/analyze-repository.js';
import { recommendSSG } from '../../src/tools/recommend-ssg.js';
describe('Memory MCP Tools Integration', () => {
let tempDir: string;
let testProjectDir: string;
beforeEach(async () => {
// Create unique temp directory for each test
tempDir = path.join(
os.tmpdir(),
`memory-mcp-integration-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
);
await fs.mkdir(tempDir, { recursive: true });
// Create a mock project structure for testing
testProjectDir = path.join(tempDir, 'test-project');
await createMockProject(testProjectDir);
});
afterEach(async () => {
// Cleanup temp directory
try {
await fs.rm(tempDir, { recursive: true, force: true });
} catch (error) {
// Ignore cleanup errors
}
});
async function createMockProject(projectPath: string) {
await fs.mkdir(projectPath, { recursive: true });
// Create package.json
await fs.writeFile(
path.join(projectPath, 'package.json'),
JSON.stringify(
{
name: 'test-project',
version: '1.0.0',
dependencies: {
react: '^18.0.0',
typescript: '^5.0.0',
},
devDependencies: {
jest: '^29.0.0',
},
},
null,
2,
),
);
// Create README.md
await fs.writeFile(
path.join(projectPath, 'README.md'),
`# Test Project
A test project for memory integration testing.
## Features
- TypeScript support
- React components
- Jest testing
`,
);
// Create src directory with TypeScript files
await fs.mkdir(path.join(projectPath, 'src'));
await fs.writeFile(path.join(projectPath, 'src/index.ts'), 'export const hello = "world";');
await fs.writeFile(
path.join(projectPath, 'src/component.tsx'),
'import React from "react"; export const Component = () => <div>Hello</div>;',
);
// Create tests directory
await fs.mkdir(path.join(projectPath, '__tests__'));
await fs.writeFile(
path.join(projectPath, '__tests__/index.test.ts'),
'test("hello world", () => { expect(true).toBe(true); });',
);
// Create docs directory
await fs.mkdir(path.join(projectPath, 'docs'));
await fs.writeFile(
path.join(projectPath, 'docs/setup.md'),
'# Setup Guide\n\nHow to set up the project.',
);
}
describe('Memory Integration Initialization', () => {
test('should initialize memory system for MCP tools', async () => {
const memoryManager = await initializeMemory();
expect(memoryManager).toBeDefined();
expect(memoryManager.constructor.name).toBe('MemoryManager');
});
test('should handle memory system events', async () => {
const memoryManager = await initializeMemory();
let eventsFired = 0;
memoryManager.on('memory-created', () => {
eventsFired++;
});
// Create a memory entry
await memoryManager.remember('analysis', { test: true });
// Give events time to fire
await new Promise((resolve) => setTimeout(resolve, 50));
expect(eventsFired).toBeGreaterThanOrEqual(1);
});
});
describe('Repository Analysis Integration', () => {
test('should integrate repository analysis with memory system', async () => {
// Run repository analysis tool
const analysisResult = await analyzeRepository({
path: testProjectDir,
depth: 'standard',
});
expect(analysisResult.isError).toBeFalsy();
expect(analysisResult.content).toBeDefined();
// Extract analysis data from MCP response
const analysisContent = analysisResult.content.find(
(c) => c.type === 'text' && c.text.includes('Analysis Complete'),
);
expect(analysisContent).toBeDefined();
// Remember analysis in memory system
const analysisData = {
projectId: 'test-project',
language: { primary: 'typescript' },
framework: { name: 'react' },
stats: { files: 5 },
repository: { url: 'github.com/test/project' },
};
const memoryId = await rememberAnalysis(testProjectDir, analysisData);
expect(memoryId).toBeDefined();
expect(typeof memoryId).toBe('string');
// Verify memory was stored
const memoryManager = await initializeMemory();
const recalled = await memoryManager.recall(memoryId);
expect(recalled).not.toBeNull();
expect(recalled?.data.language.primary).toBe('typescript');
expect(recalled?.data.framework.name).toBe('react');
});
test('should store analysis metadata correctly', async () => {
const analysisData = {
projectId: 'metadata-test',
language: { primary: 'javascript' },
framework: { name: 'vue' },
stats: { files: 25 },
repository: { url: 'github.com/test/vue-project' },
};
const memoryId = await rememberAnalysis('/test/vue-project', analysisData);
const memoryManager = await initializeMemory();
const recalled = await memoryManager.recall(memoryId);
expect(recalled?.metadata.repository).toBe('github.com/test/vue-project');
expect(recalled?.metadata.tags).toContain('analysis');
expect(recalled?.metadata.tags).toContain('javascript');
expect(recalled?.metadata.tags).toContain('vue');
});
});
describe('SSG Recommendation Integration', () => {
test('should integrate SSG recommendation with memory system', async () => {
// First create an analysis
const analysisData = {
projectId: 'ssg-test',
language: { primary: 'typescript' },
framework: { name: 'react' },
};
const analysisId = await rememberAnalysis('/test/ssg-project', analysisData);
// Run SSG recommendation tool
const recommendationResult = await recommendSSG({
analysisId,
preferences: {
priority: 'features',
ecosystem: 'javascript',
},
});
expect(recommendationResult.content).toBeDefined();
// Extract recommendation data
const recommendationData = {
recommended: 'docusaurus',
confidence: 0.85,
reasoning: ['React-based', 'TypeScript support'],
analysisId,
};
const memoryId = await rememberRecommendation(analysisId, recommendationData);
expect(memoryId).toBeDefined();
// Verify memory linking
const memoryManager = await initializeMemory();
const recalled = await memoryManager.recall(memoryId);
expect(recalled?.data.recommended).toBe('docusaurus');
expect(recalled?.metadata.ssg).toBe('docusaurus');
expect(recalled?.metadata.tags).toContain('recommendation');
expect(recalled?.metadata.tags).toContain('docusaurus');
});
test('should link recommendations to analysis', async () => {
// Create analysis
const analysisData = {
projectId: 'linked-test',
language: { primary: 'python' },
};
const analysisId = await rememberAnalysis('/test/python-project', analysisData);
// Create recommendation
const recommendationData = {
recommended: 'mkdocs',
confidence: 0.9,
};
const recommendationId = await rememberRecommendation(analysisId, recommendationData);
// Verify linking
const memoryManager = await initializeMemory();
const recommendation = await memoryManager.recall(recommendationId);
const analysis = await memoryManager.recall(analysisId);
expect(recommendation?.metadata.projectId).toBe(analysis?.metadata.projectId);
expect(recommendation?.metadata.projectId).toBe('linked-test');
});
});
describe('Deployment Memory Integration', () => {
test('should store deployment results in memory', async () => {
const deploymentData = {
ssg: 'hugo',
status: 'success',
duration: 120,
url: 'https://test-project.github.io',
branch: 'gh-pages',
};
const memoryId = await rememberDeployment('github.com/test/project', deploymentData);
const memoryManager = await initializeMemory();
const recalled = await memoryManager.recall(memoryId);
expect(recalled?.data.ssg).toBe('hugo');
expect(recalled?.data.status).toBe('success');
expect(recalled?.metadata.repository).toBe('github.com/test/project');
expect(recalled?.metadata.tags).toContain('deployment');
expect(recalled?.metadata.tags).toContain('success');
expect(recalled?.metadata.tags).toContain('hugo');
});
test('should track deployment failures', async () => {
const failedDeployment = {
ssg: 'jekyll',
status: 'failed',
error: 'Build failed: missing dependency',
duration: 45,
};
const memoryId = await rememberDeployment('github.com/test/failed-project', failedDeployment);
const memoryManager = await initializeMemory();
const recalled = await memoryManager.recall(memoryId);
expect(recalled?.data.status).toBe('failed');
expect(recalled?.data.error).toContain('Build failed');
expect(recalled?.metadata.tags).toContain('failed');
});
});
describe('Configuration Memory Integration', () => {
test('should store configuration data in memory', async () => {
const configData = {
title: 'Test Documentation',
theme: 'material',
plugins: ['search', 'navigation'],
build: {
outputDir: '_site',
baseUrl: '/docs/',
},
};
const memoryId = await rememberConfiguration('test-docs', 'mkdocs', configData);
const memoryManager = await initializeMemory();
const recalled = await memoryManager.recall(memoryId);
expect(recalled?.data.title).toBe('Test Documentation');
expect(recalled?.data.theme).toBe('material');
expect(recalled?.metadata.ssg).toBe('mkdocs');
expect(recalled?.metadata.tags).toContain('configuration');
expect(recalled?.metadata.tags).toContain('mkdocs');
expect(recalled?.metadata.tags).toContain('test-docs');
});
});
describe('Project History and Insights', () => {
test('should recall comprehensive project history', async () => {
const projectId = 'history-test';
// Create a complete project workflow in memory
const analysisData = {
projectId,
language: { primary: 'typescript' },
framework: { name: 'react' },
};
await rememberAnalysis('/test/history-project', analysisData);
const recommendationData = {
recommended: 'docusaurus',
confidence: 0.8,
};
await rememberRecommendation('analysis-id', recommendationData);
const deploymentData = {
ssg: 'docusaurus',
status: 'success',
duration: 90,
};
await rememberDeployment('github.com/test/history-project', deploymentData);
// Recall project history
const history = await recallProjectHistory(projectId);
expect(history.projectId).toBe(projectId);
expect(history.history).toBeDefined();
expect(history.insights).toBeDefined();
expect(Array.isArray(history.insights)).toBe(true);
});
test('should generate meaningful project insights', async () => {
const projectId = 'insights-test';
// Create deployment history
const successfulDeployment = {
ssg: 'hugo',
status: 'success',
duration: 60,
};
await rememberDeployment('github.com/test/insights-project', successfulDeployment);
const failedDeployment = {
ssg: 'hugo',
status: 'failed',
error: 'Build timeout',
};
await rememberDeployment('github.com/test/insights-project', failedDeployment);
const insights = await getProjectInsights(projectId);
expect(Array.isArray(insights)).toBe(true);
// Insights may be empty if memories don't meet criteria, that's ok
if (insights.length > 0) {
// Should include deployment success rate if deployments exist
const successRateInsight = insights.find((i) => i.includes('success rate'));
expect(successRateInsight).toBeDefined();
}
});
});
describe('Similar Projects Discovery', () => {
test('should find similar projects based on characteristics', async () => {
// Create multiple projects with different characteristics
await rememberAnalysis('/project1', {
projectId: 'project1',
language: { primary: 'typescript' },
framework: { name: 'react' },
});
await rememberAnalysis('/project2', {
projectId: 'project2',
language: { primary: 'typescript' },
framework: { name: 'vue' },
});
await rememberAnalysis('/project3', {
projectId: 'project3',
language: { primary: 'python' },
framework: { name: 'django' },
});
// Search for similar projects
const targetProject = {
language: { primary: 'typescript' },
framework: { name: 'react' },
stats: { files: 100 },
};
const similarProjects = await getSimilarProjects(targetProject, 3);
expect(Array.isArray(similarProjects)).toBe(true);
// Similar projects may be empty if no matches found, that's ok
if (similarProjects.length > 0) {
// Should find TypeScript projects first
const tsProjects = similarProjects.filter((p) => p.similarity > 0);
expect(tsProjects.length).toBeGreaterThan(0);
}
});
test('should calculate similarity scores correctly', async () => {
// Create projects with known characteristics
await rememberAnalysis('/exact-match', {
projectId: 'exact-match',
language: { primary: 'javascript' },
framework: { name: 'vue' },
stats: { files: 50 },
documentation: { type: 'api' },
});
await rememberAnalysis('/partial-match', {
projectId: 'partial-match',
language: { primary: 'javascript' },
framework: { name: 'react' },
stats: { files: 45 },
});
const targetProject = {
language: { primary: 'javascript' },
framework: { name: 'vue' },
stats: { files: 52 },
documentation: { type: 'api' },
};
const similarProjects = await getSimilarProjects(targetProject, 5);
expect(Array.isArray(similarProjects)).toBe(true);
// Similar projects may be empty, but if found should have similarity scores
if (similarProjects.length > 0) {
const exactMatch = similarProjects.find((p) => p.projectId === 'exact-match');
const partialMatch = similarProjects.find((p) => p.projectId === 'partial-match');
if (exactMatch && partialMatch) {
expect(exactMatch.similarity).toBeGreaterThan(partialMatch.similarity);
}
}
});
});
describe('Memory Statistics and Analytics', () => {
test('should provide memory statistics for tools', async () => {
// Create some test data
await rememberAnalysis('/stats-test', {
projectId: 'stats-test',
language: { primary: 'go' },
});
await rememberDeployment('github.com/test/stats', {
ssg: 'hugo',
status: 'success',
});
const stats = await getMemoryStatistics();
expect(stats).toBeDefined();
expect(typeof stats).toBe('object');
});
});
describe('Error Handling and Edge Cases', () => {
test('should handle malformed analysis data gracefully', async () => {
const malformedData = {
// Missing required fields
language: null,
framework: undefined,
};
// Should not throw but handle gracefully
const memoryId = await rememberAnalysis('/malformed', malformedData);
expect(memoryId).toBeDefined();
const memoryManager = await initializeMemory();
const recalled = await memoryManager.recall(memoryId);
expect(recalled).not.toBeNull();
});
test('should handle missing analysis when creating recommendations', async () => {
const recommendationData = {
recommended: 'jekyll',
confidence: 0.7,
};
// Reference non-existent analysis
const memoryId = await rememberRecommendation('non-existent-analysis', recommendationData);
expect(memoryId).toBeDefined();
const memoryManager = await initializeMemory();
const recalled = await memoryManager.recall(memoryId);
expect(recalled?.data.recommended).toBe('jekyll');
});
test('should handle empty project history gracefully', async () => {
const history = await recallProjectHistory('non-existent-project');
expect(history.projectId).toBe('non-existent-project');
expect(history.history).toBeDefined();
expect(Array.isArray(history.insights)).toBe(true);
});
test('should handle similar projects search with no matches', async () => {
const uniqueProject = {
language: { primary: 'rust' },
framework: { name: 'actix' },
stats: { files: 500 },
};
const similarProjects = await getSimilarProjects(uniqueProject, 5);
expect(Array.isArray(similarProjects)).toBe(true);
// Should return empty array or minimal matches
expect(similarProjects.length).toBeGreaterThanOrEqual(0);
});
});
});