@boundless-oss/atlas
Version:
Atlas - MCP Server for comprehensive startup project management
329 lines • 12.9 kB
JavaScript
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