Files
sentryagent-idp/sdk-rust/tests/integration_test.rs
SentryAgent.ai Developer a4aab1b5b3 feat(phase-5): WS1 — Rust SDK
Implements the sentryagent-idp Rust SDK crate (sdk-rust/) with:
- TokenManager with Arc<Mutex<TokenCache>> for thread-safe token caching
- AgentIdPClient with full method coverage: agents, oauth2, credentials, audit, marketplace, delegation
- Error hierarchy via thiserror (AgentIdPError enum)
- All model types with serde derive
- 429 RateLimited handling with Retry-After parsing; zero unwrap() calls
- Unit tests (mockito), doc tests, and integration tests (#[ignore])
- quickstart example, full README, cargo doc clean

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 02:48:14 +00:00

370 lines
11 KiB
Rust

//! Integration tests for the SentryAgent.ai AgentIdP Rust SDK.
//!
//! These tests run against a real API instance. They are marked `#[ignore]`
//! and will not execute in CI unless explicitly opted in with:
//!
//! ```bash
//! AGENTIDP_API_URL=https://api.sentryagent.ai \
//! AGENTIDP_CLIENT_ID=... \
//! AGENTIDP_CLIENT_SECRET=... \
//! cargo test -- --ignored
//! ```
use sentryagent_idp::{
AgentIdPClient, AuditLogFilters, DelegateRequest, MarketplaceFilters, RegisterAgentRequest,
UpdateAgentRequest,
};
/// Helper — build a client from environment variables, skipping the test when
/// any required variable is unset (rather than panicking).
fn client_from_env() -> Option<AgentIdPClient> {
AgentIdPClient::from_env().ok()
}
// ─── Agent CRUD ───────────────────────────────────────────────────────────────
#[tokio::test]
#[ignore]
async fn test_register_and_delete_agent() {
let client = client_from_env().expect("AGENTIDP_* env vars must be set");
let agent = client
.register_agent(RegisterAgentRequest {
name: "integration-test-agent".to_owned(),
description: Some("Created by integration test".to_owned()),
agent_type: "worker".to_owned(),
capabilities: vec!["read:data".to_owned()],
metadata: None,
})
.await
.expect("register_agent should succeed");
assert!(!agent.id.is_empty());
assert_eq!(agent.name, "integration-test-agent");
client
.delete_agent(&agent.id)
.await
.expect("delete_agent should succeed");
}
#[tokio::test]
#[ignore]
async fn test_get_agent() {
let client = client_from_env().expect("AGENTIDP_* env vars must be set");
let created = client
.register_agent(RegisterAgentRequest {
name: "get-test-agent".to_owned(),
description: None,
agent_type: "worker".to_owned(),
capabilities: vec![],
metadata: None,
})
.await
.expect("register_agent should succeed");
let fetched = client
.get_agent(&created.id)
.await
.expect("get_agent should succeed");
assert_eq!(fetched.id, created.id);
assert_eq!(fetched.name, "get-test-agent");
client.delete_agent(&created.id).await.ok();
}
#[tokio::test]
#[ignore]
async fn test_list_agents() {
let client = client_from_env().expect("AGENTIDP_* env vars must be set");
let list = client
.list_agents(Some(1), Some(10))
.await
.expect("list_agents should succeed");
// Must return a valid pagination envelope.
assert!(list.page >= 1);
assert!(list.per_page > 0);
}
#[tokio::test]
#[ignore]
async fn test_update_agent() {
let client = client_from_env().expect("AGENTIDP_* env vars must be set");
let agent = client
.register_agent(RegisterAgentRequest {
name: "update-test-agent".to_owned(),
description: None,
agent_type: "worker".to_owned(),
capabilities: vec![],
metadata: None,
})
.await
.expect("register_agent should succeed");
let updated = client
.update_agent(
&agent.id,
UpdateAgentRequest {
name: Some("updated-name".to_owned()),
description: Some("Updated description".to_owned()),
capabilities: None,
is_public: None,
metadata: None,
},
)
.await
.expect("update_agent should succeed");
assert_eq!(updated.name, "updated-name");
client.delete_agent(&agent.id).await.ok();
}
#[tokio::test]
#[ignore]
async fn test_get_agent_not_found() {
use sentryagent_idp::AgentIdPError;
let client = client_from_env().expect("AGENTIDP_* env vars must be set");
let result = client
.get_agent("00000000-0000-0000-0000-000000000000")
.await;
assert!(
matches!(result, Err(AgentIdPError::NotFound(_))),
"Expected NotFound error, got: {:?}",
result
);
}
// ─── Credentials ─────────────────────────────────────────────────────────────
#[tokio::test]
#[ignore]
async fn test_generate_and_rotate_credentials() {
let client = client_from_env().expect("AGENTIDP_* env vars must be set");
let agent = client
.register_agent(RegisterAgentRequest {
name: "creds-test-agent".to_owned(),
description: None,
agent_type: "worker".to_owned(),
capabilities: vec![],
metadata: None,
})
.await
.expect("register_agent should succeed");
let creds = client
.generate_credentials(&agent.id)
.await
.expect("generate_credentials should succeed");
assert!(!creds.client_id.is_empty());
assert!(!creds.client_secret.is_empty());
let rotated = client
.rotate_credentials(&agent.id)
.await
.expect("rotate_credentials should succeed");
// Rotated secret must differ from the original.
assert_ne!(rotated.client_secret, creds.client_secret);
client.delete_agent(&agent.id).await.ok();
}
// ─── OAuth2 ──────────────────────────────────────────────────────────────────
#[tokio::test]
#[ignore]
async fn test_issue_token() {
let client = client_from_env().expect("AGENTIDP_* env vars must be set");
let agent = client
.register_agent(RegisterAgentRequest {
name: "token-test-agent".to_owned(),
description: None,
agent_type: "worker".to_owned(),
capabilities: vec![],
metadata: None,
})
.await
.expect("register_agent should succeed");
let token = client
.issue_token(&agent.id, &["agents:read"])
.await
.expect("issue_token should succeed");
assert!(!token.access_token.is_empty());
assert_eq!(token.token_type.to_lowercase(), "bearer");
client.delete_agent(&agent.id).await.ok();
}
// ─── Audit logs ──────────────────────────────────────────────────────────────
#[tokio::test]
#[ignore]
async fn test_list_audit_logs() {
let client = client_from_env().expect("AGENTIDP_* env vars must be set");
let logs = client
.list_audit_logs(AuditLogFilters {
agent_id: None,
event_type: None,
from: None,
to: None,
page: 1,
per_page: 20,
})
.await
.expect("list_audit_logs should succeed");
assert!(logs.page >= 1);
assert!(logs.per_page > 0);
}
// ─── Marketplace ─────────────────────────────────────────────────────────────
#[tokio::test]
#[ignore]
async fn test_list_public_agents() {
let client = client_from_env().expect("AGENTIDP_* env vars must be set");
let results = client
.list_public_agents(MarketplaceFilters {
q: None,
capability: None,
publisher: None,
page: 1,
per_page: 10,
})
.await
.expect("list_public_agents should succeed");
assert!(results.page >= 1);
}
#[tokio::test]
#[ignore]
async fn test_marketplace_search() {
let client = client_from_env().expect("AGENTIDP_* env vars must be set");
let results = client
.list_public_agents(MarketplaceFilters {
q: Some("agent".to_owned()),
capability: None,
publisher: None,
page: 1,
per_page: 5,
})
.await
.expect("list_public_agents with query should succeed");
// Result may be empty but must be a valid envelope.
assert!(results.per_page > 0);
}
// ─── Delegation ──────────────────────────────────────────────────────────────
#[tokio::test]
#[ignore]
async fn test_delegate_and_verify() {
let client = client_from_env().expect("AGENTIDP_* env vars must be set");
let delegator = client
.register_agent(RegisterAgentRequest {
name: "delegator-agent".to_owned(),
description: None,
agent_type: "orchestrator".to_owned(),
capabilities: vec!["agents:write".to_owned()],
metadata: None,
})
.await
.expect("register delegator should succeed");
let delegatee = client
.register_agent(RegisterAgentRequest {
name: "delegatee-agent".to_owned(),
description: None,
agent_type: "worker".to_owned(),
capabilities: vec!["agents:read".to_owned()],
metadata: None,
})
.await
.expect("register delegatee should succeed");
let delegation = client
.delegate(DelegateRequest {
delegatee_agent_id: delegatee.id.clone(),
scopes: vec!["agents:read".to_owned()],
ttl_seconds: 3600,
})
.await
.expect("delegate should succeed");
assert!(!delegation.delegation_token.is_empty());
assert!(!delegation.chain_id.is_empty());
let verification = client
.verify_delegation(&delegation.delegation_token)
.await
.expect("verify_delegation should succeed");
assert!(verification.valid);
assert_eq!(
verification.delegatee_agent_id.as_deref(),
Some(delegatee.id.as_str())
);
client.delete_agent(&delegator.id).await.ok();
client.delete_agent(&delegatee.id).await.ok();
}
// ─── Token manager concurrency ────────────────────────────────────────────────
#[tokio::test]
#[ignore]
async fn test_token_manager_concurrent_calls() {
use std::sync::Arc;
use sentryagent_idp::TokenManager;
let api_url = std::env::var("AGENTIDP_API_URL").expect("AGENTIDP_API_URL must be set");
let client_id =
std::env::var("AGENTIDP_CLIENT_ID").expect("AGENTIDP_CLIENT_ID must be set");
let client_secret =
std::env::var("AGENTIDP_CLIENT_SECRET").expect("AGENTIDP_CLIENT_SECRET must be set");
let tm = Arc::new(TokenManager::new(&api_url, &client_id, &client_secret));
let handles: Vec<_> = (0..50)
.map(|_| {
let tm_clone = Arc::clone(&tm);
tokio::spawn(async move { tm_clone.get_token().await })
})
.collect();
let mut tokens = Vec::with_capacity(50);
for handle in handles {
let token = handle
.await
.expect("task did not panic")
.expect("get_token succeeded");
tokens.push(token);
}
// All 50 calls must return the same token (single fetch, all from cache).
let first = &tokens[0];
for t in &tokens[1..] {
assert_eq!(t, first, "all concurrent calls must return the same token");
}
}