This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
breakpilot-pwa/backend/api/tests/registry/routes/ci.py
Benjamin Admin bfdaf63ba9 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>
2026-02-09 09:51:32 +01:00

296 lines
10 KiB
Python

"""
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}")