#![allow(dead_code, unused_imports)] use super::common::*; use serde_json::{Value, json}; use serial_test::serial; #[tokio::test(flavor = "multi_thread")] #[serial] async fn binding_requires_exact_published_operation_version() { let registry = test_registry().await; let storage_root = test_storage_root("agent_catalog_binding_requires_published"); 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 draft_operation = assert_success_json( client .post(format!("{base_url}/operations")) .json(&test_operation_payload( &upstream_base_url, "draft_not_bindable_tool", )) .send() .await .unwrap(), ) .await; let draft_operation_id = draft_operation["operation_id"].as_str().unwrap().to_owned(); let agent = assert_success_json( client .post(format!("{base_url}/agents")) .json(&json!({ "slug": "strict-catalog-agent", "display_name": "Strict Catalog Agent", "description": "Rejects draft bindings", "instructions": {}, "tool_selection_policy": {} })) .send() .await .unwrap(), ) .await; let agent_id = agent["agent_id"].as_str().unwrap().to_owned(); let response = client .post(format!("{base_url}/agents/{agent_id}/bindings")) .header( reqwest::header::IF_MATCH, agent_etag(&client, &base_url, &agent_id).await, ) .json(&json!([ { "operation_id": draft_operation_id, "operation_version": 1, "tool_name": "draft_not_bindable_tool", "tool_title": "Draft should not bind", "tool_description_override": null, "enabled": true } ])) .send() .await .unwrap(); assert_eq!(response.status(), reqwest::StatusCode::UNPROCESSABLE_ENTITY); let body = response.json::().await.unwrap(); assert_eq!( body["error"]["code"], "agent_binding_not_published", "binding Draft Operation must fail before publish and not be silently filtered" ); } #[tokio::test(flavor = "multi_thread")] #[serial] async fn stale_agent_revision_rejects_mutation() { let registry = test_registry().await; let storage_root = test_storage_root("agent_catalog_stale_revision"); 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 operation = assert_success_json( client .post(format!("{base_url}/operations")) .json(&test_operation_payload( &upstream_base_url, "stale_agent_tool", )) .send() .await .unwrap(), ) .await; let operation_id = operation["operation_id"].as_str().unwrap().to_owned(); assert_success_json( client .post(format!("{base_url}/operations/{operation_id}/publish")) .header( reqwest::header::IF_MATCH, operation_etag(&client, &base_url, &operation_id).await, ) .json(&json!({ "version": 1 })) .send() .await .unwrap(), ) .await; let agent = assert_success_json( client .post(format!("{base_url}/agents")) .json(&json!({ "slug": "stale-agent", "display_name": "Stale Agent", "description": "Stale revision test", "instructions": {}, "tool_selection_policy": {} })) .send() .await .unwrap(), ) .await; let agent_id = agent["agent_id"].as_str().unwrap().to_owned(); assert_success_json( client .post(format!("{base_url}/agents/{agent_id}/bindings")) .header( reqwest::header::IF_MATCH, agent_etag(&client, &base_url, &agent_id).await, ) .json(&json!([{ "operation_id": operation_id, "operation_version": 1, "tool_name": "stale_agent_tool", "tool_title": "Stale Agent Tool", "enabled": true }])) .send() .await .unwrap(), ) .await; assert_success_json( client .post(format!("{base_url}/agents/{agent_id}/publish")) .header( reqwest::header::IF_MATCH, agent_etag(&client, &base_url, &agent_id).await, ) .json(&json!({ "version": 1 })) .send() .await .unwrap(), ) .await; let response = client .post(format!("{base_url}/agents/{agent_id}/bindings")) .json(&json!([])) .send() .await .unwrap(); assert_eq!( response.status(), reqwest::StatusCode::PRECONDITION_REQUIRED, "Agent mutations after read must require current revision precondition" ); let body = response.json::().await.unwrap(); assert_eq!(body["error"]["code"], "agent_precondition_required"); } #[tokio::test(flavor = "multi_thread")] #[serial] async fn unpublishes_and_archives_agent() { let registry = test_registry().await; let storage_root = test_storage_root("agent_statuses"); 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 operation = assert_success_json( client .post(format!("{base_url}/operations")) .json(&test_operation_payload( &upstream_base_url, "crm_create_lead_agent_status", )) .send() .await .unwrap(), ) .await; let operation_id = operation["operation_id"].as_str().unwrap().to_owned(); client .post(format!("{base_url}/operations/{operation_id}/publish")) .header( reqwest::header::IF_MATCH, operation_etag(&client, &base_url, &operation_id).await, ) .json(&json!({ "version": 1 })) .send() .await .unwrap(); let created = assert_success_json( client .post(format!("{base_url}/agents")) .json(&json!({ "slug": "sales-routing", "display_name": "Sales Routing", "description": "Routing agent", "instructions": {}, "tool_selection_policy": {} })) .send() .await .unwrap(), ) .await; let agent_id = created["agent_id"].as_str().unwrap().to_owned(); client .post(format!("{base_url}/agents/{agent_id}/bindings")) .header( reqwest::header::IF_MATCH, agent_etag(&client, &base_url, &agent_id).await, ) .json(&json!([ { "operation_id": operation_id, "operation_version": 1, "tool_name": "crm_create_lead_agent_status", "tool_title": "Create Lead", "tool_description_override": null, "enabled": true } ])) .send() .await .unwrap(); client .post(format!("{base_url}/agents/{agent_id}/publish")) .header( reqwest::header::IF_MATCH, agent_etag(&client, &base_url, &agent_id).await, ) .json(&json!({ "version": 1 })) .send() .await .unwrap(); let unpublished = assert_success_json( client .post(format!("{base_url}/agents/{agent_id}/unpublish")) .header( reqwest::header::IF_MATCH, agent_etag(&client, &base_url, &agent_id).await, ) .json(&json!({})) .send() .await .unwrap(), ) .await; assert_eq!(unpublished["agent_id"], agent_id); let draft_detail = assert_success_json( client .get(format!("{base_url}/agents/{agent_id}")) .send() .await .unwrap(), ) .await; assert_eq!(draft_detail["status"], "draft"); assert_eq!(draft_detail["latest_published_version"], 1); let archived = assert_success_json( client .post(format!("{base_url}/agents/{agent_id}/archive")) .header( reqwest::header::IF_MATCH, agent_etag(&client, &base_url, &agent_id).await, ) .json(&json!({})) .send() .await .unwrap(), ) .await; assert_eq!(archived["agent_id"], agent_id); let archived_detail = assert_success_json( client .get(format!("{base_url}/agents/{agent_id}")) .send() .await .unwrap(), ) .await; assert_eq!(archived_detail["status"], "archived"); }