UNPKG

@boundless-oss/atlas

Version:

Atlas - MCP Server for comprehensive startup project management

329 lines 12.9 kB
import { StorageManager } from '../../storage/storage-manager.js'; import crypto from 'crypto'; export class TestingStore { storageManager; moduleName = 'testing-framework'; constructor(configManager) { this.storageManager = new StorageManager(); } async initialize() { await this.storageManager.ensureStorageDirectories(); } // Test Result Management async saveTestResult(result) { let history = []; try { history = await this.storageManager.loadData(this.moduleName, 'test-history.json') || []; } catch { // File doesn't exist yet } history.push(result); // Keep only last 100 test runs if (history.length > 100) { history = history.slice(-100); } await this.storageManager.saveData(this.moduleName, 'test-history.json', history); } async getTestHistory(limit) { try { const history = await this.storageManager.loadData(this.moduleName, 'test-history.json'); if (limit && limit > 0) { return (history || []).slice(-limit); } return history || []; } catch { return []; } } async getLatestTestResult() { const history = await this.getTestHistory(1); return history.length > 0 ? history[0] : null; } // Coverage Management async saveCoverageData(coverage) { const filename = `coverage-${new Date().toISOString().split('T')[0]}.json`; await this.storageManager.saveData(this.moduleName, filename, coverage); // Also update latest coverage await this.storageManager.saveData(this.moduleName, 'coverage-latest.json', coverage); } async getLatestCoverage() { try { return await this.storageManager.loadData(this.moduleName, 'coverage-latest.json'); } catch { return null; } } async getCoverageHistory(days = 30) { const history = []; const startDate = new Date(); startDate.setDate(startDate.getDate() - days); // This is simplified - in a real implementation, you'd scan for coverage files // For now, just return the latest coverage if it exists const latest = await this.getLatestCoverage(); if (latest) { history.push(latest); } return history; } // Flaky Test Detection async trackFlakyTest(testName, suiteName, passed) { let flakyTests = {}; try { flakyTests = await this.storageManager.loadData(this.moduleName, 'flaky-tests.json') || {}; } catch { // File doesn't exist yet } const key = `${suiteName}::${testName}`; if (!flakyTests[key]) { flakyTests[key] = { testId: crypto.randomUUID(), name: testName, suite: suiteName, failureRate: 0, recentResults: [], lastFailed: new Date().toISOString(), errorPatterns: [], testName, suiteName, firstSeen: new Date().toISOString(), lastSeen: new Date().toISOString(), failures: 0, passes: 0, totalRuns: 0, recentRuns: [], }; } const test = flakyTests[key]; test.lastSeen = new Date().toISOString(); test.totalRuns++; if (passed) { test.passes++; } else { test.failures++; } test.failureRate = test.failures / test.totalRuns; // Track recent runs (last 10) test.recentRuns.push({ passed, timestamp: new Date().toISOString(), }); if (test.recentRuns.length > 10) { test.recentRuns = test.recentRuns.slice(-10); } await this.storageManager.saveData(this.moduleName, 'flaky-tests.json', flakyTests); } async getFlakyTests(threshold = 0.1) { try { const flakyTests = await this.storageManager.loadData(this.moduleName, 'flaky-tests.json'); return Object.values(flakyTests || {}) .filter(test => test.failureRate > threshold && test.failureRate < (1 - threshold) && test.totalRuns >= 5) .sort((a, b) => b.failureRate - a.failureRate); } catch { return []; } } // Test Baselines async setTestBaseline(name, baseline) { let baselines = {}; try { baselines = await this.storageManager.loadData(this.moduleName, 'test-baselines.json') || {}; } catch { // File doesn't exist yet } baselines[name] = baseline; await this.storageManager.saveData(this.moduleName, 'test-baselines.json', baselines); } async getTestBaseline(name) { try { const baselines = await this.storageManager.loadData(this.moduleName, 'test-baselines.json'); return baselines[name] || null; } catch { return null; } } async getAllBaselines() { try { return await this.storageManager.loadData(this.moduleName, 'test-baselines.json') || {}; } catch { return {}; } } // Additional methods for testing-framework module async getTestHistoryForProject(projectId) { const history = await this.getTestHistory(); const projectHistory = history.filter(r => r.projectId === projectId); // Calculate trends const trends = []; if (projectHistory.length >= 2) { const recent = projectHistory.slice(-10); const successRates = recent.map(r => r.summary.successRate); const avgSuccessRate = successRates.reduce((a, b) => a + b, 0) / successRates.length; const firstRate = successRates[0]; const lastRate = successRates[successRates.length - 1]; trends.push({ metric: 'successRate', values: successRates.map((v, i) => ({ timestamp: recent[i].timestamp, value: v })), trend: lastRate > firstRate ? 'improving' : lastRate < firstRate ? 'degrading' : 'stable', changePercentage: ((lastRate - firstRate) / firstRate) * 100, }); } return { projectId, results: projectHistory, baseline: projectHistory.find(r => r.tags?.includes('baseline')), trends, lastUpdated: new Date().toISOString(), }; } async compareTestResults(baselineId, currentId) { const history = await this.getTestHistory(); const baseline = history.find(r => r.id === baselineId); const current = history.find(r => r.id === currentId); if (!baseline || !current) { throw new Error('Test results not found'); } const baselineTests = new Set(baseline.suites.flatMap(s => s.tests.map(t => `${s.name}::${t.name}`))); const currentTests = new Set(current.suites.flatMap(s => s.tests.map(t => `${s.name}::${t.name}`))); return { baseline, current, improvements: [], regressions: [], newTests: Array.from(currentTests).filter(t => !baselineTests.has(t)), removedTests: Array.from(baselineTests).filter(t => !currentTests.has(t)), coverageChange: { lines: (current.coverage?.lines.percentage || 0) - (baseline.coverage?.lines.percentage || 0), statements: (current.coverage?.statements.percentage || 0) - (baseline.coverage?.statements.percentage || 0), functions: (current.coverage?.functions.percentage || 0) - (baseline.coverage?.functions.percentage || 0), branches: (current.coverage?.branches.percentage || 0) - (baseline.coverage?.branches.percentage || 0), }, }; } async saveFlakyTests(tests) { await this.storageManager.saveData(this.moduleName, 'flaky-tests-report.json', { flakyTests: tests, timestamp: new Date().toISOString(), }); } async setBaseline(resultId, projectId) { const history = await this.getTestHistory(); const result = history.find(r => r.id === resultId); if (!result) { throw new Error('Test result not found'); } // Add baseline tag if (!result.tags) { result.tags = []; } if (!result.tags.includes('baseline')) { result.tags.push('baseline'); } // Update history await this.storageManager.saveData(this.moduleName, 'test-history.json', history); } async getTestResultById(id) { const history = await this.getTestHistory(); return history.find(r => r.id === id) || null; } // Test History Analysis async getTestTrend(testName, days = 7) { const history = await this.getTestHistory(); const startDate = new Date(); startDate.setDate(startDate.getDate() - days); const trend = []; for (const result of history) { if (new Date(result.timestamp) < startDate) continue; const suite = result.suites.find(s => s.tests.some(t => t.name === testName)); if (suite) { const test = suite.tests.find(t => t.name === testName); if (test) { trend.push({ timestamp: result.timestamp, passed: test.status === 'passed', duration: test.duration, }); } } } return trend; } async getSuccessRate(days = 30) { const history = await this.getTestHistory(); const startDate = new Date(); startDate.setDate(startDate.getDate() - days); const recentRuns = history.filter(result => new Date(result.timestamp) >= startDate); if (recentRuns.length === 0) return 0; const successfulRuns = recentRuns.filter(result => result.summary.failed === 0).length; return (successfulRuns / recentRuns.length) * 100; } // Export/Import async exportTestData() { const [history, coverage, flakyTests, baselines,] = await Promise.all([ this.getTestHistory(), this.getLatestCoverage(), this.storageManager.loadData(this.moduleName, 'flaky-tests.json').catch(() => ({})), this.getAllBaselines(), ]); return { testHistory: history, latestCoverage: coverage, flakyTests, testBaselines: baselines, exportedAt: new Date().toISOString(), }; } async importTestData(data) { if (data.testHistory) { await this.storageManager.saveData(this.moduleName, 'test-history.json', data.testHistory); } if (data.latestCoverage) { await this.storageManager.saveData(this.moduleName, 'coverage-latest.json', data.latestCoverage); } if (data.flakyTests) { await this.storageManager.saveData(this.moduleName, 'flaky-tests.json', data.flakyTests); } if (data.testBaselines) { await this.storageManager.saveData(this.moduleName, 'test-baselines.json', data.testBaselines); } } // Cleanup async cleanupOldData(daysToKeep = 30) { let cleanedItems = 0; // Clean test history const history = await this.getTestHistory(); const cutoffDate = new Date(); cutoffDate.setDate(cutoffDate.getDate() - daysToKeep); const filteredHistory = history.filter(result => new Date(result.timestamp) > cutoffDate); if (filteredHistory.length < history.length) { await this.storageManager.saveData(this.moduleName, 'test-history.json', filteredHistory); cleanedItems += history.length - filteredHistory.length; } // Clean flaky test data that hasn't been seen recently const flakyTests = await this.storageManager.loadData(this.moduleName, 'flaky-tests.json').catch(() => ({})); const filteredFlakyTests = {}; for (const [key, test] of Object.entries(flakyTests)) { if (new Date(test.lastSeen) > cutoffDate) { filteredFlakyTests[key] = test; } else { cleanedItems++; } } await this.storageManager.saveData(this.moduleName, 'flaky-tests.json', filteredFlakyTests); return cleanedItems; } } //# sourceMappingURL=store.js.map