diff --git a/backend/app/schemas/test.py b/backend/app/schemas/test.py index e1b57f9..0d4e2a9 100644 --- a/backend/app/schemas/test.py +++ b/backend/app/schemas/test.py @@ -7,6 +7,7 @@ from pydantic import BaseModel, ConfigDict from app.domain.enums import DataClassification from app.models.enums import TestResult, TestState +from app.schemas.evidence import EvidenceOut # ── Create ────────────────────────────────────────────────────────── @@ -166,12 +167,40 @@ class TestOut(BaseModel): technique_mitre_id: str | None = None technique_name: str | None = None + # Evidences split by team (populated from the ORM relationship) + red_evidences: list[EvidenceOut] = [] + blue_evidences: list[EvidenceOut] = [] + model_config = ConfigDict(from_attributes=True) @classmethod def model_validate(cls, obj, **kwargs): - """Override to populate technique fields from the relationship.""" + """Override to populate technique and evidence fields from ORM relationships.""" if hasattr(obj, "technique") and obj.technique is not None: obj.__dict__["technique_mitre_id"] = obj.technique.mitre_id obj.__dict__["technique_name"] = obj.technique.name + + # Split evidences by team and inject the backend-proxy download URL + if hasattr(obj, "evidences") and obj.evidences is not None: + red_evs: list[EvidenceOut] = [] + blue_evs: list[EvidenceOut] = [] + for ev in obj.evidences: + ev_out = EvidenceOut( + id=ev.id, + test_id=ev.test_id, + file_name=ev.file_name, + sha256_hash=ev.sha256_hash, + uploaded_by=ev.uploaded_by, + uploaded_at=ev.uploaded_at, + team=ev.team, + notes=ev.notes, + download_url=f"/api/v1/evidence/{ev.id}/file", + ) + if ev.team and ev.team.value == "blue": + blue_evs.append(ev_out) + else: + red_evs.append(ev_out) + obj.__dict__["red_evidences"] = red_evs + obj.__dict__["blue_evidences"] = blue_evs + return super().model_validate(obj, **kwargs) diff --git a/backend/app/services/tempo_service.py b/backend/app/services/tempo_service.py index a7e9568..fc17b5c 100644 --- a/backend/app/services/tempo_service.py +++ b/backend/app/services/tempo_service.py @@ -70,15 +70,27 @@ def log_worklog( time_spent_seconds: int, description: str, ) -> dict: - """Create a worklog entry in Tempo using *user*'s personal token.""" + """Create a worklog entry in Tempo using *user*'s personal token. + + Note: tempoapiclient raises SystemExit (not Exception) on API errors, so + we intercept BaseException and re-raise as RuntimeError to keep it non-fatal. + """ tempo = get_user_tempo_client(user) - return tempo.create_worklog( - accountId=author_account_id, - issueId=jira_issue_id, - dateFrom=date, - timeSpentSeconds=time_spent_seconds, - description=description, - ) + try: + return tempo.create_worklog( + accountId=author_account_id, + issueId=jira_issue_id, + dateFrom=date, + timeSpentSeconds=time_spent_seconds, + description=description, + ) + except Exception: + raise + except BaseException as exc: + # tempoapiclient raises SystemExit on HTTP errors (e.g. 400 Bad Request). + # SystemExit is a BaseException, not Exception, so convert it so callers + # can catch it with the usual `except Exception` pattern. + raise RuntimeError(f"Tempo API error: {exc}") from exc def auto_log_test_worklog(