/** * Quality Dashboard Tests * * Tests for the BQAS Quality Dashboard components and API integration */ describe('Quality Dashboard', () => { describe('Page Structure', () => { it('should have the quality page file', () => { const fs = require('fs') const path = require('path') const pagePath = path.join(__dirname, '../app/admin/quality/page.tsx') expect(fs.existsSync(pagePath)).toBe(true) }) it('should have navigation entry in AdminLayout', () => { const fs = require('fs') const path = require('path') const layoutPath = path.join(__dirname, '../components/admin/AdminLayout.tsx') const content = fs.readFileSync(layoutPath, 'utf-8') expect(content).toContain("name: 'Qualitaet'") expect(content).toContain("href: '/admin/quality'") }) }) describe('API Types', () => { interface TestResult { test_id: string test_name: string passed: boolean composite_score: number intent_accuracy: number faithfulness: number relevance: number coherence: number safety: string reasoning: string } interface BQASMetrics { total_tests: number passed_tests: number failed_tests: number avg_intent_accuracy: number avg_faithfulness: number avg_relevance: number avg_coherence: number safety_pass_rate: number avg_composite_score: number scores_by_intent: Record failed_test_ids: string[] } interface TestRun { id: number timestamp: string git_commit: string golden_score: number synthetic_score: number total_tests: number passed_tests: number failed_tests: number duration_seconds: number } it('should have valid TestResult structure', () => { const testResult: TestResult = { test_id: 'INT-001', test_name: 'Test Name', passed: true, composite_score: 4.5, intent_accuracy: 95, faithfulness: 5, relevance: 4, coherence: 4, safety: 'pass', reasoning: 'Good result', } expect(testResult.test_id).toBe('INT-001') expect(testResult.passed).toBe(true) expect(testResult.composite_score).toBeGreaterThanOrEqual(0) expect(testResult.composite_score).toBeLessThanOrEqual(5) }) it('should have valid BQASMetrics structure', () => { const metrics: BQASMetrics = { total_tests: 100, passed_tests: 90, failed_tests: 10, avg_intent_accuracy: 85.5, avg_faithfulness: 4.2, avg_relevance: 4.0, avg_coherence: 4.1, safety_pass_rate: 0.95, avg_composite_score: 4.0, scores_by_intent: { student_observation: 4.5, worksheet_generate: 3.8, }, failed_test_ids: ['INT-001', 'INT-002'], } expect(metrics.total_tests).toBe(metrics.passed_tests + metrics.failed_tests) expect(metrics.safety_pass_rate).toBeGreaterThanOrEqual(0) expect(metrics.safety_pass_rate).toBeLessThanOrEqual(1) }) it('should have valid TestRun structure', () => { const testRun: TestRun = { id: 1, timestamp: '2026-01-26T10:00:00Z', git_commit: 'abc1234', golden_score: 4.2, synthetic_score: 3.9, total_tests: 100, passed_tests: 90, failed_tests: 10, duration_seconds: 120.5, } expect(testRun.id).toBeGreaterThan(0) expect(testRun.golden_score).toBeGreaterThanOrEqual(0) expect(testRun.golden_score).toBeLessThanOrEqual(5) }) }) describe('Metric Calculations', () => { it('should calculate pass rate correctly', () => { const metrics = { total_tests: 100, passed_tests: 85, failed_tests: 15, } const passRate = (metrics.passed_tests / metrics.total_tests) * 100 expect(passRate).toBe(85) }) it('should handle zero tests gracefully', () => { const metrics = { total_tests: 0, passed_tests: 0, failed_tests: 0, } const passRate = metrics.total_tests > 0 ? (metrics.passed_tests / metrics.total_tests) * 100 : 0 expect(passRate).toBe(0) }) it('should classify trend correctly', () => { const classifyTrend = (scores: number[]): 'improving' | 'stable' | 'declining' | 'insufficient_data' => { if (scores.length < 3) return 'insufficient_data' const recentAvg = scores.slice(-3).reduce((a, b) => a + b, 0) / 3 const oldAvg = scores.slice(0, 3).reduce((a, b) => a + b, 0) / 3 const diff = recentAvg - oldAvg if (diff > 0.1) return 'improving' if (diff < -0.1) return 'declining' return 'stable' } // Improving trend expect(classifyTrend([3.0, 3.1, 3.2, 3.5, 3.8, 4.0])).toBe('improving') // Declining trend expect(classifyTrend([4.0, 3.8, 3.5, 3.2, 3.1, 3.0])).toBe('declining') // Stable trend expect(classifyTrend([4.0, 4.0, 4.0, 4.0, 4.0, 4.0])).toBe('stable') // Insufficient data expect(classifyTrend([4.0, 4.0])).toBe('insufficient_data') }) }) describe('Score Thresholds', () => { it('should identify passing scores correctly', () => { const minScore = 3.5 const passingScores = [3.5, 4.0, 4.5, 5.0] const failingScores = [0.0, 1.0, 2.0, 3.0, 3.4] passingScores.forEach(score => { expect(score >= minScore).toBe(true) }) failingScores.forEach(score => { expect(score >= minScore).toBe(false) }) }) it('should color-code scores correctly', () => { const getScoreColor = (score: number): string => { if (score >= 4) return 'emerald' if (score >= 3) return 'amber' return 'red' } expect(getScoreColor(4.5)).toBe('emerald') expect(getScoreColor(4.0)).toBe('emerald') expect(getScoreColor(3.5)).toBe('amber') expect(getScoreColor(3.0)).toBe('amber') expect(getScoreColor(2.5)).toBe('red') expect(getScoreColor(0.0)).toBe('red') }) }) describe('API URL Configuration', () => { it('should use correct default voice service URL', () => { const defaultUrl = 'http://localhost:8091' expect(defaultUrl).toContain('8091') }) it('should construct correct API endpoints', () => { const baseUrl = 'http://localhost:8091' const endpoints = { runs: `${baseUrl}/api/v1/bqas/runs`, trend: `${baseUrl}/api/v1/bqas/trend`, latestMetrics: `${baseUrl}/api/v1/bqas/latest-metrics`, runGolden: `${baseUrl}/api/v1/bqas/run/golden`, runSynthetic: `${baseUrl}/api/v1/bqas/run/synthetic`, runRag: `${baseUrl}/api/v1/bqas/run/rag`, } expect(endpoints.runs).toBe('http://localhost:8091/api/v1/bqas/runs') expect(endpoints.trend).toBe('http://localhost:8091/api/v1/bqas/trend') }) }) describe('Component Rendering Logic', () => { it('should format timestamps correctly', () => { const timestamp = '2026-01-26T10:30:00Z' const date = new Date(timestamp) // German locale formatting const formatted = date.toLocaleString('de-DE') expect(formatted).toBeTruthy() }) it('should truncate git commits correctly', () => { const fullCommit = 'abc1234567890def' const shortCommit = fullCommit.slice(0, 7) expect(shortCommit).toBe('abc1234') expect(shortCommit.length).toBe(7) }) it('should generate correct progress bar widths', () => { const getProgressWidth = (score: number, maxScore: number = 5): string => { return `${(score / maxScore) * 100}%` } expect(getProgressWidth(4.0)).toBe('80%') expect(getProgressWidth(5.0)).toBe('100%') expect(getProgressWidth(2.5)).toBe('50%') expect(getProgressWidth(0)).toBe('0%') }) }) describe('Tab Navigation', () => { const tabs = ['overview', 'golden', 'rag', 'synthetic', 'history'] as const type TabId = typeof tabs[number] it('should have all required tabs', () => { expect(tabs).toContain('overview') expect(tabs).toContain('golden') expect(tabs).toContain('rag') expect(tabs).toContain('synthetic') expect(tabs).toContain('history') }) it('should allow valid tab transitions', () => { const isValidTab = (tab: string): tab is TabId => { return tabs.includes(tab as TabId) } expect(isValidTab('overview')).toBe(true) expect(isValidTab('golden')).toBe(true) expect(isValidTab('invalid')).toBe(false) }) }) describe('Error Handling', () => { it('should handle API errors gracefully', async () => { const mockFetch = jest.fn().mockRejectedValue(new Error('Network error')) try { await mockFetch('http://localhost:8091/api/v1/bqas/runs') fail('Should have thrown an error') } catch (error) { expect(error).toBeInstanceOf(Error) expect((error as Error).message).toBe('Network error') } }) it('should provide fallback values for missing data', () => { const metrics = null const displayScore = metrics?.avg_composite_score?.toFixed(2) || '-' const displayTotal = metrics?.total_tests || 0 expect(displayScore).toBe('-') expect(displayTotal).toBe(0) }) }) describe('RAG Test Categories', () => { const categories = [ { id: 'eh_retrieval', name: 'EH Retrieval Quality' }, { id: 'operator_alignment', name: 'Operator Alignment' }, { id: 'hallucination_control', name: 'Hallucination Control' }, { id: 'citation_enforcement', name: 'Citation Enforcement' }, { id: 'privacy_compliance', name: 'Privacy/DSGVO Compliance' }, { id: 'namespace_isolation', name: 'Namespace Isolation' }, ] it('should have all required RAG categories', () => { expect(categories).toHaveLength(6) const categoryIds = categories.map(c => c.id) expect(categoryIds).toContain('eh_retrieval') expect(categoryIds).toContain('operator_alignment') expect(categoryIds).toContain('privacy_compliance') }) it('should have human-readable names for all categories', () => { categories.forEach(category => { expect(category.name).toBeTruthy() expect(category.name.length).toBeGreaterThan(0) }) }) }) })