use std::time::{SystemTime, UNIX_EPOCH};
use op_mcp::op::{OpClient, OpError};
fn unique_name(prefix: &str) -> String {
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis();
format!("{}-test-{}", prefix, timestamp)
}
fn create_client() -> Result<OpClient, OpError> {
OpClient::new()
}
mod auth {
use super::*;
#[tokio::test]
async fn test_whoami() {
let client = create_client().expect("Failed to create client");
let result = client.whoami().await;
if let Err(ref e) = result {
let err_str = format!("{:?}", e);
if err_str.contains("ParseError") {
println!("whoami returned unexpected format (this is ok): {}", err_str);
return;
}
if err_str.contains("NotSignedIn") {
let vaults = client.vault_list().await;
if vaults.is_ok() {
println!("whoami returned NotSignedIn but vault_list works - different auth method");
return;
}
panic!("Not signed in to 1Password. Please run 'op signin' first.");
}
}
assert!(result.is_ok(), "whoami should succeed: {:?}", result.err());
let whoami = result.unwrap();
assert!(!whoami.email.is_empty(), "Email should not be empty");
assert!(!whoami.account_uuid.is_empty(), "Account UUID should not be empty");
assert!(!whoami.user_uuid.is_empty(), "User UUID should not be empty");
assert!(!whoami.url.is_empty(), "URL should not be empty");
println!("Logged in as: {} ({})", whoami.email, whoami.url);
}
#[tokio::test]
async fn test_is_authenticated() {
let client = create_client().expect("Failed to create client");
let is_auth = client.vault_list().await.is_ok();
assert!(is_auth, "Should be authenticated (vault_list should succeed)");
}
}
mod account {
use super::*;
#[tokio::test]
async fn test_account_list() {
let client = create_client().expect("Failed to create client");
let result = client.account_list().await;
assert!(result.is_ok(), "account_list should succeed: {:?}", result.err());
let accounts = result.unwrap();
assert!(!accounts.is_empty(), "Should have at least one account");
for account in &accounts {
assert!(!account.email.is_empty(), "Account email should not be empty");
assert!(!account.url.is_empty(), "Account URL should not be empty");
println!("Account: {} @ {}", account.email, account.url);
}
}
#[tokio::test]
async fn test_account_get() {
let client = create_client().expect("Failed to create client");
let result = client.account_get(None).await;
if let Err(ref e) = result {
let err_str = format!("{:?}", e);
if err_str.contains("NotSignedIn") {
let accounts = client.account_list().await.expect("account_list should work");
assert!(!accounts.is_empty(), "Should have at least one account");
println!("Account from list: {} @ {}", accounts[0].email, accounts[0].url);
return;
}
}
assert!(result.is_ok(), "account_get should succeed: {:?}", result.err());
let account = result.unwrap();
assert!(!account.id.is_empty(), "Account ID should not be empty");
assert!(!account.name.is_empty(), "Account name should not be empty");
assert!(!account.domain.is_empty(), "Account domain should not be empty");
println!("Account: {} ({})", account.name, account.domain);
}
}
mod vault {
use super::*;
#[tokio::test]
async fn test_vault_list() {
let client = create_client().expect("Failed to create client");
let result = client.vault_list().await;
assert!(result.is_ok(), "vault_list should succeed: {:?}", result.err());
let vaults = result.unwrap();
assert!(!vaults.is_empty(), "Should have at least one vault");
for vault in &vaults {
assert!(!vault.id.is_empty(), "Vault ID should not be empty");
assert!(!vault.name.is_empty(), "Vault name should not be empty");
println!("Vault: {} ({})", vault.name, vault.id);
}
}
#[tokio::test]
async fn test_vault_get() {
let client = create_client().expect("Failed to create client");
let vaults = client.vault_list().await.expect("Failed to list vaults");
let first_vault = vaults.first().expect("No vaults available");
let result = client.vault_get(&first_vault.id).await;
assert!(result.is_ok(), "vault_get should succeed: {:?}", result.err());
let vault = result.unwrap();
assert_eq!(vault.id, first_vault.id);
assert!(!vault.name.is_empty());
println!("Vault details: {} - {:?}", vault.name, vault.vault_type);
}
#[tokio::test]
async fn test_vault_get_by_name() {
let client = create_client().expect("Failed to create client");
let result = client.vault_get("Private").await;
if let Ok(vault) = result {
assert_eq!(vault.name.to_lowercase(), "private");
println!("Found Private vault: {}", vault.id);
} else {
println!("No 'Private' vault found (this is ok)");
}
}
#[tokio::test]
async fn test_vault_create_edit_delete() {
let client = create_client().expect("Failed to create client");
let vault_name = unique_name("mcp-vault");
let vault_description = "Integration test vault - safe to delete";
println!("Creating vault: {}", vault_name);
let create_result = client
.vault_create(&vault_name, Some(vault_description), None, Some(true))
.await;
assert!(create_result.is_ok(), "vault_create should succeed: {:?}", create_result.err());
let created_vault = create_result.unwrap();
assert_eq!(created_vault.name, vault_name);
println!("Created vault: {} ({})", created_vault.name, created_vault.id);
let new_name = format!("{}-edited", vault_name);
println!("Editing vault to: {}", new_name);
let edit_result = client
.vault_edit(&created_vault.id, Some(&new_name), Some("Edited description"), None, None)
.await;
let vault_after_edit = if edit_result.is_ok() {
edit_result.unwrap()
} else {
println!("vault_edit didn't return JSON, verifying with vault_get...");
client
.vault_get(&created_vault.id)
.await
.expect("Should be able to get vault after edit")
};
println!("Vault after edit attempt: {}", vault_after_edit.name);
println!("Deleting vault: {}", created_vault.id);
let delete_result = client.vault_delete(&created_vault.id).await;
assert!(delete_result.is_ok(), "vault_delete should succeed: {:?}", delete_result.err());
let get_result = client.vault_get(&created_vault.id).await;
assert!(get_result.is_err(), "Vault should no longer exist");
println!("Vault lifecycle test completed successfully");
}
#[tokio::test]
async fn test_vault_user_list() {
let client = create_client().expect("Failed to create client");
let vaults = client.vault_list().await.expect("Failed to list vaults");
let vault = vaults.first().expect("No vaults available");
let result = client.vault_user_list(&vault.id).await;
assert!(result.is_ok(), "vault_user_list should succeed: {:?}", result.err());
let users = result.unwrap();
println!("Vault '{}' has {} user(s) with access", vault.name, users.len());
for user in &users {
println!(" - {} ({:?})", user.id, user.email);
}
}
#[tokio::test]
async fn test_vault_group_list() {
let client = create_client().expect("Failed to create client");
let vaults = client.vault_list().await.expect("Failed to list vaults");
let vault = vaults.first().expect("No vaults available");
let result = client.vault_group_list(&vault.id).await;
assert!(result.is_ok(), "vault_group_list should succeed: {:?}", result.err());
let groups = result.unwrap();
println!("Vault '{}' has {} group(s) with access", vault.name, groups.len());
for group in &groups {
println!(" - {} ({:?})", group.id, group.name);
}
}
}
mod item {
use super::*;
#[tokio::test]
async fn test_item_list() {
let client = create_client().expect("Failed to create client");
let result = client.item_list(None, None, None, None).await;
assert!(result.is_ok(), "item_list should succeed: {:?}", result.err());
let items = result.unwrap();
println!("Found {} items across all vaults", items.len());
for item in items.iter().take(5) {
println!(" - {} ({}) in {:?}", item.title, item.category, item.vault.as_ref().map(|v| &v.id));
}
}
#[tokio::test]
async fn test_item_list_in_vault() {
let client = create_client().expect("Failed to create client");
let vaults = client.vault_list().await.expect("Failed to list vaults");
let vault = vaults.first().expect("No vaults available");
let result = client.item_list(Some(&vault.id), None, None, None).await;
assert!(result.is_ok(), "item_list should succeed: {:?}", result.err());
let items = result.unwrap();
println!("Found {} items in vault '{}'", items.len(), vault.name);
}
#[tokio::test]
async fn test_item_list_by_category() {
let client = create_client().expect("Failed to create client");
let result = client.item_list(None, Some(&["LOGIN"]), None, None).await;
assert!(result.is_ok(), "item_list should succeed: {:?}", result.err());
let items = result.unwrap();
println!("Found {} LOGIN items", items.len());
for item in &items {
assert!(
item.category == "LOGIN" || item.category == "Login",
"Item should be LOGIN category, got: {}",
item.category
);
}
}
#[tokio::test]
async fn test_item_template_list() {
let client = create_client().expect("Failed to create client");
let result = client.item_template_list().await;
assert!(result.is_ok(), "item_template_list should succeed: {:?}", result.err());
let templates = result.unwrap();
assert!(!templates.is_empty(), "Should have at least one template");
println!("Available item templates:");
for template in &templates {
println!(" - {}", template.name);
}
}
#[tokio::test]
async fn test_item_template_get() {
let client = create_client().expect("Failed to create client");
let result = client.item_template_get("Login").await;
if let Ok(template) = result {
println!("Got template: {}", template.name);
if let Some(fields) = &template.fields {
println!("Template has {} fields:", fields.len());
for field in fields {
println!(" - {} ({:?})", field.id, field.label);
}
}
} else {
let list_result = client.item_template_list().await;
assert!(list_result.is_ok(), "At least template list should work");
println!("Template get returned different format, but template list works");
}
}
#[tokio::test]
async fn test_item_create_get_edit_delete() {
let client = create_client().expect("Failed to create client");
let vaults = client.vault_list().await.expect("Failed to list vaults");
let vault = vaults.first().expect("No vaults available");
let item_title = unique_name("mcp-item");
println!("Creating item: {}", item_title);
let create_result = client
.item_create(
"LOGIN",
&item_title,
Some(&vault.id),
None, Some("https://example.com"),
Some(&["test", "integration"]),
Some(&["username=testuser", "password=testpass123"]),
false,
)
.await;
if let Err(ref e) = create_result {
let err_str = format!("{:?}", e);
if err_str.contains("409") || err_str.contains("Conflict") {
println!("Got transient server conflict (409) during create, skipping test");
return;
}
}
assert!(create_result.is_ok(), "item_create should succeed: {:?}", create_result.err());
let created_item = create_result.unwrap();
assert_eq!(created_item.title, item_title);
println!("Created item: {} ({})", created_item.title, created_item.id);
let get_result = client.item_get(&created_item.id, Some(&vault.id), false).await;
assert!(get_result.is_ok(), "item_get should succeed: {:?}", get_result.err());
let item = get_result.unwrap();
assert_eq!(item.id, created_item.id);
let get_reveal_result = client.item_get(&created_item.id, Some(&vault.id), true).await;
assert!(get_reveal_result.is_ok(), "item_get with reveal should succeed");
let item_revealed = get_reveal_result.unwrap();
if let Some(fields) = &item_revealed.fields {
let password_field = fields.iter().find(|f| f.purpose.as_deref() == Some("PASSWORD"));
assert!(password_field.is_some(), "Should have a password field");
if let Some(pf) = password_field {
assert!(pf.value.is_some(), "Password should be revealed");
}
}
let new_title = format!("{}-edited", item_title);
println!("Editing item to: {}", new_title);
let edit_result = client
.item_edit(
&created_item.id,
Some(&vault.id),
Some(&new_title),
None,
None,
None,
None,
Some(true), )
.await;
if let Err(ref e) = edit_result {
let err_str = format!("{:?}", e);
if err_str.contains("409") || err_str.contains("Conflict") {
println!("Got transient server conflict (409) during edit, skipping verification");
let _ = client.item_delete(&created_item.id, Some(&vault.id), true).await;
println!("Item lifecycle test completed (with conflict during edit)");
return;
}
}
assert!(edit_result.is_ok(), "item_edit should succeed: {:?}", edit_result.err());
let edited_item = edit_result.unwrap();
assert_eq!(edited_item.title, new_title);
println!("Deleting (archiving) item: {}", edited_item.id);
let delete_result = client.item_delete(&edited_item.id, Some(&vault.id), true).await;
assert!(delete_result.is_ok(), "item_delete should succeed: {:?}", delete_result.err());
println!("Item lifecycle test completed successfully");
}
#[tokio::test]
async fn test_item_create_with_generated_password() {
let client = create_client().expect("Failed to create client");
let vaults = client.vault_list().await.expect("Failed to list vaults");
let vault = vaults.first().expect("No vaults available");
let item_title = unique_name("mcp-genpass");
println!("Creating item with generated password: {}", item_title);
let create_result = client
.item_create(
"Login", &item_title,
Some(&vault.id),
Some("true"), Some("https://secure-example.com"),
None,
Some(&["username=secureuser"]),
false,
)
.await;
assert!(create_result.is_ok(), "item_create should succeed: {:?}", create_result.err());
let created_item = create_result.unwrap();
let get_result = client.item_get(&created_item.id, Some(&vault.id), true).await;
assert!(get_result.is_ok());
let item = get_result.unwrap();
if let Some(fields) = &item.fields {
let password_field = fields.iter().find(|f| f.purpose.as_deref() == Some("PASSWORD"));
assert!(password_field.is_some(), "Should have a generated password");
if let Some(pf) = password_field {
let password = pf.value.as_ref().expect("Password should be revealed");
assert!(!password.is_empty(), "Generated password should not be empty");
println!("Generated password length: {}", password.len());
}
}
let _ = client.item_delete(&created_item.id, Some(&vault.id), true).await;
println!("Item with generated password test completed successfully");
}
#[tokio::test]
async fn test_item_create_secure_note() {
let client = create_client().expect("Failed to create client");
let vaults = client.vault_list().await.expect("Failed to list vaults");
let vault = vaults.first().expect("No vaults available");
let item_title = unique_name("mcp-note");
println!("Creating secure note: {}", item_title);
let create_result = client
.item_create(
"Secure Note", &item_title,
Some(&vault.id),
None,
None,
Some(&["test", "notes"]),
Some(&["notesPlain=This is a test secure note created by integration tests."]),
false,
)
.await;
if let Err(ref e) = create_result {
let err_str = format!("{:?}", e);
if err_str.contains("409") || err_str.contains("Conflict") {
println!("Got transient server conflict (409), skipping test");
return;
}
}
assert!(create_result.is_ok(), "item_create should succeed: {:?}", create_result.err());
let created_item = create_result.unwrap();
assert!(
created_item.category == "SECURE_NOTE" || created_item.category == "Secure Note",
"Should be a secure note, got: {}",
created_item.category
);
let _ = client.item_delete(&created_item.id, Some(&vault.id), true).await;
println!("Secure note test completed successfully");
}
}
mod document {
use super::*;
use std::fs;
use std::path::Path;
#[tokio::test]
async fn test_document_list() {
let client = create_client().expect("Failed to create client");
let result = client.document_list(None).await;
if let Err(ref e) = result {
let err_str = format!("{:?}", e);
if err_str.contains("NotSignedIn") {
tokio::time::sleep(std::time::Duration::from_millis(500)).await;
let retry = client.document_list(None).await;
if retry.is_ok() {
let documents = retry.unwrap();
println!("Found {} documents across all vaults (after retry)", documents.len());
return;
}
}
}
assert!(result.is_ok(), "document_list should succeed: {:?}", result.err());
let documents = result.unwrap();
println!("Found {} documents across all vaults", documents.len());
for doc in documents.iter().take(5) {
println!(" - {} ({})", doc.title, doc.id);
}
}
#[tokio::test]
async fn test_document_lifecycle() {
let client = create_client().expect("Failed to create client");
let vaults_result = client.vault_list().await;
if vaults_result.is_err() {
println!("Could not list vaults, skipping document lifecycle test");
return;
}
let vaults = vaults_result.unwrap();
let vault = vaults.first().expect("No vaults available");
let temp_dir = std::env::temp_dir();
let temp_file = temp_dir.join(format!("{}.txt", unique_name("mcp-doc")));
let doc_content = "This is a test document created by MCP integration tests.\nSafe to delete.";
fs::write(&temp_file, doc_content).expect("Failed to write temp file");
let doc_title = unique_name("mcp-document");
println!("Creating document: {}", doc_title);
let create_result = client
.document_create(
temp_file.to_str().unwrap(),
Some(&vault.id),
Some(&doc_title),
Some(&["test", "integration"]),
)
.await;
assert!(create_result.is_ok(), "document_create should succeed: {:?}", create_result.err());
let created_doc = create_result.unwrap();
println!("Created document: {:?} ({})", created_doc.title, created_doc.id);
let get_result = client.document_get(&created_doc.id, Some(&vault.id), None).await;
assert!(get_result.is_ok(), "document_get should succeed: {:?}", get_result.err());
let base64_content = get_result.unwrap();
use base64::Engine;
let decoded = base64::engine::general_purpose::STANDARD.decode(&base64_content);
assert!(decoded.is_ok(), "Should be valid base64");
let content = String::from_utf8(decoded.unwrap()).expect("Should be valid UTF-8");
assert_eq!(content, doc_content, "Content should match");
println!("Deleting document: {}", created_doc.id);
let delete_result = client.document_delete(&created_doc.id, Some(&vault.id), true).await;
assert!(delete_result.is_ok(), "document_delete should succeed: {:?}", delete_result.err());
let _ = fs::remove_file(&temp_file);
println!("Document lifecycle test completed successfully");
}
#[tokio::test]
async fn test_document_get_to_file() {
let client = create_client().expect("Failed to create client");
let vaults_result = client.vault_list().await;
if vaults_result.is_err() {
println!("Could not list vaults, skipping document get to file test");
return;
}
let vaults = vaults_result.unwrap();
let vault = vaults.first().expect("No vaults available");
let temp_dir = std::env::temp_dir();
let upload_file = temp_dir.join(format!("{}-upload.txt", unique_name("mcp-doc")));
let doc_content = "Test document for download test.";
fs::write(&upload_file, doc_content).expect("Failed to write temp file");
let create_result = client
.document_create(upload_file.to_str().unwrap(), Some(&vault.id), None, None)
.await;
assert!(create_result.is_ok(), "document_create should succeed");
let created_doc = create_result.unwrap();
let download_file = temp_dir.join(format!("{}-download.txt", unique_name("mcp-doc")));
let get_result = client
.document_get(&created_doc.id, Some(&vault.id), Some(download_file.to_str().unwrap()))
.await;
assert!(get_result.is_ok(), "document_get to file should succeed");
assert!(Path::new(&download_file).exists(), "Downloaded file should exist");
let downloaded_content = fs::read_to_string(&download_file).expect("Failed to read downloaded file");
assert_eq!(downloaded_content, doc_content, "Downloaded content should match");
let _ = client.document_delete(&created_doc.id, Some(&vault.id), true).await;
let _ = fs::remove_file(&upload_file);
let _ = fs::remove_file(&download_file);
println!("Document download to file test completed successfully");
}
}
mod user {
use super::*;
#[tokio::test]
async fn test_user_list() {
let client = create_client().expect("Failed to create client");
let result = client.user_list(None, None).await;
assert!(result.is_ok(), "user_list should succeed: {:?}", result.err());
let users = result.unwrap();
assert!(!users.is_empty(), "Should have at least one user (yourself)");
println!("Found {} users:", users.len());
for user in &users {
println!(" - {} ({}) - {:?}", user.email, user.id, user.state);
}
}
#[tokio::test]
async fn test_user_get() {
let client = create_client().expect("Failed to create client");
let whoami_result = client.whoami().await;
if let Err(ref e) = whoami_result {
println!("whoami failed (may be due to auth method): {:?}", e);
let users = client.user_list(None, None).await.expect("user_list should work");
if let Some(user) = users.first() {
let result = client.user_get(&user.id).await;
assert!(result.is_ok(), "user_get should succeed: {:?}", result.err());
let fetched = result.unwrap();
println!("User: {} ({:?}) - State: {:?}", fetched.email, fetched.name, fetched.state);
}
return;
}
let whoami = whoami_result.unwrap();
let result = client.user_get(&whoami.email).await;
assert!(result.is_ok(), "user_get should succeed: {:?}", result.err());
let user = result.unwrap();
assert_eq!(user.email, whoami.email, "Email should match");
println!("User: {} ({:?}) - State: {:?}", user.email, user.name, user.state);
}
#[tokio::test]
async fn test_user_list_by_group() {
let client = create_client().expect("Failed to create client");
let groups_result = client.group_list(None, None).await;
if let Ok(groups) = groups_result {
if let Some(group) = groups.first() {
let result = client.user_list(Some(&group.id), None).await;
if let Ok(users) = result {
println!("Group '{}' has {} user(s)", group.name, users.len());
} else {
println!("Could not list users in group (may be due to permissions)");
}
} else {
println!("No groups found to test user listing by group");
}
} else {
println!("Could not list groups (may be due to permissions)");
}
}
}
mod group {
use super::*;
#[tokio::test]
async fn test_group_list() {
let client = create_client().expect("Failed to create client");
let result = client.group_list(None, None).await;
assert!(result.is_ok(), "group_list should succeed: {:?}", result.err());
let groups = result.unwrap();
println!("Found {} groups:", groups.len());
for group in &groups {
println!(" - {} ({}) - {:?}", group.name, group.id, group.state);
}
}
#[tokio::test]
async fn test_group_get() {
let client = create_client().expect("Failed to create client");
let groups = client.group_list(None, None).await.expect("Failed to list groups");
if let Some(group) = groups.first() {
let result = client.group_get(&group.id).await;
assert!(result.is_ok(), "group_get should succeed: {:?}", result.err());
let fetched = result.unwrap();
assert_eq!(fetched.id, group.id);
println!("Group details: {} ({:?})", fetched.name, fetched.description);
}
}
#[tokio::test]
async fn test_group_lifecycle() {
let client = create_client().expect("Failed to create client");
let group_name = unique_name("mcp-group");
let group_description = "Integration test group - safe to delete";
println!("Creating group: {}", group_name);
let create_result = client
.group_create(&group_name, Some(group_description))
.await;
if let Err(ref e) = create_result {
if format!("{:?}", e).contains("Forbidden") || format!("{:?}", e).contains("403") {
println!("Skipping group lifecycle test - insufficient permissions (this is normal for non-admin users)");
return;
}
}
assert!(create_result.is_ok(), "group_create should succeed: {:?}", create_result.err());
let created_group = create_result.unwrap();
assert_eq!(created_group.name, group_name);
println!("Created group: {} ({})", created_group.name, created_group.id);
let new_name = format!("{}-edited", group_name);
println!("Editing group to: {}", new_name);
let edit_result = client
.group_edit(&created_group.id, Some(&new_name), Some("Edited description"))
.await;
assert!(edit_result.is_ok(), "group_edit should succeed: {:?}", edit_result.err());
let edited_group = edit_result.unwrap();
assert_eq!(edited_group.name, new_name);
let members_result = client.group_user_list(&edited_group.id).await;
assert!(members_result.is_ok(), "group_user_list should succeed");
println!("Group has {} members", members_result.unwrap().len());
println!("Deleting group: {}", edited_group.id);
let delete_result = client.group_delete(&edited_group.id).await;
assert!(delete_result.is_ok(), "group_delete should succeed: {:?}", delete_result.err());
println!("Group lifecycle test completed successfully");
}
}
mod secret {
use super::*;
#[tokio::test]
async fn test_secret_read_invalid_reference() {
let client = create_client().expect("Failed to create client");
let result = client.read("invalid/reference").await;
assert!(result.is_err(), "Should fail for invalid reference");
match result.err().unwrap() {
OpError::InvalidSecretReference(ref_str) => {
assert_eq!(ref_str, "invalid/reference");
}
other => panic!("Expected InvalidSecretReference error, got: {:?}", other),
}
}
#[tokio::test]
async fn test_secret_read_nonexistent() {
let client = create_client().expect("Failed to create client");
let result = client.read("op://NonexistentVault/NonexistentItem/password").await;
assert!(result.is_err(), "Should fail for nonexistent secret");
}
#[tokio::test]
async fn test_inject_template() {
let client = create_client().expect("Failed to create client");
let template = "CONFIG_VALUE=some_value\nANOTHER_VALUE=123";
let result = client.inject(template).await;
assert!(result.is_ok(), "inject should succeed for template without secrets");
let output = result.unwrap();
assert!(output.contains("CONFIG_VALUE"), "Output should contain the config");
}
#[tokio::test]
async fn test_secret_read_existing() {
let client = create_client().expect("Failed to create client");
let vaults = client.vault_list().await.expect("Failed to list vaults");
let vault = vaults.first().expect("No vaults available");
let item_title = unique_name("mcp-secret-ref");
let test_password = "test-secret-value-12345";
let create_result = client
.item_create(
"LOGIN",
&item_title,
Some(&vault.id),
None,
None,
None,
Some(&[&format!("password={}", test_password)]),
false,
)
.await;
if let Err(ref e) = create_result {
let err_str = format!("{:?}", e);
if err_str.contains("409") || err_str.contains("Conflict") {
println!("Got transient server conflict (409) during create, skipping test");
return;
}
}
assert!(create_result.is_ok(), "item_create should succeed");
let created_item = create_result.unwrap();
let reference = format!("op://{}/{}/password", vault.name, item_title);
println!("Reading secret reference: {}", reference);
let read_result = client.read(&reference).await;
assert!(read_result.is_ok(), "read should succeed: {:?}", read_result.err());
let secret_value = read_result.unwrap();
assert_eq!(secret_value, test_password, "Secret value should match");
let _ = client.item_delete(&created_item.id, Some(&vault.id), true).await;
println!("Secret read test completed successfully");
}
}
mod connect {
use super::*;
#[tokio::test]
async fn test_connect_server_list() {
let client = create_client().expect("Failed to create client");
let result = client.connect_server_list().await;
if let Ok(servers) = result {
println!("Found {} Connect servers", servers.len());
for server in &servers {
println!(" - {} ({})", server.name, server.id);
}
} else {
println!("Connect server list not available (this is ok if Connect is not configured)");
}
}
}
mod error_handling {
use super::*;
#[tokio::test]
async fn test_vault_get_not_found() {
let client = create_client().expect("Failed to create client");
let result = client.vault_get("nonexistent-vault-12345").await;
assert!(result.is_err(), "Should fail for nonexistent vault");
let err = result.err().unwrap();
println!("Error for nonexistent vault: {:?}", err);
}
#[tokio::test]
async fn test_item_get_not_found() {
let client = create_client().expect("Failed to create client");
let result = client.item_get("nonexistent-item-12345", None, false).await;
assert!(result.is_err(), "Should fail for nonexistent item");
let err = result.err().unwrap();
println!("Error for nonexistent item: {:?}", err);
}
#[tokio::test]
async fn test_user_get_not_found() {
let client = create_client().expect("Failed to create client");
let result = client.user_get("nonexistent-user@example.com").await;
assert!(result.is_err(), "Should fail for nonexistent user");
let err = result.err().unwrap();
println!("Error for nonexistent user: {:?}", err);
}
#[tokio::test]
async fn test_group_get_not_found() {
let client = create_client().expect("Failed to create client");
let result = client.group_get("nonexistent-group-12345").await;
assert!(result.is_err(), "Should fail for nonexistent group");
let err = result.err().unwrap();
println!("Error for nonexistent group: {:?}", err);
}
}
mod concurrent {
use super::*;
#[tokio::test]
async fn test_concurrent_reads() {
let client = create_client().expect("Failed to create client");
let (vaults_result, items_result, users_result, groups_result) = tokio::join!(
client.vault_list(),
client.item_list(None, None, None, None),
client.user_list(None, None),
client.group_list(None, None),
);
assert!(vaults_result.is_ok(), "vault_list should succeed: {:?}", vaults_result.err());
let vaults_count = vaults_result.map(|v| v.len()).unwrap_or(0);
let items_count = items_result.map(|v| v.len()).unwrap_or(0);
let users_count = users_result.map(|v| v.len()).unwrap_or(0);
let groups_count = groups_result.map(|v| v.len()).unwrap_or(0);
println!(
"Concurrent reads: {} vaults, {} items, {} users, {} groups",
vaults_count, items_count, users_count, groups_count,
);
}
#[tokio::test]
async fn test_concurrent_item_creation() {
let client = create_client().expect("Failed to create client");
let vaults_result = client.vault_list().await;
if vaults_result.is_err() {
println!("Could not list vaults, skipping concurrent item creation test");
return;
}
let vaults = vaults_result.unwrap();
let vault = vaults.first().expect("No vaults available");
let vault_id = vault.id.clone();
let item1_title = unique_name("mcp-concurrent-1");
let item2_title = unique_name("mcp-concurrent-2");
let item3_title = unique_name("mcp-concurrent-3");
let (result1, result2, result3) = tokio::join!(
client.item_create("Secure Note", &item1_title, Some(&vault_id), None, None, None, None, false),
client.item_create("Secure Note", &item2_title, Some(&vault_id), None, None, None, None, false),
client.item_create("Secure Note", &item3_title, Some(&vault_id), None, None, None, None, false),
);
let mut created_items = Vec::new();
if let Ok(item) = result1 {
created_items.push(item.id.clone());
println!("Created item 1: {}", item.id);
} else {
println!("Item 1 creation failed: {:?}", result1.err());
}
if let Ok(item) = result2 {
created_items.push(item.id.clone());
println!("Created item 2: {}", item.id);
} else {
println!("Item 2 creation failed: {:?}", result2.err());
}
if let Ok(item) = result3 {
created_items.push(item.id.clone());
println!("Created item 3: {}", item.id);
} else {
println!("Item 3 creation failed: {:?}", result3.err());
}
assert!(!created_items.is_empty(), "At least one item should be created");
println!("Created {} items concurrently", created_items.len());
for item_id in created_items {
let _ = client.item_delete(&item_id, Some(&vault_id), true).await;
}
println!("Concurrent item creation/deletion test completed successfully");
}
}