@iservu-inc/adf-cli
Version:
CLI tool for AgentDevFramework - AI-assisted development framework with multi-provider AI support
343 lines (266 loc) • 11.2 kB
JavaScript
const QuestionMapper = require('../lib/analysis/question-mapper');
const KnowledgeGraph = require('../lib/analysis/knowledge-graph');
const fs = require('fs-extra');
const path = require('path');
const os = require('os');
describe('QuestionMapper', () => {
let mapper;
let kg;
let tempDir;
beforeEach(async () => {
mapper = new QuestionMapper();
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'qm-test-'));
kg = new KnowledgeGraph(tempDir);
});
afterEach(async () => {
await fs.remove(tempDir);
});
describe('mapQuestion', () => {
it('should map tech stack question correctly', () => {
const question = {
id: 'tech-stack',
text: 'What tech stack will you use?'
};
const mapping = mapper.mapQuestion(question);
expect(mapping.types).toContain('tech_stack');
expect(mapping.priority).toBeLessThanOrEqual(2); // High priority
expect(mapping.confidence).toBeGreaterThan(70);
});
it('should map goal question correctly', () => {
const question = {
id: 'project-goal',
text: 'What are you building?'
};
const mapping = mapper.mapQuestion(question);
expect(mapping.types).toContain('project_goal');
expect(mapping.priority).toBe(1); // Highest priority
expect(mapping.confidence).toBeGreaterThan(70);
});
it('should map user question correctly', () => {
const question = {
id: 'target-users',
text: 'Who are your target users?'
};
const mapping = mapper.mapQuestion(question);
expect(mapping.types).toContain('target_users');
expect(mapping.confidence).toBeGreaterThan(70);
});
it('should map architecture question correctly', () => {
const question = {
id: 'architecture',
text: 'How will you structure your application?'
};
const mapping = mapper.mapQuestion(question);
expect(mapping.types).toContain('architecture');
expect(mapping.confidence).toBeGreaterThan(70);
});
it('should map platform question correctly', () => {
const question = {
id: 'platform',
text: 'What platform will this run on?'
};
const mapping = mapper.mapQuestion(question);
expect(mapping.types).toContain('platform');
expect(mapping.confidence).toBeGreaterThan(70);
});
it('should return default mapping for unrecognized questions', () => {
const question = {
id: 'random',
text: 'Something totally unrelated?'
};
const mapping = mapper.mapQuestion(question);
expect(mapping.types.length).toBeGreaterThan(0); // Has some type
expect(mapping.confidence).toBeGreaterThan(0); // Has some confidence
});
it('should map multiple information types for comprehensive questions', () => {
const question = {
id: 'goal-and-tech',
text: 'What are you building and what technology will you use?'
};
const mapping = mapper.mapQuestion(question);
expect(mapping.types).toContain('project_goal');
expect(mapping.types).toContain('tech_stack');
expect(mapping.types.length).toBeGreaterThan(1);
});
});
describe('canSkipQuestion', () => {
it('should allow skipping when all information types are satisfied', () => {
const question = {
id: 'tech-stack',
text: 'What tech stack will you use?'
};
// Add tech stack knowledge
kg.add([
{ type: 'tech_stack', content: 'React and Node.js', confidence: 90, source: 'prev-q' }
]);
const result = mapper.canSkipQuestion(question, kg, 70);
expect(result.canSkip).toBe(true);
expect(result.reason).toContain('tech stack');
expect(result.satisfiedTypes).toHaveLength(1);
expect(result.missingTypes).toHaveLength(0);
});
it('should not skip when confidence is below threshold', () => {
const question = {
id: 'tech-stack',
text: 'What tech stack?'
};
kg.add([
{ type: 'tech_stack', content: 'React', confidence: 60, source: 'prev-q' }
]);
const result = mapper.canSkipQuestion(question, kg, 70);
expect(result.canSkip).toBe(false);
});
it('should not skip when information is missing', () => {
const question = {
id: 'tech-stack',
text: 'What tech stack will you use?'
};
// No knowledge added
const result = mapper.canSkipQuestion(question, kg, 70);
expect(result.canSkip).toBe(false);
expect(result.satisfiedTypes).toHaveLength(0);
expect(result.missingTypes.length).toBeGreaterThan(0);
});
it('should handle partial satisfaction', () => {
const question = {
id: 'goal-and-tech',
text: 'What are you building and what technology?'
};
// Only have goal, missing tech
kg.add([
{ type: 'project_goal', content: 'Building a CRM', confidence: 90, source: 'prev-q' }
]);
const result = mapper.canSkipQuestion(question, kg, 70);
expect(result.canSkip).toBe(false);
expect(result.satisfiedTypes.length).toBeGreaterThan(0);
expect(result.missingTypes.length).toBeGreaterThan(0);
expect(result.reason).toContain('Partial');
});
it('should respect custom minConfidence threshold', () => {
const question = {
id: 'tech-stack',
text: 'What tech stack?'
};
kg.add([
{ type: 'tech_stack', content: 'React', confidence: 75, source: 'prev-q' }
]);
// Should skip with 70 threshold
expect(mapper.canSkipQuestion(question, kg, 70).canSkip).toBe(true);
// Should not skip with 80 threshold
expect(mapper.canSkipQuestion(question, kg, 80).canSkip).toBe(false);
});
});
describe('reorderQuestions', () => {
it('should prioritize questions with missing information', () => {
const questions = [
{ id: 'tech-stack', text: 'What tech stack?' },
{ id: 'timeline', text: 'What is the timeline?' },
{ id: 'platform', text: 'What platform?' }
];
// Already have tech stack
kg.add([
{ type: 'tech_stack', content: 'React', confidence: 90, source: 'prev' }
]);
const reordered = mapper.reorderQuestions(questions, kg);
// Questions without satisfied info should come first
expect(reordered[0].question.id).not.toBe('tech-stack');
expect(reordered[2].question.id).toBe('tech-stack'); // Should be last
expect(reordered[2].relevanceScore).toBe(0); // Can skip
});
it('should assign high scores to unsatisfied questions', () => {
const questions = [
{ id: 'tech-stack', text: 'What tech stack?' }
];
const reordered = mapper.reorderQuestions(questions, kg);
expect(reordered[0].relevanceScore).toBe(100);
});
it('should assign zero score to fully satisfied questions', () => {
const questions = [
{ id: 'tech-stack', text: 'What tech stack?' }
];
kg.add([
{ type: 'tech_stack', content: 'React', confidence: 90, source: 'prev' },
{ type: 'architecture', content: 'SPA', confidence: 85, source: 'prev' }
]);
const reordered = mapper.reorderQuestions(questions, kg);
expect(reordered[0].relevanceScore).toBe(0);
expect(reordered[0].skipInfo.canSkip).toBe(true);
});
it('should boost high-priority questions', () => {
const questions = [
{ id: 'timeline', text: 'Timeline?' }, // Priority 4
{ id: 'goal', text: 'What building?' } // Priority 1
];
// Add some knowledge so we can see priority effects
kg.add([
{ type: 'timeline', content: '2 weeks', confidence: 60, source: 'prev' }
]);
const reordered = mapper.reorderQuestions(questions, kg);
// Goal question should have higher score due to priority boost
const goalItem = reordered.find(r => r.question.id === 'goal');
const timelineItem = reordered.find(r => r.question.id === 'timeline');
expect(goalItem.relevanceScore).toBeGreaterThanOrEqual(timelineItem.relevanceScore);
});
it('should handle partially satisfied questions', () => {
const questions = [
{ id: 'goal', text: 'What building?' },
{ id: 'tech', text: 'What tech?' }
];
// Partial knowledge - only have goal
kg.add([
{ type: 'project_goal', content: 'CRM', confidence: 90, source: 'prev' }
]);
const reordered = mapper.reorderQuestions(questions, kg);
// Tech should have full score (100), goal should be skippable (0)
const techItem = reordered.find(r => r.question.id === 'tech');
const goalItem = reordered.find(r => r.question.id === 'goal');
expect(techItem.relevanceScore).toBeGreaterThan(goalItem.relevanceScore);
});
});
describe('getStats', () => {
it('should calculate statistics correctly', () => {
const questions = [
{ id: 'tech-stack', text: 'What tech stack?' },
{ id: 'platform', text: 'What platform?' },
{ id: 'timeline', text: 'Timeline?' },
{ id: 'users', text: 'Who are users?' }
];
// Add some knowledge
kg.add([
{ type: 'tech_stack', content: 'React', confidence: 90, source: 'prev' },
{ type: 'platform', content: 'Web', confidence: 85, source: 'prev' }
]);
const stats = mapper.getStats(questions, kg);
expect(stats.total).toBe(4);
expect(stats.canSkip).toBeGreaterThanOrEqual(2); // tech_stack and platform
expect(stats.needed).toBeGreaterThan(0);
expect(stats.estimatedTimeSaved).toBeGreaterThan(0);
});
it('should handle empty knowledge graph', () => {
const questions = [
{ id: 'tech-stack', text: 'What tech stack?' },
{ id: 'platform', text: 'What platform?' }
];
const stats = mapper.getStats(questions, kg);
expect(stats.total).toBe(2);
expect(stats.canSkip).toBe(0);
expect(stats.needed).toBe(2);
expect(stats.estimatedTimeSaved).toBe(0);
});
it('should estimate time saved correctly', () => {
const questions = [
{ id: 'tech-stack', text: 'What tech stack?' },
{ id: 'platform', text: 'What platform?' },
{ id: 'timeline', text: 'What is the timeline?' }
];
// Can skip 2 questions
kg.add([
{ type: 'tech_stack', content: 'React', confidence: 90, source: 'prev' },
{ type: 'platform', content: 'Web', confidence: 90, source: 'prev' }
]);
const stats = mapper.getStats(questions, kg);
// If 2 can be skipped, time saved should be around 3 minutes (1.5 * 2)
expect(stats.estimatedTimeSaved).toBeGreaterThanOrEqual(1.5);
});
});
});