From b9ab17d1d3296ed6ed1d7dc7770b5f1d5f130564 Mon Sep 17 00:00:00 2001 From: "a.tolmachev" Date: Sun, 19 Apr 2026 21:54:41 +0000 Subject: [PATCH] api: add structured context for usage and session errors --- apps/admin-api/src/app.rs | 77 +++++++++++++++++++++++++++++++++++ apps/admin-api/src/service.rs | 43 ++++++++++--------- 2 files changed, 100 insertions(+), 20 deletions(-) diff --git a/apps/admin-api/src/app.rs b/apps/admin-api/src/app.rs index 507c80c..f3bef6f 100644 --- a/apps/admin-api/src/app.rs +++ b/apps/admin-api/src/app.rs @@ -1695,6 +1695,36 @@ mod tests { ); } + #[tokio::test(flavor = "multi_thread")] + #[serial] + async fn returns_structured_context_for_missing_stream_session() { + let registry = test_registry().await; + let storage_root = test_storage_root("missing_stream_session"); + let base_url = spawn_admin_api(build_test_app(registry, storage_root)).await; + let client = authorized_client(&base_url).await; + + let response = client + .get(format!("{base_url}/stream-sessions/sess_missing")) + .send() + .await + .unwrap(); + let status = response.status(); + let body = response.json::().await.unwrap(); + + assert_eq!(status, reqwest::StatusCode::NOT_FOUND); + assert_eq!(body["error"]["code"], "not_found"); + assert_eq!( + body["error"]["message"], + "stream session sess_missing was not found" + ); + assert_eq!( + body["error"]["context"], + json!({ + "session_id": "sess_missing" + }) + ); + } + #[tokio::test(flavor = "multi_thread")] #[serial] async fn uploads_wsdl_and_tests_soap_operation() { @@ -1812,6 +1842,53 @@ mod tests { ); } + #[tokio::test(flavor = "multi_thread")] + #[serial] + async fn returns_structured_context_for_missing_operation_usage() { + let registry = test_registry().await; + let storage_root = test_storage_root("missing_operation_usage"); + let upstream_base_url = spawn_upstream_server().await; + let base_url = spawn_admin_api(build_test_app(registry, storage_root)).await; + let client = authorized_client(&base_url).await; + + let created = client + .post(format!("{base_url}/operations")) + .json(&test_operation_payload( + &upstream_base_url, + "crm_missing_usage", + )) + .send() + .await + .unwrap() + .json::() + .await + .unwrap(); + let operation_id = created["operation_id"].as_str().unwrap().to_owned(); + + let response = client + .get(format!( + "{base_url}/usage/operations/{operation_id}?period=7d" + )) + .send() + .await + .unwrap(); + let status = response.status(); + let body = response.json::().await.unwrap(); + + assert_eq!(status, reqwest::StatusCode::NOT_FOUND); + assert_eq!(body["error"]["code"], "not_found"); + assert_eq!( + body["error"]["message"], + format!("usage for operation {operation_id} was not found") + ); + assert_eq!( + body["error"]["context"], + json!({ + "operation_id": operation_id + }) + ); + } + #[tokio::test(flavor = "multi_thread")] #[serial] async fn manages_auth_profiles_and_yaml_upsert() { diff --git a/apps/admin-api/src/service.rs b/apps/admin-api/src/service.rs index 7138760..e7a8b64 100644 --- a/apps/admin-api/src/service.rs +++ b/apps/admin-api/src/service.rs @@ -1439,10 +1439,13 @@ impl AdminService { ) .await? .ok_or_else(|| { - ApiError::not_found(format!( - "usage for operation {} was not found", - operation_id.as_str() - )) + ApiError::not_found_with_context( + format!( + "usage for operation {} was not found", + operation_id.as_str() + ), + json!({ "operation_id": operation_id.as_str() }), + ) }) } @@ -1470,10 +1473,10 @@ impl AdminService { ) .await? .ok_or_else(|| { - ApiError::not_found(format!( - "usage for agent {} was not found", - agent_id.as_str() - )) + ApiError::not_found_with_context( + format!("usage for agent {} was not found", agent_id.as_str()), + json!({ "agent_id": agent_id.as_str() }), + ) }) } @@ -1534,10 +1537,10 @@ impl AdminService { .get_stream_session(session_id) .await? .ok_or_else(|| { - ApiError::not_found(format!( - "stream session {} was not found", - session_id.as_str() - )) + ApiError::not_found_with_context( + format!("stream session {} was not found", session_id.as_str()), + json!({ "session_id": session_id.as_str() }), + ) })?; ensure_stream_session_workspace(&session, workspace_id)?; @@ -1556,10 +1559,10 @@ impl AdminService { .get_stream_session(session_id) .await? .ok_or_else(|| { - ApiError::not_found(format!( - "stream session {} was not found", - session_id.as_str() - )) + ApiError::not_found_with_context( + format!("stream session {} was not found", session_id.as_str()), + json!({ "session_id": session_id.as_str() }), + ) })?; ensure_stream_session_workspace(&session, workspace_id)?; self.registry @@ -1570,10 +1573,10 @@ impl AdminService { .get_stream_session(session_id) .await? .ok_or_else(|| { - ApiError::not_found(format!( - "stream session {} was not found", - session_id.as_str() - )) + ApiError::not_found_with_context( + format!("stream session {} was not found", session_id.as_str()), + json!({ "session_id": session_id.as_str() }), + ) })?; Ok(stream_session_detail_view(updated))