//! 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::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"); } }