'use client' /** * Test Dashboard - Zentrales Test-Registry * * Aggregiert alle 280+ Tests aus allen Services: * - Go Unit Tests (~57) * - Python Tests (~50) * - BQAS Golden (97) * - BQAS RAG (~20) * - TypeScript Jest (~8) * - SDK Vitest Unit Tests (~43) * - SDK Playwright E2E (~25) * - E2E Playwright (~5) */ import React, { useState, useEffect, useCallback, useRef } from 'react' import Link from 'next/link' import { PagePurpose } from '@/components/common/PagePurpose' import { DevOpsPipelineSidebarResponsive } from '@/components/infrastructure/DevOpsPipelineSidebar' import type { LLMRoutingOption } from '@/types/infrastructure-modules' import type { ServiceTestInfo, TestRegistryStats, TestRun, CoverageData, TabType, Toast, FailedTest, BacklogItem, BacklogPriority, BacklogStatus, TrendDataPoint, } from './types' // API Configuration const API_BASE = '/api/tests' // ============================================================================== // Toast Notification Component // ============================================================================== function ToastContainer({ toasts, onDismiss }: { toasts: Toast[]; onDismiss: (id: number) => void }) { return (
{toasts.map((toast) => (
{toast.type === 'loading' ? ( ) : toast.type === 'success' ? ( ) : toast.type === 'error' ? ( ) : ( )} {toast.message} {toast.type !== 'loading' && ( )}
))}
) } // ============================================================================== // Helper Components // ============================================================================== function MetricCard({ title, value, subtitle, trend, color = 'blue', }: { title: string value: string | number subtitle?: string trend?: 'up' | 'down' | 'stable' color?: 'blue' | 'green' | 'red' | 'yellow' | 'orange' | 'purple' }) { const colorClasses = { blue: 'bg-blue-50 border-blue-200', green: 'bg-emerald-50 border-emerald-200', red: 'bg-red-50 border-red-200', yellow: 'bg-amber-50 border-amber-200', orange: 'bg-orange-50 border-orange-200', purple: 'bg-purple-50 border-purple-200', } const trendIcons = { up: ( ), down: ( ), stable: ( ), } return (

{title}

{value}

{subtitle &&

{subtitle}

}
{trend &&
{trendIcons[trend]}
}
) } function ServiceTestCard({ service, onRun, isRunning, progress, }: { service: ServiceTestInfo onRun: (service: string) => void isRunning: boolean progress?: { current_file: string files_done: number files_total: number passed: number failed: number status: string } }) { const passRate = service.total_tests > 0 ? (service.passed_tests / service.total_tests) * 100 : 0 const getLanguageIcon = (lang: string) => { switch (lang) { case 'go': return '🐹' case 'python': return '🐍' case 'typescript': return '📘' case 'mixed': return '🔀' default: return '📦' } } const getStatusColor = (status: string) => { switch (status) { case 'passed': return 'bg-emerald-100 text-emerald-700' case 'failed': return 'bg-red-100 text-red-700' case 'running': return 'bg-blue-100 text-blue-700' default: return 'bg-slate-100 text-slate-700' } } return (
{getLanguageIcon(service.language)}

{service.display_name}

{service.port ? `Port ${service.port}` : 'Library'} • {service.language}

{service.status === 'passed' ? 'Bestanden' : service.status === 'failed' ? 'Fehler' : 'Ausstehend'}
Pass Rate {passRate.toFixed(0)}%
= 80 ? 'bg-emerald-500' : passRate >= 60 ? 'bg-amber-500' : 'bg-red-500' }`} style={{ width: `${passRate}%` }} />

{service.total_tests}

Tests

{service.passed_tests}

Bestanden

{service.failed_tests}

Fehler

{service.coverage_percent && (
Coverage = 70 ? 'text-emerald-600' : 'text-amber-600'}`}> {service.coverage_percent.toFixed(1)}%
)} {/* Progress-Anzeige wenn Tests laufen */} {isRunning && progress && progress.status === 'running' && (
{progress.current_file || 'Starte...'} {progress.files_done}/{progress.files_total} Dateien
0 ? (progress.files_done / progress.files_total) * 100 : 0}%` }} />
{progress.passed} bestanden {progress.failed} fehler
)}
) } function CoverageChart({ data }: { data: CoverageData[] }) { if (data.length === 0) { return (
Keine Coverage-Daten verfuegbar
) } const sortedData = [...data].sort((a, b) => b.coverage_percent - a.coverage_percent) return (
{sortedData.map((item) => (
{item.display_name} = 80 ? 'text-emerald-600' : item.coverage_percent >= 60 ? 'text-amber-600' : 'text-red-600' }`} > {item.coverage_percent.toFixed(1)}%
= 80 ? 'bg-emerald-500' : item.coverage_percent >= 60 ? 'bg-amber-500' : 'bg-red-500' }`} style={{ width: `${item.coverage_percent}%` }} />
))}
) } function FrameworkDistribution({ data }: { data: Record }) { const total = Object.values(data).reduce((a, b) => a + b, 0) if (total === 0) return null const frameworkLabels: Record = { go_test: 'Go Tests', pytest: 'Python (pytest)', jest: 'Jest (TS)', vitest: 'Vitest (SDK)', playwright: 'Playwright (E2E)', bqas_golden: 'BQAS Golden', bqas_rag: 'BQAS RAG', bqas_synthetic: 'BQAS Synthetic', } const frameworkColors: Record = { go_test: 'bg-cyan-500', pytest: 'bg-yellow-500', jest: 'bg-blue-500', vitest: 'bg-orange-500', playwright: 'bg-purple-500', bqas_golden: 'bg-emerald-500', bqas_rag: 'bg-teal-500', bqas_synthetic: 'bg-amber-500', } return (
{Object.entries(data) .sort((a, b) => b[1] - a[1]) .map(([framework, count]) => (
{frameworkLabels[framework] || framework} {count} ({((count / total) * 100).toFixed(0)}%)
))}
) } function TestRunsTable({ runs }: { runs: TestRun[] }) { if (runs.length === 0) { return (
Keine Test-Laeufe vorhanden
) } return (
{runs.map((run) => ( ))}
ID Service Zeitpunkt Tests Bestanden Dauer Status
{run.id.slice(-8)} {run.service} {new Date(run.started_at).toLocaleString('de-DE')} {run.total_tests} {run.passed_tests} / {run.failed_tests} {run.duration_seconds.toFixed(1)}s {run.status}
) } // ============================================================================== // Guide Tab // ============================================================================== function GuideTab() { return (

Was ist das Test Dashboard?

Das Test Dashboard ist die zentrale Uebersicht fuer alle 260+ Tests im Breakpilot-System. Es aggregiert Tests aus verschiedenen Services (Go, Python, TypeScript) ohne diese physisch zu migrieren. Tests bleiben an ihren konventionellen Orten, werden aber hier zentral ueberwacht und ausgefuehrt. Seit 2026-02 inklusive AI Compliance SDK Unit Tests (Vitest) und E2E Tests (Playwright).

Test-Kategorien

🐹

Go Unit Tests (~57)

consent-service, billing-service, school-service, edu-search-service, ai-compliance-sdk

🐍

Python Tests (~50)

backend, voice-service, klausur-service, geo-service

🎯

BQAS Golden (97)

Validierte Referenz-Tests mit LLM-Judge fuer Intent-Erkennung

📚

BQAS RAG (~20)

RAG-Judge Tests fuer Retrieval, Citations, Hallucination-Control

📘

TypeScript Jest (~8)

Website Unit Tests fuer React-Komponenten

SDK Vitest (~43)

AI Compliance SDK Unit Tests: Types, Export, Components, Reducer

🎭

SDK Playwright (~25)

SDK E2E Tests: Navigation, Workflow, Command Bar, Export

🌐

Website E2E (~5)

End-to-End Tests fuer kritische User Flows

🔗

Integration Tests (~15)

Docker Compose basierte E2E-Tests mit Backend, Consent-Service, DB

Architektur

{`┌────────────────────────────────────────────────────────────────────┐
│               Admin-v2 Test Dashboard                               │
│               /infrastructure/tests                                 │
├────────────────────────────────────────────────────────────────────┤
│  ┌────────────┐  ┌────────────┐  ┌────────────┐  ┌─────────────┐  │
│  │ Unit Tests │  │ SDK Tests  │  │   BQAS     │  │ E2E Tests   │  │
│  │  (Go, Py)  │  │  (Vitest)  │  │ (LLM/RAG)  │  │ (Playwright)│  │
│  └────────────┘  └────────────┘  └────────────┘  └─────────────┘  │
│        │               │               │               │           │
│        ▼               ▼               ▼               ▼           │
│  ┌──────────────────────────────────────────────────────────────┐ │
│  │                    Test Registry API                          │ │
│  │              /backend/api/tests/registry.py                   │ │
│  └──────────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────────┘

Tests bleiben wo sie sind:
- /consent-service/internal/**/*_test.go
- /backend/tests/test_*.py
- /voice-service/tests/bqas/
- /admin-v2/components/sdk/__tests__/*.test.ts   (Vitest)
- /admin-v2/e2e/specs/*.spec.ts                  (Playwright)`}
        
{/* CI/CD Workflow Anleitung */}

CI/CD Integration

🤖 Automatisch (bei jedem Push/PR)

  • Unit Tests - Go & Python Tests laufen automatisch
  • Test-Ergebnisse - Werden ans Dashboard gesendet
  • Backlog - Fehlgeschlagene Tests erscheinen hier
  • Linting - Code-Qualitaet bei PRs pruefen

👆 Manuell (Button oder Tag)

  • Docker Builds - Container erstellen
  • SBOM/Scans - Sicherheitsanalyse ausfuehren
  • Deployment - In Produktion deployen
  • Pipeline starten - Im CI/CD Dashboard

Daten-Fluss: Woodpecker CI → POST /api/tests/ci-result → PostgreSQL → Test Dashboard

BQAS Dashboard

Detaillierte BQAS-Metriken und Trend-Analyse

CI/CD Pipelines

Gitea Actions und automatische Test-Planung

) } // ============================================================================== // Backlog Component // ============================================================================== function FailedTestCard({ test, onStatusChange, onPriorityChange, priority = 'medium', failureCount = 1, }: { test: FailedTest onStatusChange: (testId: string, status: string) => void onPriorityChange?: (testId: string, priority: string) => void priority?: BacklogPriority failureCount?: number }) { const errorTypeColors: Record = { assertion: 'bg-amber-100 text-amber-700', nil_pointer: 'bg-red-100 text-red-700', type_error: 'bg-purple-100 text-purple-700', network: 'bg-blue-100 text-blue-700', timeout: 'bg-orange-100 text-orange-700', logic_error: 'bg-slate-100 text-slate-700', unknown: 'bg-slate-100 text-slate-700', } const statusColors: Record = { open: 'bg-red-100 text-red-700', in_progress: 'bg-blue-100 text-blue-700', fixed: 'bg-emerald-100 text-emerald-700', wont_fix: 'bg-slate-100 text-slate-700', flaky: 'bg-purple-100 text-purple-700', } const priorityColors: Record = { critical: 'bg-red-500 text-white', high: 'bg-orange-500 text-white', medium: 'bg-yellow-500 text-white', low: 'bg-slate-400 text-white', } const priorityLabels: Record = { critical: '!!! Kritisch', high: '!! Hoch', medium: '! Mittel', low: 'Niedrig', } return (
{priorityLabels[priority]} {test.error_type.replace('_', ' ')} {test.service} {failureCount > 1 && ( {failureCount}x fehlgeschlagen )}

{test.name}

{test.file_path}

{onPriorityChange && ( )}

Fehlermeldung:

{test.error_message || 'Keine Details verfuegbar'}

{test.suggestion && (

💡 Loesungsvorschlag:

{test.suggestion}

)}
Zuletzt fehlgeschlagen: {test.last_failed ? new Date(test.last_failed).toLocaleString('de-DE') : 'Unbekannt'}
) } function BacklogTab({ failedTests, onStatusChange, onPriorityChange, isLoading, backlogItems, usePostgres = false, }: { failedTests: FailedTest[] onStatusChange: (testId: string, status: string) => void onPriorityChange?: (testId: string, priority: string) => void isLoading: boolean backlogItems?: BacklogItem[] usePostgres?: boolean }) { const [filterStatus, setFilterStatus] = useState('open') const [filterService, setFilterService] = useState('all') const [filterPriority, setFilterPriority] = useState('all') const [llmAutoAnalysis, setLlmAutoAnalysis] = useState(true) const [llmRouting, setLlmRouting] = useState('smart_routing') // Nutze PostgreSQL-Backlog wenn verfuegbar, sonst Legacy const items = usePostgres && backlogItems ? backlogItems : failedTests // Gruppiere nach Service const services = [...new Set(items.map(t => 'service' in t ? t.service : (t as BacklogItem).service))] // Filtere Items const filteredItems = items.filter(item => { const status = 'status' in item ? item.status : 'open' const service = 'service' in item ? item.service : '' const priority = 'priority' in item ? (item as BacklogItem).priority : 'medium' if (filterStatus !== 'all' && status !== filterStatus) return false if (filterService !== 'all' && service !== filterService) return false if (filterPriority !== 'all' && priority !== filterPriority) return false return true }) // Zaehle nach Status const openCount = items.filter(t => t.status === 'open').length const inProgressCount = items.filter(t => t.status === 'in_progress').length const fixedCount = items.filter(t => t.status === 'fixed').length const flakyCount = items.filter(t => t.status === 'flaky').length // Zaehle nach Prioritaet (nur bei PostgreSQL) const criticalCount = backlogItems?.filter(t => t.priority === 'critical').length || 0 const highCount = backlogItems?.filter(t => t.priority === 'high').length || 0 if (isLoading) { return (
) } // Konvertiere BacklogItem zu FailedTest fuer die Anzeige const convertToFailedTest = (item: BacklogItem): FailedTest => ({ id: String(item.id), name: item.test_name, service: item.service, file_path: item.test_file || '', error_message: item.error_message || '', error_type: item.error_type || 'unknown', suggestion: item.fix_suggestion || '', run_id: '', last_failed: item.last_failed_at, status: item.status, }) return (
{/* Stats */}

{openCount}

Offene Fehler

{inProgressCount}

In Arbeit

{fixedCount}

Behoben

{flakyCount}

Flaky

{usePostgres && criticalCount + highCount > 0 && (

{criticalCount + highCount}

Kritisch/Hoch

)}
{/* PostgreSQL Badge */} {usePostgres && (
Persistente Speicherung aktiv (PostgreSQL)
)} {/* LLM Analysis Toggle */}

Automatische LLM-Analyse

KI-gestuetzte Fix-Vorschlaege fuer Backlog-Eintraege

{llmAutoAnalysis && (

LLM-Routing Strategie:

{llmRouting === 'local_only' && 'Alle Analysen werden mit Qwen2.5-32B lokal durchgefuehrt. Keine Daten verlassen den Server.'} {llmRouting === 'claude_preferred' && 'Verwendet Claude fuer beste Fix-Qualitaet. Nur Code-Snippets werden uebertragen.'} {llmRouting === 'smart_routing' && 'Privacy Classifier entscheidet automatisch: Sensitive Daten → lokal, Code → Claude.'}

)}
{/* Filter */}
{usePostgres && (
)}
{filteredItems.length} von {items.length} Tests angezeigt
{/* Test-Liste */} {filteredItems.length === 0 ? (

{filterStatus === 'open' ? 'Keine offenen Fehler! 🎉' : 'Keine Tests mit diesem Filter gefunden.'}

{filterStatus === 'open' && (

Alle Tests bestanden. Bereit fuer Go-Live!

)}
) : (
{filteredItems.map((item) => { const test = usePostgres && 'test_name' in item ? convertToFailedTest(item as BacklogItem) : item as FailedTest const priority = usePostgres && 'priority' in item ? (item as BacklogItem).priority : 'medium' const failureCount = usePostgres && 'failure_count' in item ? (item as BacklogItem).failure_count : 1 return ( ) })}
)} {/* Info */}

Workflow fuer fehlgeschlagene Tests:

  1. Markiere den Test als "In Arbeit" wenn du daran arbeitest
  2. Analysiere die Fehlermeldung und den Loesungsvorschlag
  3. Behebe den Fehler im Code
  4. Fuehre den Test erneut aus (Button im Service-Tab)
  5. Markiere als "Behoben" wenn der Test besteht
  6. {usePostgres &&
  7. Setze "Flaky" fuer sporadisch fehlschlagende Tests
  8. }
) } // ============================================================================== // Main Component // ============================================================================== export default function TestDashboardPage() { const [activeTab, setActiveTab] = useState('overview') const [isLoading, setIsLoading] = useState(true) const [error, setError] = useState(null) // Toast state const [toasts, setToasts] = useState([]) const toastIdRef = useRef(0) const addToast = useCallback((type: Toast['type'], message: string) => { const id = ++toastIdRef.current setToasts((prev) => [...prev, { id, type, message }]) if (type !== 'loading') { setTimeout(() => { setToasts((prev) => prev.filter((t) => t.id !== id)) }, 5000) } return id }, []) const removeToast = useCallback((id: number) => { setToasts((prev) => prev.filter((t) => t.id !== id)) }, []) const updateToast = useCallback((id: number, type: Toast['type'], message: string) => { setToasts((prev) => prev.map((t) => (t.id === id ? { ...t, type, message } : t))) if (type !== 'loading') { setTimeout(() => { setToasts((prev) => prev.filter((t) => t.id !== id)) }, 5000) } }, []) // Data states const [services, setServices] = useState([]) const [stats, setStats] = useState(null) const [coverage, setCoverage] = useState([]) const [testRuns, setTestRuns] = useState([]) const [failedTests, setFailedTests] = useState([]) const [backlogItems, setBacklogItems] = useState([]) const [usePostgres, setUsePostgres] = useState(false) // Running states const [runningServices, setRunningServices] = useState>(new Set()) // Progress states fuer laufende Tests const [serviceProgress, setServiceProgress] = useState>({}) // Demo data for when API is not available const DEMO_SERVICES: ServiceTestInfo[] = [ { service: 'consent-service', display_name: 'Consent Service', port: 8081, language: 'go', total_tests: 22, passed_tests: 20, failed_tests: 2, skipped_tests: 0, pass_rate: 90.9, coverage_percent: 82.3, last_run: new Date().toISOString(), status: 'failed' }, { service: 'backend', display_name: 'Python Backend', port: 8000, language: 'python', total_tests: 40, passed_tests: 38, failed_tests: 2, skipped_tests: 0, pass_rate: 95.0, coverage_percent: 75.1, last_run: new Date().toISOString(), status: 'failed' }, { service: 'voice-service', display_name: 'Voice Service', port: 8091, language: 'python', total_tests: 5, passed_tests: 5, failed_tests: 0, skipped_tests: 0, pass_rate: 100, coverage_percent: 68.9, last_run: new Date().toISOString(), status: 'passed' }, { service: 'bqas-golden', display_name: 'BQAS Golden Suite', port: 8091, language: 'python', total_tests: 97, passed_tests: 89, failed_tests: 8, skipped_tests: 0, pass_rate: 91.7, coverage_percent: undefined, last_run: new Date().toISOString(), status: 'failed' }, { service: 'bqas-rag', display_name: 'BQAS RAG Tests', port: 8091, language: 'python', total_tests: 20, passed_tests: 18, failed_tests: 2, skipped_tests: 0, pass_rate: 90.0, coverage_percent: undefined, last_run: new Date().toISOString(), status: 'failed' }, { service: 'klausur-service', display_name: 'Klausur Service', port: 8086, language: 'python', total_tests: 8, passed_tests: 8, failed_tests: 0, skipped_tests: 0, pass_rate: 100, coverage_percent: 71.2, last_run: new Date().toISOString(), status: 'passed' }, { service: 'billing-service', display_name: 'Billing Service', port: 8082, language: 'go', total_tests: 5, passed_tests: 5, failed_tests: 0, skipped_tests: 0, pass_rate: 100, coverage_percent: 78.5, last_run: new Date().toISOString(), status: 'passed' }, { service: 'school-service', display_name: 'School Service', port: 8084, language: 'go', total_tests: 6, passed_tests: 6, failed_tests: 0, skipped_tests: 0, pass_rate: 100, coverage_percent: 81.4, last_run: new Date().toISOString(), status: 'passed' }, { service: 'sdk-unit', display_name: 'SDK Unit Tests (Vitest)', port: undefined, language: 'typescript', total_tests: 43, passed_tests: 43, failed_tests: 0, skipped_tests: 0, pass_rate: 100, coverage_percent: 85.2, last_run: new Date().toISOString(), status: 'passed' }, { service: 'sdk-e2e', display_name: 'SDK E2E Tests (Playwright)', port: undefined, language: 'typescript', total_tests: 25, passed_tests: 25, failed_tests: 0, skipped_tests: 0, pass_rate: 100, coverage_percent: undefined, last_run: new Date().toISOString(), status: 'passed' }, { service: 'integration-tests', display_name: 'Integration Tests', port: undefined, language: 'python', total_tests: 15, passed_tests: 15, failed_tests: 0, skipped_tests: 0, pass_rate: 100, coverage_percent: undefined, last_run: new Date().toISOString(), status: 'passed' }, ] const DEMO_STATS: TestRegistryStats = { total_tests: 278, total_passed: 263, total_failed: 15, total_skipped: 0, overall_pass_rate: 94.6, average_coverage: 78.5, services_count: 11, by_category: { unit: 118, bqas: 117, e2e: 30, integration: 15 }, by_framework: { go_test: 57, pytest: 68, bqas_golden: 97, bqas_rag: 20, jest: 8, vitest: 43, playwright: 30 }, } // Fetch data const fetchData = useCallback(async () => { setIsLoading(true) setError(null) try { const registryResponse = await fetch(`${API_BASE}/registry`) if (registryResponse.ok) { const data = await registryResponse.json() setServices(data.services || DEMO_SERVICES) setStats(data.stats || DEMO_STATS) } else { setServices(DEMO_SERVICES) setStats(DEMO_STATS) } const coverageResponse = await fetch(`${API_BASE}/coverage`) if (coverageResponse.ok) { const data = await coverageResponse.json() setCoverage(data.services || []) } else { setCoverage(DEMO_SERVICES.filter(s => s.coverage_percent).map(s => ({ service: s.service, display_name: s.display_name, coverage_percent: s.coverage_percent!, language: s.language, }))) } const runsResponse = await fetch(`${API_BASE}/runs`) if (runsResponse.ok) { const data = await runsResponse.json() setTestRuns(data.runs || []) } // Lade fehlgeschlagene Tests fuer Backlog const failedResponse = await fetch(`${API_BASE}/failed`) if (failedResponse.ok) { const data = await failedResponse.json() setFailedTests(data.tests || []) } // Versuche PostgreSQL-Backlog zu laden (neue API) try { const backlogResponse = await fetch(`${API_BASE}/backlog`) if (backlogResponse.ok) { const data = await backlogResponse.json() if (data.items && data.items.length > 0) { setBacklogItems(data.items) setUsePostgres(true) } } } catch { // PostgreSQL nicht verfuegbar, nutze Legacy setUsePostgres(false) } } catch (err) { console.error('Failed to fetch test registry data:', err) setServices(DEMO_SERVICES) setStats(DEMO_STATS) setCoverage(DEMO_SERVICES.filter(s => s.coverage_percent).map(s => ({ service: s.service, display_name: s.display_name, coverage_percent: s.coverage_percent!, language: s.language, }))) } finally { setIsLoading(false) } }, []) useEffect(() => { fetchData() }, [fetchData]) // Update failed test status const updateTestStatus = async (testId: string, status: string) => { try { // Nutze PostgreSQL-Endpoint wenn verfuegbar const endpoint = usePostgres ? `${API_BASE}/backlog/${testId}/status` : `${API_BASE}/failed/${encodeURIComponent(testId)}/status?status=${status}` const response = await fetch(endpoint, { method: 'POST', headers: usePostgres ? { 'Content-Type': 'application/json' } : undefined, body: usePostgres ? JSON.stringify({ status }) : undefined, }) if (response.ok) { // Aktualisiere lokalen State if (usePostgres) { setBacklogItems(prev => prev.map(t => String(t.id) === testId ? { ...t, status: status as any } : t) ) } setFailedTests(prev => prev.map(t => t.id === testId ? { ...t, status: status as any } : t) ) addToast('success', `Test-Status auf "${status}" gesetzt`) } } catch (err) { console.error('Failed to update test status:', err) // Trotzdem lokal aktualisieren fuer bessere UX setFailedTests(prev => prev.map(t => t.id === testId ? { ...t, status: status as any } : t) ) if (usePostgres) { setBacklogItems(prev => prev.map(t => String(t.id) === testId ? { ...t, status: status as any } : t) ) } } } // Update failed test priority (nur PostgreSQL) const updateTestPriority = async (testId: string, priority: string) => { if (!usePostgres) return try { const response = await fetch(`${API_BASE}/backlog/${testId}/priority`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ priority }), }) if (response.ok) { setBacklogItems(prev => prev.map(t => String(t.id) === testId ? { ...t, priority: priority as any } : t) ) addToast('success', `Prioritaet auf "${priority}" gesetzt`) } } catch (err) { console.error('Failed to update test priority:', err) // Trotzdem lokal aktualisieren setBacklogItems(prev => prev.map(t => String(t.id) === testId ? { ...t, priority: priority as any } : t) ) } } // Run tests mit Progress-Polling const runTests = async (service: string) => { setRunningServices((prev) => new Set(prev).add(service)) const loadingToast = addToast('loading', `Tests fuer ${service} werden gestartet...`) // Progress-Polling starten let pollInterval: NodeJS.Timeout | null = null const pollProgress = async () => { try { const progressResponse = await fetch(`${API_BASE}/progress/${service}`) if (progressResponse.ok) { const progress = await progressResponse.json() setServiceProgress((prev) => ({ ...prev, [service]: progress, })) // Toast-Message mit aktuellem Fortschritt aktualisieren if (progress.status === 'running' && progress.files_total > 0) { const percent = Math.round((progress.files_done / progress.files_total) * 100) const toastMsg = `${service}: ${progress.current_file} (${progress.passed} bestanden, ${progress.failed} fehler)` updateToast(loadingToast, 'loading', toastMsg) } } } catch (err) { // Ignore polling errors } } // Start polling (alle 1 Sekunde) pollInterval = setInterval(pollProgress, 1000) try { const response = await fetch(`${API_BASE}/run/${service}`, { method: 'POST', }) if (response.ok) { const result = await response.json() // Warte kurz und pruefe finalen Progress await new Promise(resolve => setTimeout(resolve, 500)) await pollProgress() const finalProgress = serviceProgress[service] const passedMsg = finalProgress ? `${finalProgress.passed} bestanden, ${finalProgress.failed} fehler` : 'abgeschlossen' updateToast(loadingToast, 'success', `${service}: Tests ${passedMsg}`) await fetchData() } else { updateToast(loadingToast, 'info', `${service}: Demo-Modus (API nicht verfuegbar)`) } } catch (err) { console.error('Failed to run tests:', err) updateToast(loadingToast, 'info', `${service}: Demo-Modus (API nicht verfuegbar)`) } finally { // Polling stoppen if (pollInterval) { clearInterval(pollInterval) } setRunningServices((prev) => { const next = new Set(prev) next.delete(service) return next }) // Progress-Daten entfernen nach Abschluss setServiceProgress((prev) => { const next = { ...prev } delete next[service] return next }) } } // Filter services by category const unitServices = services.filter(s => !s.service.startsWith('bqas-')) const bqasServices = services.filter(s => s.service.startsWith('bqas-')) // Tab content renderer const renderTabContent = () => { switch (activeTab) { case 'overview': return (

Framework-Verteilung

Coverage nach Service

Service-Uebersicht

{services.slice(0, 8).map((service) => ( ))}
) case 'unit': return (

Unit Tests (Go & Python)

{unitServices.map((service) => ( ))}
) case 'bqas': return (

BQAS (LLM Quality Assurance)

Golden Suite, RAG Tests und Synthetic Tests

Vollstaendiges BQAS Dashboard →
{bqasServices.map((service) => ( ))}

Tipp: Das vollstaendige BQAS Dashboard unter /ai/test-quality bietet detaillierte Metriken, Trend-Analyse und Intent-spezifische Scores.

) case 'history': return (

Test Run Historie

) case 'backlog': return ( ) case 'guide': return default: return null } } return (
{/* DevOps Pipeline Sidebar */} {error && (
{error}
)}
{isLoading ? (
) : ( renderTabContent() )}
Test Registry: /api/tests
BQAS Details CI/CD Pipelines
) }