fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.
This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).
Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
295
backend/api/tests/registry/routes/ci.py
Normal file
295
backend/api/tests/registry/routes/ci.py
Normal file
@@ -0,0 +1,295 @@
|
||||
"""
|
||||
Test Registry - CI/CD Integration Endpoints
|
||||
|
||||
Endpoints for receiving results from CI/CD pipelines.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Dict
|
||||
|
||||
from fastapi import APIRouter, BackgroundTasks
|
||||
|
||||
from ...database import get_db_session
|
||||
from ...repository import TestRepository
|
||||
from ..api_models import CIResultRequest
|
||||
from ..config import (
|
||||
get_test_runs,
|
||||
get_persisted_results,
|
||||
is_postgres_available,
|
||||
)
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/ci-result")
|
||||
async def receive_ci_result(result: CIResultRequest, background_tasks: BackgroundTasks):
|
||||
"""
|
||||
Empfaengt Test-Ergebnisse von der CI/CD-Pipeline.
|
||||
|
||||
Wird vom report-test-results Step in .woodpecker/main.yml aufgerufen.
|
||||
|
||||
Flow:
|
||||
1. Pipeline fuehrt Tests aus und sammelt JSON-Ergebnisse
|
||||
2. Pipeline sendet detaillierte Ergebnisse pro Service hierher
|
||||
3. Dieser Endpoint speichert in PostgreSQL
|
||||
4. Dashboard zeigt die Daten an
|
||||
|
||||
test_results Format:
|
||||
{
|
||||
"service": "consent-service",
|
||||
"framework": "go",
|
||||
"total": 57,
|
||||
"passed": 57,
|
||||
"failed": 0,
|
||||
"skipped": 0,
|
||||
"coverage": 75.5
|
||||
}
|
||||
"""
|
||||
test_runs = get_test_runs()
|
||||
persisted_results = get_persisted_results()
|
||||
|
||||
# Extrahiere Service-spezifische Daten aus test_results
|
||||
tr = result.test_results or {}
|
||||
service_name = tr.get("service", "ci-pipeline")
|
||||
framework = tr.get("framework", "unknown")
|
||||
total = tr.get("total", 0)
|
||||
passed = tr.get("passed", 0)
|
||||
failed = tr.get("failed", 0)
|
||||
skipped = tr.get("skipped", 0)
|
||||
coverage = tr.get("coverage", 0)
|
||||
|
||||
# Log zur Debugging
|
||||
print(f"[CI-RESULT] Pipeline {result.pipeline_id} - Service: {service_name}")
|
||||
print(f"[CI-RESULT] Tests: {passed}/{total} passed, {failed} failed, {skipped} skipped")
|
||||
print(f"[CI-RESULT] Coverage: {coverage}%, Commit: {result.commit[:8]}")
|
||||
|
||||
# Speichere in PostgreSQL wenn verfuegbar
|
||||
if is_postgres_available():
|
||||
try:
|
||||
with get_db_session() as db:
|
||||
repo = TestRepository(db)
|
||||
|
||||
# Erstelle eindeutige Run-ID pro Service
|
||||
run_id = f"ci-{result.pipeline_id}-{service_name}"
|
||||
|
||||
# Erstelle Test-Run Eintrag
|
||||
run = repo.create_run(
|
||||
run_id=run_id,
|
||||
service=service_name,
|
||||
framework=framework,
|
||||
triggered_by="ci",
|
||||
git_commit=result.commit[:8] if result.commit else None,
|
||||
git_branch=result.branch
|
||||
)
|
||||
|
||||
# Markiere als abgeschlossen mit detaillierten Zahlen
|
||||
status = "passed" if failed == 0 else "failed"
|
||||
repo.complete_run(
|
||||
run_id=run_id,
|
||||
status=status,
|
||||
total_tests=total,
|
||||
passed_tests=passed,
|
||||
failed_tests=failed,
|
||||
skipped_tests=skipped,
|
||||
duration_seconds=0
|
||||
)
|
||||
print(f"[CI-RESULT] Stored as run_id: {run_id}, status: {status}")
|
||||
|
||||
# WICHTIG: Aktualisiere den In-Memory Cache fuer sofortige Frontend-Updates
|
||||
persisted_results[service_name] = {
|
||||
"total": total,
|
||||
"passed": passed,
|
||||
"failed": failed,
|
||||
"last_run": datetime.utcnow().isoformat(),
|
||||
"status": status,
|
||||
"failed_test_ids": []
|
||||
}
|
||||
print(f"[CI-RESULT] Updated cache for {service_name}: {passed}/{total} passed")
|
||||
|
||||
# Bei fehlgeschlagenen Tests: Backlog-Eintrag erstellen
|
||||
if failed > 0:
|
||||
background_tasks.add_task(
|
||||
_create_backlog_entry,
|
||||
service_name,
|
||||
framework,
|
||||
failed,
|
||||
result.pipeline_id,
|
||||
result.commit,
|
||||
result.branch
|
||||
)
|
||||
else:
|
||||
# Alle Tests bestanden: Schließe offene Backlog-Einträge
|
||||
background_tasks.add_task(
|
||||
_close_backlog_entry,
|
||||
service_name,
|
||||
result.pipeline_id,
|
||||
result.commit
|
||||
)
|
||||
|
||||
return {
|
||||
"received": True,
|
||||
"run_id": run_id,
|
||||
"service": service_name,
|
||||
"pipeline_id": result.pipeline_id,
|
||||
"status": status,
|
||||
"tests": {"total": total, "passed": passed, "failed": failed},
|
||||
"stored_in": "postgres"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
print(f"[CI-RESULT] PostgreSQL Error: {e}")
|
||||
# Fallback auf Memory-Storage
|
||||
pass
|
||||
|
||||
# Memory-Fallback
|
||||
ci_run = {
|
||||
"id": f"ci-{result.pipeline_id}",
|
||||
"pipeline_id": result.pipeline_id,
|
||||
"commit": result.commit,
|
||||
"branch": result.branch,
|
||||
"status": result.status,
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"test_results": result.test_results
|
||||
}
|
||||
test_runs.append(ci_run)
|
||||
|
||||
return {
|
||||
"received": True,
|
||||
"pipeline_id": result.pipeline_id,
|
||||
"status": result.status,
|
||||
"stored_in": "memory"
|
||||
}
|
||||
|
||||
|
||||
async def _create_backlog_entry(
|
||||
service_name: str,
|
||||
framework: str,
|
||||
failed_count: int,
|
||||
pipeline_id: str,
|
||||
commit: str,
|
||||
branch: str
|
||||
):
|
||||
"""
|
||||
Background-Task: Erstellt Backlog-Eintraege fuer fehlgeschlagene Tests.
|
||||
|
||||
Wird asynchron aufgerufen wenn Tests fehlgeschlagen sind.
|
||||
"""
|
||||
from ...db_models import FailedTestBacklogDB
|
||||
|
||||
print(f"[CI-RESULT] Creating backlog entry for {service_name}: {failed_count} failed tests")
|
||||
|
||||
if is_postgres_available():
|
||||
try:
|
||||
with get_db_session() as db:
|
||||
now = datetime.utcnow()
|
||||
|
||||
# Pruefe ob schon ein offener Backlog-Eintrag fuer diesen Service existiert
|
||||
existing = db.query(FailedTestBacklogDB).filter(
|
||||
FailedTestBacklogDB.service == service_name,
|
||||
FailedTestBacklogDB.status == "open"
|
||||
).first()
|
||||
|
||||
if existing:
|
||||
# Aktualisiere existierenden Eintrag
|
||||
existing.last_failed_at = now
|
||||
existing.failure_count += 1
|
||||
existing.error_message = f"{failed_count} Tests fehlgeschlagen in Pipeline {pipeline_id} (Branch: {branch})"
|
||||
db.commit()
|
||||
print(f"[CI-RESULT] Updated existing backlog entry (ID: {existing.id})")
|
||||
else:
|
||||
# Neuen Eintrag erstellen
|
||||
backlog = FailedTestBacklogDB(
|
||||
test_name=f"{service_name} Tests",
|
||||
test_file=f"{service_name}/",
|
||||
service=service_name,
|
||||
framework=framework,
|
||||
error_message=f"{failed_count} Tests fehlgeschlagen in Pipeline {pipeline_id} (Branch: {branch})",
|
||||
error_type="TEST_FAILURE",
|
||||
first_failed_at=now,
|
||||
last_failed_at=now,
|
||||
failure_count=1,
|
||||
status="open",
|
||||
priority="high" if failed_count > 5 else "medium"
|
||||
)
|
||||
db.add(backlog)
|
||||
db.commit()
|
||||
print(f"[CI-RESULT] Created new backlog entry (ID: {backlog.id})")
|
||||
|
||||
except Exception as e:
|
||||
print(f"[CI-RESULT] Error creating backlog entry: {e}")
|
||||
|
||||
|
||||
async def _close_backlog_entry(
|
||||
service_name: str,
|
||||
pipeline_id: str,
|
||||
commit: str
|
||||
):
|
||||
"""
|
||||
Background-Task: Schließt Backlog-Einträge wenn alle Tests bestanden.
|
||||
|
||||
Wird asynchron aufgerufen wenn Tests erfolgreich waren.
|
||||
"""
|
||||
from ...db_models import FailedTestBacklogDB
|
||||
|
||||
print(f"[CI-RESULT] Checking for open backlog entries to close for {service_name}")
|
||||
|
||||
if is_postgres_available():
|
||||
try:
|
||||
with get_db_session() as db:
|
||||
now = datetime.utcnow()
|
||||
|
||||
# Finde offene Backlog-Einträge für diesen Service
|
||||
open_entries = db.query(FailedTestBacklogDB).filter(
|
||||
FailedTestBacklogDB.service == service_name,
|
||||
FailedTestBacklogDB.status == "open"
|
||||
).all()
|
||||
|
||||
for entry in open_entries:
|
||||
entry.status = "resolved"
|
||||
entry.resolved_at = now
|
||||
entry.resolution_commit = commit[:8] if commit else None
|
||||
entry.resolution_notes = f"Automatisch geschlossen - alle Tests in Pipeline {pipeline_id} bestanden"
|
||||
print(f"[CI-RESULT] Auto-closed backlog entry (ID: {entry.id}) for {service_name}")
|
||||
|
||||
if open_entries:
|
||||
db.commit()
|
||||
print(f"[CI-RESULT] Closed {len(open_entries)} backlog entries for {service_name}")
|
||||
else:
|
||||
print(f"[CI-RESULT] No open backlog entries for {service_name}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"[CI-RESULT] Error closing backlog entries: {e}")
|
||||
|
||||
|
||||
async def _fetch_and_store_failed_tests(pipeline_id: str, commit: str, branch: str):
|
||||
"""
|
||||
Legacy Background-Task fuer generische Pipeline-Fehler.
|
||||
"""
|
||||
from ...db_models import FailedTestBacklogDB
|
||||
|
||||
print(f"[CI-RESULT] Fetching failed test details for pipeline {pipeline_id}")
|
||||
|
||||
if is_postgres_available():
|
||||
try:
|
||||
with get_db_session() as db:
|
||||
now = datetime.utcnow()
|
||||
|
||||
backlog = FailedTestBacklogDB(
|
||||
test_name=f"CI Pipeline {pipeline_id}",
|
||||
test_file=".woodpecker/main.yml",
|
||||
service="ci-pipeline",
|
||||
framework="woodpecker",
|
||||
error_message=f"Pipeline {pipeline_id} fehlgeschlagen auf Branch {branch}",
|
||||
error_type="CI_FAILURE",
|
||||
first_failed_at=now,
|
||||
last_failed_at=now,
|
||||
failure_count=1,
|
||||
status="open",
|
||||
priority="high"
|
||||
)
|
||||
db.add(backlog)
|
||||
db.commit()
|
||||
print(f"[CI-RESULT] Added pipeline failure to backlog (ID: {backlog.id})")
|
||||
|
||||
except Exception as e:
|
||||
print(f"[CI-RESULT] Error adding to backlog: {e}")
|
||||
Reference in New Issue
Block a user