311 lines
9.3 KiB
Rust
311 lines
9.3 KiB
Rust
#![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::<Value>().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::<Value>().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");
|
|
}
|