fix(api): preserve project context for source correlation writes
This commit is contained in:
@@ -17,6 +17,7 @@ from src.core.awooop_operator_auth import (
|
||||
AwoooPOperatorPrincipal,
|
||||
verify_awooop_operator,
|
||||
)
|
||||
from src.core.context import clear_project_context, get_current_project_context, set_project_context
|
||||
from src.services.channel_event_dossier_service import (
|
||||
RecurrenceWorkItemHandoffKind,
|
||||
RecurrenceWorkItemMode,
|
||||
@@ -37,6 +38,28 @@ from src.services.platform_operator_service import list_recent_channel_events
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
class _BodyProjectContext:
|
||||
"""Temporarily promote body project_id into the request project context."""
|
||||
|
||||
def __init__(self, project_id: str | None) -> None:
|
||||
self._project_id = project_id.strip() if project_id else None
|
||||
self._tokens = None
|
||||
|
||||
def __enter__(self) -> None:
|
||||
if not self._project_id:
|
||||
return
|
||||
current = get_current_project_context()
|
||||
self._tokens = set_project_context(
|
||||
project_id=self._project_id,
|
||||
source="request.body",
|
||||
request_id=current.get("request_id"),
|
||||
)
|
||||
|
||||
def __exit__(self, exc_type, exc, tb) -> None:
|
||||
if self._tokens is not None:
|
||||
clear_project_context(self._tokens)
|
||||
|
||||
|
||||
class ChannelEventItem(BaseModel):
|
||||
event_id: UUID
|
||||
project_id: str
|
||||
@@ -524,16 +547,17 @@ async def review_source_correlation_work_item(
|
||||
request: SourceCorrelationReviewDecisionRequest,
|
||||
) -> dict[str, Any]:
|
||||
try:
|
||||
return await fetch_source_correlation_review_decision(
|
||||
project_id=request.project_id,
|
||||
work_item_id=request.work_item_id,
|
||||
decision=request.decision,
|
||||
target_incident_id=request.target_incident_id,
|
||||
reviewer_id=request.reviewer_id,
|
||||
operator_note=request.operator_note,
|
||||
provider=request.provider,
|
||||
limit=request.limit,
|
||||
)
|
||||
with _BodyProjectContext(request.project_id):
|
||||
return await fetch_source_correlation_review_decision(
|
||||
project_id=request.project_id,
|
||||
work_item_id=request.work_item_id,
|
||||
decision=request.decision,
|
||||
target_incident_id=request.target_incident_id,
|
||||
reviewer_id=request.reviewer_id,
|
||||
operator_note=request.operator_note,
|
||||
provider=request.provider,
|
||||
limit=request.limit,
|
||||
)
|
||||
except RecurrenceWorkItemNotFoundError as exc:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
@@ -555,14 +579,15 @@ async def apply_source_correlation_work_item(
|
||||
request: SourceCorrelationApplyRequest,
|
||||
) -> dict[str, Any]:
|
||||
try:
|
||||
return await fetch_source_correlation_apply(
|
||||
project_id=request.project_id,
|
||||
work_item_id=request.work_item_id,
|
||||
reviewer_id=request.reviewer_id,
|
||||
operator_note=request.operator_note,
|
||||
provider=request.provider,
|
||||
limit=request.limit,
|
||||
)
|
||||
with _BodyProjectContext(request.project_id):
|
||||
return await fetch_source_correlation_apply(
|
||||
project_id=request.project_id,
|
||||
work_item_id=request.work_item_id,
|
||||
reviewer_id=request.reviewer_id,
|
||||
operator_note=request.operator_note,
|
||||
provider=request.provider,
|
||||
limit=request.limit,
|
||||
)
|
||||
except RecurrenceWorkItemNotFoundError as exc:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
|
||||
98
apps/api/tests/test_platform_events_project_context.py
Normal file
98
apps/api/tests/test_platform_events_project_context.py
Normal file
@@ -0,0 +1,98 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from src.api.v1.platform import events
|
||||
from src.core.context import (
|
||||
clear_project_context,
|
||||
get_current_project_context,
|
||||
set_project_context,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_source_correlation_review_uses_body_project_context(monkeypatch):
|
||||
observed: dict[str, object] = {}
|
||||
|
||||
async def fake_fetch_source_correlation_review_decision(**kwargs):
|
||||
observed["kwargs"] = kwargs
|
||||
observed["context"] = get_current_project_context()
|
||||
return {"review_record_status": "recorded"}
|
||||
|
||||
monkeypatch.setattr(
|
||||
events,
|
||||
"fetch_source_correlation_review_decision",
|
||||
fake_fetch_source_correlation_review_decision,
|
||||
)
|
||||
|
||||
tokens = set_project_context(
|
||||
project_id=None,
|
||||
source="request.project_id.missing",
|
||||
request_id="request-123",
|
||||
)
|
||||
try:
|
||||
response = await events.review_source_correlation_work_item(
|
||||
events.SourceCorrelationReviewDecisionRequest(
|
||||
project_id="awoooi",
|
||||
work_item_id="source-evidence:sentry:canary",
|
||||
decision="accepted",
|
||||
target_incident_id="INC-20260505-25E744",
|
||||
)
|
||||
)
|
||||
assert response == {"review_record_status": "recorded"}
|
||||
assert observed["kwargs"]["project_id"] == "awoooi"
|
||||
assert observed["context"] == {
|
||||
"project_id": "awoooi",
|
||||
"source": "request.body",
|
||||
"request_id": "request-123",
|
||||
}
|
||||
assert get_current_project_context() == {
|
||||
"project_id": None,
|
||||
"source": "request.project_id.missing",
|
||||
"request_id": "request-123",
|
||||
}
|
||||
finally:
|
||||
clear_project_context(tokens)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_source_correlation_apply_uses_body_project_context(monkeypatch):
|
||||
observed: dict[str, object] = {}
|
||||
|
||||
async def fake_fetch_source_correlation_apply(**kwargs):
|
||||
observed["kwargs"] = kwargs
|
||||
observed["context"] = get_current_project_context()
|
||||
return {"apply_status": "applied"}
|
||||
|
||||
monkeypatch.setattr(
|
||||
events,
|
||||
"fetch_source_correlation_apply",
|
||||
fake_fetch_source_correlation_apply,
|
||||
)
|
||||
|
||||
tokens = set_project_context(
|
||||
project_id=None,
|
||||
source="request.project_id.missing",
|
||||
request_id="request-456",
|
||||
)
|
||||
try:
|
||||
response = await events.apply_source_correlation_work_item(
|
||||
events.SourceCorrelationApplyRequest(
|
||||
project_id="awoooi",
|
||||
work_item_id="source-evidence:sentry:canary",
|
||||
)
|
||||
)
|
||||
assert response == {"apply_status": "applied"}
|
||||
assert observed["kwargs"]["project_id"] == "awoooi"
|
||||
assert observed["context"] == {
|
||||
"project_id": "awoooi",
|
||||
"source": "request.body",
|
||||
"request_id": "request-456",
|
||||
}
|
||||
assert get_current_project_context() == {
|
||||
"project_id": None,
|
||||
"source": "request.project_id.missing",
|
||||
"request_id": "request-456",
|
||||
}
|
||||
finally:
|
||||
clear_project_context(tokens)
|
||||
Reference in New Issue
Block a user