@iservu-inc/adf-cli
Version:
CLI tool for AgentDevFramework - AI-assisted development framework with multi-provider AI support
206 lines (164 loc) • 8.42 kB
JavaScript
const fs = require('fs-extra');
const path = require('path');
const ProgressTracker = require('../lib/frameworks/progress-tracker');
const TEST_SESSION_PATH = path.join(__dirname, 'test-session');
describe('ProgressTracker', () => {
beforeEach(async () => {
// Clean up test session directory
await fs.remove(TEST_SESSION_PATH);
await fs.ensureDir(TEST_SESSION_PATH);
});
afterEach(async () => {
// Clean up after tests
await fs.remove(TEST_SESSION_PATH);
});
describe('initialize', () => {
it('should create new progress file for new session', async () => {
const tracker = new ProgressTracker(TEST_SESSION_PATH, 5, 'rapid');
const isResumable = await tracker.initialize();
expect(isResumable).toBe(false);
expect(await fs.pathExists(path.join(TEST_SESSION_PATH, '_progress.json'))).toBe(true);
expect(await fs.pathExists(path.join(TEST_SESSION_PATH, '_progress-log.md'))).toBe(true);
});
it('should load existing progress for resumable session', async () => {
// Create initial session
const tracker1 = new ProgressTracker(TEST_SESSION_PATH, 5, 'rapid');
await tracker1.initialize();
await tracker1.startBlock(1, 'Test Block');
// Resume session
const tracker2 = new ProgressTracker(TEST_SESSION_PATH, 5, 'rapid');
const isResumable = await tracker2.initialize();
expect(isResumable).toBe(true);
expect(tracker2.getProgress().currentBlock).toBe(1);
});
});
describe('answerQuestion', () => {
it('should save answer with quality metrics', async () => {
const tracker = new ProgressTracker(TEST_SESSION_PATH, 5, 'rapid');
await tracker.initialize();
const qualityMetrics = {
wordCount: 50,
qualityScore: 85,
isComprehensive: true,
hasKeywords: { matched: ['react', 'web'], count: 2 },
hasRequiredElements: { detected: ['platform'], count: 1 }
};
await tracker.answerQuestion('q1', 'What are you building?', 'A React web app', qualityMetrics);
const progress = tracker.getProgress();
expect(progress.answers['q1']).toBeDefined();
expect(progress.answers['q1'].text).toBe('A React web app');
expect(progress.answers['q1'].quality.qualityScore).toBe(85);
expect(progress.totalWordCount).toBe(50);
expect(progress.comprehensiveAnswers).toBe(1);
});
it('should calculate average answer quality correctly', async () => {
const tracker = new ProgressTracker(TEST_SESSION_PATH, 5, 'rapid');
await tracker.initialize();
await tracker.answerQuestion('q1', 'Q1', 'Answer 1', { qualityScore: 80, wordCount: 20, isComprehensive: true });
await tracker.answerQuestion('q2', 'Q2', 'Answer 2', { qualityScore: 90, wordCount: 30, isComprehensive: true });
await tracker.answerQuestion('q3', 'Q3', 'Answer 3', { qualityScore: 70, wordCount: 25, isComprehensive: true });
const progress = tracker.getProgress();
expect(progress.averageAnswerQuality).toBe(80); // (80+90+70)/3 = 80
});
it('should calculate information richness based on quality and completion', async () => {
const tracker = new ProgressTracker(TEST_SESSION_PATH, 5, 'rapid');
await tracker.initialize();
// Complete 2 of 5 blocks (40% completion)
await tracker.completeBlock(1, 'Block 1', 3);
await tracker.completeBlock(2, 'Block 2', 2);
// Add high-quality answers (90% quality)
await tracker.answerQuestion('q1', 'Q1', 'A1', { qualityScore: 90, wordCount: 50, isComprehensive: true });
await tracker.answerQuestion('q2', 'Q2', 'A2', { qualityScore: 90, wordCount: 50, isComprehensive: true });
const progress = tracker.getProgress();
// informationRichness = (0.4 * completionFactor) + (0.6 * qualityFactor)
// = (0.4 * 0.4) + (0.6 * 0.9) = 0.16 + 0.54 = 0.70 = 70%
expect(progress.informationRichness).toBeCloseTo(70, 0);
});
});
describe('saveWithBackup', () => {
it('should create triple-redundant saves', async () => {
const tracker = new ProgressTracker(TEST_SESSION_PATH, 5, 'rapid');
await tracker.initialize();
await tracker.answerQuestion('q1', 'Q1', 'Answer', { qualityScore: 75, wordCount: 20, isComprehensive: true });
// Check all three save locations exist
expect(await fs.pathExists(path.join(TEST_SESSION_PATH, '_progress.json'))).toBe(true);
expect(await fs.pathExists(path.join(TEST_SESSION_PATH, '_progress.backup.json'))).toBe(true);
expect(await fs.pathExists(path.join(TEST_SESSION_PATH, '_progress-log.md'))).toBe(true);
// Verify main and backup have same content
const main = await fs.readJson(path.join(TEST_SESSION_PATH, '_progress.json'));
const backup = await fs.readJson(path.join(TEST_SESSION_PATH, '_progress.backup.json'));
expect(main).toEqual(backup);
});
it('should create emergency save on error', async () => {
const tracker = new ProgressTracker(TEST_SESSION_PATH, 5, 'rapid');
await tracker.initialize();
// Make main file read-only to simulate save error
const mainFile = path.join(TEST_SESSION_PATH, '_progress.json');
await fs.chmod(mainFile, 0o444);
try {
await tracker.answerQuestion('q1', 'Q1', 'Answer', { qualityScore: 75, wordCount: 20, isComprehensive: true });
} catch (error) {
// May throw, but should still create emergency file
}
// Check for emergency file
const files = await fs.readdir(TEST_SESSION_PATH);
const emergencyFiles = files.filter(f => f.startsWith('_emergency-'));
// Restore permissions for cleanup
await fs.chmod(mainFile, 0o666);
// Emergency file should exist if save failed
expect(emergencyFiles.length).toBeGreaterThan(0);
});
});
describe('block tracking', () => {
it('should track block start, complete, and skip', async () => {
const tracker = new ProgressTracker(TEST_SESSION_PATH, 5, 'rapid');
await tracker.initialize();
await tracker.startBlock(1, 'Block 1');
await tracker.completeBlock(1, 'Block 1', 3);
await tracker.skipBlock(2, 'Block 2');
const progress = tracker.getProgress();
expect(progress.currentBlock).toBe(1);
expect(progress.completedBlocks.length).toBe(1);
expect(progress.completedBlocks[0].number).toBe(1);
expect(progress.completedBlocks[0].questionsAnswered).toBe(3);
expect(progress.skippedBlocks.length).toBe(1);
expect(progress.skippedBlocks[0].number).toBe(2);
});
});
describe('complete', () => {
it('should mark session as completed', async () => {
const tracker = new ProgressTracker(TEST_SESSION_PATH, 5, 'rapid');
await tracker.initialize();
await tracker.complete();
const progress = tracker.getProgress();
expect(progress.status).toBe('completed');
expect(progress.completedAt).toBeDefined();
expect(progress.canResume).toBe(false);
});
});
describe('canResume', () => {
it('should return true for in-progress sessions', async () => {
const tracker = new ProgressTracker(TEST_SESSION_PATH, 5, 'rapid');
await tracker.initialize();
expect(tracker.canResume()).toBe(true);
});
it('should return false for completed sessions', async () => {
const tracker = new ProgressTracker(TEST_SESSION_PATH, 5, 'rapid');
await tracker.initialize();
await tracker.complete();
expect(tracker.canResume()).toBe(false);
});
});
describe('getResumeInfo', () => {
it('should return resume information', async () => {
const tracker = new ProgressTracker(TEST_SESSION_PATH, 5, 'rapid');
await tracker.initialize();
await tracker.completeBlock(1, 'Block 1', 3); // This adds 3 to totalQuestionsAnswered
await tracker.answerQuestion('q1', 'Q1', 'A1', { qualityScore: 80, wordCount: 20, isComprehensive: true });
const info = tracker.getResumeInfo();
expect(info.completedBlocks).toBe(1);
expect(info.totalBlocks).toBe(5);
expect(info.totalQuestionsAnswered).toBe(3); // completeBlock added 3
});
});
});