test-image-selection-integration.js•22 kB
/**
* Integration Test: Context-Aware Image Selection System
* Task 3.2: Context-Aware Image Selection Validation
*
* Tests the complete image selection and mapping system:
* - Subject-specific image selection
* - Widget-specific image mapping
* - Educational context awareness
* - Fallback mechanisms
* - Integration with Phase 2 assessment components
*/
const { ImageSelectionService } = require('../../src/services/image-selection-service');
const { EducationalImageMapper } = require('../../src/widget-mappers/educational-image-mapper');
class ImageSelectionIntegrationTest {
constructor() {
this.imageService = new ImageSelectionService();
this.imageMapper = new EducationalImageMapper();
this.testResults = {
total: 0,
passed: 0,
failed: 0,
details: []
};
}
/**
* Run comprehensive integration tests
*/
async runIntegrationTests() {
console.log('🧪 Starting Image Selection Integration Tests...\n');
// Test 1: Subject-Specific Image Selection
await this.testSubjectSpecificSelection();
// Test 2: Widget-Specific Image Mapping
await this.testWidgetSpecificMapping();
// Test 3: Educational Context Awareness
await this.testEducationalContextAwareness();
// Test 4: Fallback Mechanisms
await this.testFallbackMechanisms();
// Test 5: Phase 2 Assessment Integration
await this.testAssessmentComponentIntegration();
// Test 6: Brazilian Educational Standards
await this.testBrazilianEducationalStandards();
// Test 7: Performance and Quality Metrics
await this.testPerformanceAndQuality();
this.printResults();
return this.testResults;
}
/**
* Test 1: Subject-Specific Image Selection
*/
async testSubjectSpecificSelection() {
console.log('📋 Test 1: Subject-Specific Image Selection');
const testCases = [
{
name: 'Physics - Ballistics',
content: 'O movimento de projéteis segue uma trajetória parabólica devido à ação da gravidade',
subject: 'física',
expectedPattern: 'trajetoria'
},
{
name: 'Chemistry - Molecular Structure',
content: 'As moléculas de água possuem ligações covalentes entre átomos de hidrogênio e oxigênio',
subject: 'química',
expectedPattern: 'moleculas'
},
{
name: 'History - Brazilian Independence',
content: 'A independência do Brasil foi proclamada por Dom Pedro I em 1822',
subject: 'história',
expectedPattern: 'independencia'
},
{
name: 'Biology - Photosynthesis',
content: 'A fotossíntese ocorre nos cloroplastos das células vegetais',
subject: 'ciências',
expectedPattern: 'fotossintese'
}
];
for (const testCase of testCases) {
this.testResults.total++;
try {
const result = this.imageService.selectContextualImage(
testCase.content,
testCase.subject,
'fundamental',
'image-1'
);
// Validate result structure
const hasRequiredFields = result.url && result.caption && result.alt_text && result.educational_context;
const hasSubjectContext = result.educational_context.includes(testCase.subject);
const isEducationalUrl = result.url.includes('education') || result.url.includes('cdn.digitalpages');
if (hasRequiredFields && hasSubjectContext && isEducationalUrl) {
this.testResults.passed++;
this.testResults.details.push({
test: testCase.name,
status: 'PASSED',
message: `Correctly selected ${testCase.subject} image`,
data: { url: result.url, context: result.educational_context }
});
console.log(` ✅ ${testCase.name}: PASSED`);
} else {
throw new Error(`Invalid image selection result for ${testCase.subject}`);
}
} catch (error) {
this.testResults.failed++;
this.testResults.details.push({
test: testCase.name,
status: 'FAILED',
message: error.message,
data: null
});
console.log(` ❌ ${testCase.name}: FAILED - ${error.message}`);
}
}
console.log('');
}
/**
* Test 2: Widget-Specific Image Mapping
*/
async testWidgetSpecificMapping() {
console.log('📋 Test 2: Widget-Specific Image Mapping');
const testCases = [
{
name: 'Header Widget (head-1)',
context: {
content: 'Aula sobre fotossíntese',
subject: 'ciências',
grade_level: 'fundamental',
widget_type: 'head-1',
learning_intent: 'introduction'
},
expectedProperties: ['background_image', 'avatar']
},
{
name: 'Quiz Widget (quiz-1)',
context: {
content: 'Questões sobre movimento de projéteis',
subject: 'física',
grade_level: 'médio',
widget_type: 'quiz-1',
learning_intent: 'assessment'
},
expectedProperties: ['question_images', 'feedback_correct', 'feedback_incorrect']
},
{
name: 'Flashcard Widget (flashcards-1)',
context: {
content: 'Conceitos de química molecular',
subject: 'química',
grade_level: 'médio',
widget_type: 'flashcards-1',
learning_intent: 'memorization'
},
expectedProperties: ['card_images']
},
{
name: 'Gallery Widget (gallery-1)',
context: {
content: 'Processo histórico da independência brasileira',
subject: 'história',
grade_level: 'fundamental',
widget_type: 'gallery-1',
learning_intent: 'exploration'
},
expectedProperties: ['slides']
}
];
for (const testCase of testCases) {
this.testResults.total++;
try {
const result = this.imageMapper.mapContentToImages(testCase.context);
// Validate widget mapping
const hasCorrectWidgetType = result.widget_type === testCase.context.widget_type;
const hasImageProperties = result.image_properties && Object.keys(result.image_properties).length > 0;
const hasEducationalMetadata = result.educational_metadata &&
result.educational_metadata.subject === testCase.context.subject;
// Check for expected properties
const hasExpectedProperties = testCase.expectedProperties.some(prop =>
result.image_properties.hasOwnProperty(prop)
);
if (hasCorrectWidgetType && hasImageProperties && hasEducationalMetadata && hasExpectedProperties) {
this.testResults.passed++;
this.testResults.details.push({
test: testCase.name,
status: 'PASSED',
message: `Correctly mapped images for ${testCase.context.widget_type}`,
data: {
widget_type: result.widget_type,
properties: Object.keys(result.image_properties),
metadata: result.educational_metadata
}
});
console.log(` ✅ ${testCase.name}: PASSED`);
} else {
throw new Error(`Invalid widget mapping for ${testCase.context.widget_type}`);
}
} catch (error) {
this.testResults.failed++;
this.testResults.details.push({
test: testCase.name,
status: 'FAILED',
message: error.message,
data: null
});
console.log(` ❌ ${testCase.name}: FAILED - ${error.message}`);
}
}
console.log('');
}
/**
* Test 3: Educational Context Awareness
*/
async testEducationalContextAwareness() {
console.log('📋 Test 3: Educational Context Awareness');
const testCases = [
{
name: 'Grade Level Adaptation - Elementary',
content: 'Plantas fazem fotossíntese',
subject: 'ciências',
grade_level: 'fundamental',
expectedComplexity: 'basic'
},
{
name: 'Grade Level Adaptation - High School',
content: 'O processo fotossintético envolve reações químicas complexas nos tilacoides',
subject: 'ciências',
grade_level: 'médio',
expectedComplexity: 'advanced'
},
{
name: 'Learning Intent - Assessment',
content: 'Questão sobre forças e movimento',
subject: 'física',
grade_level: 'médio',
learning_intent: 'assessment',
expectedImageType: 'quiz'
},
{
name: 'Learning Intent - Exploration',
content: 'Explore as diferentes fases da mitose',
subject: 'ciências',
grade_level: 'médio',
learning_intent: 'exploration',
expectedImageType: 'interactive'
}
];
for (const testCase of testCases) {
this.testResults.total++;
try {
const result = this.imageService.selectContextualImage(
testCase.content,
testCase.subject,
testCase.grade_level,
'image-1'
);
// Validate educational context awareness
const hasGradeLevelContext = result.educational_context.includes(testCase.grade_level);
const hasSubjectContext = result.educational_context.includes(testCase.subject);
const hasAppropriateCaption = result.caption.length > 10; // Reasonable caption length
const hasAccessibleAltText = result.alt_text.length > 5; // Accessible alt text
if (hasGradeLevelContext && hasSubjectContext && hasAppropriateCaption && hasAccessibleAltText) {
this.testResults.passed++;
this.testResults.details.push({
test: testCase.name,
status: 'PASSED',
message: `Context-aware image selection successful`,
data: {
context: result.educational_context,
caption: result.caption,
alt_text: result.alt_text
}
});
console.log(` ✅ ${testCase.name}: PASSED`);
} else {
throw new Error(`Insufficient educational context awareness`);
}
} catch (error) {
this.testResults.failed++;
this.testResults.details.push({
test: testCase.name,
status: 'FAILED',
message: error.message,
data: null
});
console.log(` ❌ ${testCase.name}: FAILED - ${error.message}`);
}
}
console.log('');
}
/**
* Test 4: Fallback Mechanisms
*/
async testFallbackMechanisms() {
console.log('📋 Test 4: Fallback Mechanisms');
const testCases = [
{
name: 'Unknown Subject Fallback',
content: 'Conteúdo sobre assunto desconhecido',
subject: 'assunto_inexistente',
expectedFallback: true
},
{
name: 'Unknown Topic Fallback',
content: 'Tópico completamente novo e desconhecido',
subject: 'física',
expectedFallback: true
},
{
name: 'Empty Content Fallback',
content: '',
subject: 'matemática',
expectedFallback: true
},
{
name: 'Invalid Grade Level Fallback',
content: 'Conteúdo educacional',
subject: 'história',
grade_level: 'nivel_inexistente',
expectedFallback: false
}
];
for (const testCase of testCases) {
this.testResults.total++;
try {
const result = this.imageService.selectContextualImage(
testCase.content,
testCase.subject,
testCase.grade_level || 'fundamental',
'image-1'
);
// Validate fallback mechanism
const isFallbackUsed = result.fallback_used === true;
const hasValidUrl = result.url && result.url.startsWith('http');
const hasValidCaption = result.caption && result.caption.length > 0;
const hasValidContext = result.educational_context && result.educational_context.length > 0;
// Check if fallback expectation matches result
const fallbackExpectationMet = testCase.expectedFallback === isFallbackUsed;
if (hasValidUrl && hasValidCaption && hasValidContext && (testCase.expectedFallback ? isFallbackUsed : true)) {
this.testResults.passed++;
this.testResults.details.push({
test: testCase.name,
status: 'PASSED',
message: `Fallback mechanism working correctly`,
data: {
fallback_used: result.fallback_used,
url: result.url,
context: result.educational_context
}
});
console.log(` ✅ ${testCase.name}: PASSED`);
} else {
throw new Error(`Fallback mechanism failed`);
}
} catch (error) {
this.testResults.failed++;
this.testResults.details.push({
test: testCase.name,
status: 'FAILED',
message: error.message,
data: null
});
console.log(` ❌ ${testCase.name}: FAILED - ${error.message}`);
}
}
console.log('');
}
/**
* Test 5: Phase 2 Assessment Integration
*/
async testAssessmentComponentIntegration() {
console.log('📋 Test 5: Phase 2 Assessment Component Integration');
const mockAssessmentData = {
quiz: {
metadata: {
topic: 'Movimento de Projéteis',
gradeLevel: 'médio',
subject: 'física'
},
questions: [
{ id: 1, question: 'Qual a trajetória de um projétil?', type: 'multiple_choice' },
{ id: 2, question: 'Como calcular o alcance máximo?', type: 'calculation' }
]
},
flashcards: [
'Trajetória parabólica',
'Velocidade inicial',
'Ângulo de lançamento',
'Gravidade terrestre'
],
metadata: {
gradeLevel: 'médio',
subject: 'física'
}
};
this.testResults.total++;
try {
const mappings = this.imageMapper.integrateWithAssessmentEngine(
mockAssessmentData,
'física'
);
// Validate assessment integration
const hasQuizMapping = mappings.some(m => m.widget_type === 'quiz-1');
const hasFlashcardMapping = mappings.some(m => m.widget_type === 'flashcards-1');
const allMappingsValid = mappings.every(m =>
m.image_properties &&
m.educational_metadata &&
m.educational_metadata.subject === 'física'
);
if (hasQuizMapping && hasFlashcardMapping && allMappingsValid) {
this.testResults.passed++;
this.testResults.details.push({
test: 'Assessment Integration',
status: 'PASSED',
message: `Successfully integrated with Phase 2 assessment components`,
data: {
mappings_count: mappings.length,
widget_types: mappings.map(m => m.widget_type)
}
});
console.log(` ✅ Assessment Integration: PASSED`);
} else {
throw new Error(`Failed to integrate with assessment components`);
}
} catch (error) {
this.testResults.failed++;
this.testResults.details.push({
test: 'Assessment Integration',
status: 'FAILED',
message: error.message,
data: null
});
console.log(` ❌ Assessment Integration: FAILED - ${error.message}`);
}
console.log('');
}
/**
* Test 6: Brazilian Educational Standards
*/
async testBrazilianEducationalStandards() {
console.log('📋 Test 6: Brazilian Educational Standards (BNCC)');
const brazilianTestCases = [
{
name: 'Portuguese Language Content',
content: 'Análise de obra literária brasileira: Machado de Assis',
subject: 'português',
grade_level: 'médio'
},
{
name: 'Brazilian History Content',
content: 'A colonização portuguesa no Brasil e suas consequências',
subject: 'história',
grade_level: 'fundamental'
},
{
name: 'Regional Science Content',
content: 'A biodiversidade da Amazônia brasileira',
subject: 'ciências',
grade_level: 'fundamental'
}
];
for (const testCase of brazilianTestCases) {
this.testResults.total++;
try {
const result = this.imageService.selectContextualImage(
testCase.content,
testCase.subject,
testCase.grade_level,
'image-1'
);
// Validate Brazilian educational context
const hasBrazilianContext = result.caption.includes('Brasil') ||
result.caption.includes('brasileiro') ||
result.educational_context.includes(testCase.subject);
const hasPortugueseCaption = /[áàâãéêíóôõúç]/.test(result.caption); // Check for Portuguese characters
const hasEducationalStructure = result.educational_context.split('/').length >= 3;
if (hasBrazilianContext && hasPortugueseCaption && hasEducationalStructure) {
this.testResults.passed++;
this.testResults.details.push({
test: testCase.name,
status: 'PASSED',
message: `BNCC compliance validated`,
data: {
caption: result.caption,
context: result.educational_context
}
});
console.log(` ✅ ${testCase.name}: PASSED`);
} else {
throw new Error(`BNCC compliance issues detected`);
}
} catch (error) {
this.testResults.failed++;
this.testResults.details.push({
test: testCase.name,
status: 'FAILED',
message: error.message,
data: null
});
console.log(` ❌ ${testCase.name}: FAILED - ${error.message}`);
}
}
console.log('');
}
/**
* Test 7: Performance and Quality Metrics
*/
async testPerformanceAndQuality() {
console.log('📋 Test 7: Performance and Quality Metrics');
const performanceTests = [
{
name: 'Image Selection Speed',
test: async () => {
const startTime = Date.now();
await this.imageService.selectContextualImage(
'Teste de performance para seleção de imagens',
'física',
'médio',
'image-1'
);
const endTime = Date.now();
return endTime - startTime;
},
threshold: 2000, // 2 seconds max
metric: 'milliseconds'
},
{
name: 'Widget Mapping Speed',
test: async () => {
const startTime = Date.now();
await this.imageMapper.mapContentToImages({
content: 'Teste de performance para mapeamento de widgets',
subject: 'química',
grade_level: 'médio',
widget_type: 'quiz-1',
learning_intent: 'assessment'
});
const endTime = Date.now();
return endTime - startTime;
},
threshold: 1000, // 1 second max
metric: 'milliseconds'
}
];
for (const perfTest of performanceTests) {
this.testResults.total++;
try {
const duration = await perfTest.test();
if (duration <= perfTest.threshold) {
this.testResults.passed++;
this.testResults.details.push({
test: perfTest.name,
status: 'PASSED',
message: `Performance within threshold: ${duration}${perfTest.metric}`,
data: { duration, threshold: perfTest.threshold }
});
console.log(` ✅ ${perfTest.name}: PASSED (${duration}ms)`);
} else {
throw new Error(`Performance threshold exceeded: ${duration}ms > ${perfTest.threshold}ms`);
}
} catch (error) {
this.testResults.failed++;
this.testResults.details.push({
test: perfTest.name,
status: 'FAILED',
message: error.message,
data: null
});
console.log(` ❌ ${perfTest.name}: FAILED - ${error.message}`);
}
}
console.log('');
}
/**
* Print test results summary
*/
printResults() {
console.log('📊 IMAGE SELECTION INTEGRATION TEST RESULTS');
console.log('=' .repeat(50));
console.log(`Total Tests: ${this.testResults.total}`);
console.log(`Passed: ${this.testResults.passed} ✅`);
console.log(`Failed: ${this.testResults.failed} ❌`);
console.log(`Success Rate: ${((this.testResults.passed / this.testResults.total) * 100).toFixed(1)}%`);
console.log('');
if (this.testResults.failed > 0) {
console.log('❌ FAILED TESTS:');
this.testResults.details
.filter(detail => detail.status === 'FAILED')
.forEach(detail => {
console.log(` - ${detail.test}: ${detail.message}`);
});
console.log('');
}
const successRate = (this.testResults.passed / this.testResults.total) * 100;
if (successRate >= 90) {
console.log('🎉 EXCELLENT: Image selection system ready for production!');
} else if (successRate >= 75) {
console.log('✅ GOOD: Image selection system functional with minor issues');
} else {
console.log('⚠️ WARNING: Image selection system needs significant improvements');
}
}
}
// Run tests if called directly
if (require.main === module) {
const tester = new ImageSelectionIntegrationTest();
tester.runIntegrationTests()
.then(results => {
process.exit(results.failed > 0 ? 1 : 0);
})
.catch(error => {
console.error('Test execution failed:', error);
process.exit(1);
});
}
module.exports = { ImageSelectionIntegrationTest };