fix(tempo,evidence): fix SystemExit crash + evidence not shown in frontend
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
tempo: tempoapiclient raises SystemExit (BaseException) on API errors like
'User is invalid' 400 responses; except Exception never catches it, killing
the uvicorn worker and causing a 500. Wrap create_worklog() to intercept
BaseException and re-raise as RuntimeError so callers can catch it safely.
evidence: TestOut schema was missing red_evidences / blue_evidences fields.
The ORM model has evidences loaded via joinedload but they were never
serialized into the API response. Add both fields to TestOut and override
model_validate to split Test.evidences by team, injecting the backend-proxy
download_url for each one (/api/v1/evidence/{id}/file).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user