feat: extract workflow notifications phase 3
This commit is contained in:
@@ -17,12 +17,14 @@ from app.domains.products.models import CadFile, Product
|
||||
from app.domains.rendering.models import OutputType, RenderTemplate
|
||||
from app.domains.rendering.workflow_runtime_services import (
|
||||
auto_populate_materials_for_cad,
|
||||
emit_order_line_render_notifications,
|
||||
persist_order_line_output,
|
||||
resolve_cad_bbox,
|
||||
prepare_order_line_render_context,
|
||||
resolve_order_line_material_map,
|
||||
resolve_order_line_template_context,
|
||||
)
|
||||
from app.domains.tenants.models import Tenant
|
||||
|
||||
import app.models # noqa: F401
|
||||
|
||||
@@ -543,3 +545,178 @@ def test_persist_order_line_output_marks_failure_without_result_path(sync_sessio
|
||||
assert line.result_path is None
|
||||
assert line.render_log == {"error": "boom"}
|
||||
assert assets == []
|
||||
|
||||
|
||||
def test_emit_order_line_render_notifications_emits_websocket_and_activity(
|
||||
sync_session,
|
||||
tmp_path,
|
||||
):
|
||||
line = _seed_order_line_graph(sync_session, tmp_path)
|
||||
tenant = Tenant(name="Workflow Tenant", slug=f"workflow-{uuid.uuid4().hex[:8]}")
|
||||
sync_session.add(tenant)
|
||||
sync_session.commit()
|
||||
|
||||
line.product.cad_file.tenant_id = tenant.id
|
||||
line.product.tenant_id = tenant.id
|
||||
line.order.tenant_id = tenant.id
|
||||
sync_session.commit()
|
||||
|
||||
websocket_events: list[tuple[str, dict]] = []
|
||||
activity_events: list[dict] = []
|
||||
|
||||
def _capture_websocket(tenant_id: str, event: dict) -> None:
|
||||
websocket_events.append((tenant_id, event))
|
||||
|
||||
def _capture_activity(**payload) -> None:
|
||||
activity_events.append(payload)
|
||||
|
||||
monkeypatch = pytest.MonkeyPatch()
|
||||
monkeypatch.setattr(
|
||||
"app.core.websocket.publish_event_sync",
|
||||
_capture_websocket,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"app.services.notification_service.emit_notification_sync",
|
||||
_capture_activity,
|
||||
)
|
||||
|
||||
try:
|
||||
emit_order_line_render_notifications(
|
||||
success=True,
|
||||
order_line_id=str(line.id),
|
||||
tenant_id=str(tenant.id),
|
||||
product_name=line.product.name or "product",
|
||||
output_type_name=line.output_type.name,
|
||||
session=sync_session,
|
||||
line=line,
|
||||
)
|
||||
finally:
|
||||
monkeypatch.undo()
|
||||
|
||||
assert websocket_events == [
|
||||
(
|
||||
str(tenant.id),
|
||||
{
|
||||
"type": "render_complete",
|
||||
"order_line_id": str(line.id),
|
||||
"order_id": str(line.order_id),
|
||||
"status": "completed",
|
||||
},
|
||||
)
|
||||
]
|
||||
assert activity_events == [
|
||||
{
|
||||
"actor_user_id": None,
|
||||
"target_user_id": str(line.order.created_by),
|
||||
"action": "render.completed",
|
||||
"entity_type": "order",
|
||||
"entity_id": str(line.order_id),
|
||||
"details": {
|
||||
"order_number": line.order.order_number,
|
||||
"product_name": line.product.name,
|
||||
"output_type": line.output_type.name,
|
||||
},
|
||||
"channel": "activity",
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def test_emit_order_line_render_notifications_truncates_failure_error_and_skips_websocket_without_tenant(
|
||||
sync_session,
|
||||
tmp_path,
|
||||
):
|
||||
line = _seed_order_line_graph(sync_session, tmp_path)
|
||||
activity_events: list[dict] = []
|
||||
websocket_events: list[tuple[str, dict]] = []
|
||||
|
||||
def _capture_websocket(tenant_id: str, event: dict) -> None:
|
||||
websocket_events.append((tenant_id, event))
|
||||
|
||||
def _capture_activity(**payload) -> None:
|
||||
activity_events.append(payload)
|
||||
|
||||
monkeypatch = pytest.MonkeyPatch()
|
||||
monkeypatch.setattr(
|
||||
"app.core.websocket.publish_event_sync",
|
||||
_capture_websocket,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"app.services.notification_service.emit_notification_sync",
|
||||
_capture_activity,
|
||||
)
|
||||
|
||||
try:
|
||||
emit_order_line_render_notifications(
|
||||
success=False,
|
||||
order_line_id=str(line.id),
|
||||
product_name=line.product.name or "product",
|
||||
output_type_name=line.output_type.name,
|
||||
render_log={"error": "x" * 400},
|
||||
session=sync_session,
|
||||
line=line,
|
||||
)
|
||||
finally:
|
||||
monkeypatch.undo()
|
||||
|
||||
assert websocket_events == []
|
||||
assert activity_events == [
|
||||
{
|
||||
"actor_user_id": None,
|
||||
"target_user_id": str(line.order.created_by),
|
||||
"action": "render.failed",
|
||||
"entity_type": "order",
|
||||
"entity_id": str(line.order_id),
|
||||
"details": {
|
||||
"order_number": line.order.order_number,
|
||||
"product_name": line.product.name,
|
||||
"output_type": line.output_type.name,
|
||||
"error": "x" * 300,
|
||||
},
|
||||
"channel": "activity",
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def test_emit_order_line_render_notifications_supports_retry_exhausted_activity_payload():
|
||||
activity_events: list[dict] = []
|
||||
|
||||
def _capture_activity(**payload) -> None:
|
||||
activity_events.append(payload)
|
||||
|
||||
monkeypatch = pytest.MonkeyPatch()
|
||||
monkeypatch.setattr(
|
||||
"app.services.notification_service.emit_notification_sync",
|
||||
_capture_activity,
|
||||
)
|
||||
|
||||
try:
|
||||
emit_order_line_render_notifications(
|
||||
success=False,
|
||||
order_line_id="line-1",
|
||||
order_number="ORD-FAIL",
|
||||
order_creator_id="user-1",
|
||||
product_name="unknown",
|
||||
output_type_name="unknown",
|
||||
render_log={"error": "retry exhausted"},
|
||||
emit_websocket=False,
|
||||
activity_entity_id=None,
|
||||
)
|
||||
finally:
|
||||
monkeypatch.undo()
|
||||
|
||||
assert activity_events == [
|
||||
{
|
||||
"actor_user_id": None,
|
||||
"target_user_id": "user-1",
|
||||
"action": "render.failed",
|
||||
"entity_type": "order",
|
||||
"entity_id": None,
|
||||
"details": {
|
||||
"order_number": "ORD-FAIL",
|
||||
"product_name": "unknown",
|
||||
"output_type": "unknown",
|
||||
"error": "retry exhausted",
|
||||
},
|
||||
"channel": "activity",
|
||||
}
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user