corteq-onepassword 0.1.5

Secure 1Password SDK wrapper with FFI bindings for Rust applications
Documentation
//! Client initialization and connection tests.

use corteq_onepassword::{Error, OnePassword};
use serial_test::serial;

/// Helper to check if integration tests should run.
fn should_run_integration_tests() -> bool {
    std::env::var("OP_SERVICE_ACCOUNT_TOKEN").is_ok()
}

/// Helper to skip test if no token is available.
macro_rules! skip_if_no_token {
    () => {
        if !should_run_integration_tests() {
            eprintln!("Skipping: OP_SERVICE_ACCOUNT_TOKEN not set");
            return;
        }
    };
}

#[test]
#[serial]
fn test_from_env_returns_error_when_no_token() {
    // Temporarily remove the token
    let original = std::env::var("OP_SERVICE_ACCOUNT_TOKEN").ok();
    std::env::remove_var("OP_SERVICE_ACCOUNT_TOKEN");

    let result = OnePassword::from_env();
    assert!(matches!(result, Err(Error::MissingAuthToken)));

    // Restore
    if let Some(val) = original {
        std::env::set_var("OP_SERVICE_ACCOUNT_TOKEN", val);
    }
}

#[test]
#[serial]
fn test_from_env_returns_error_when_empty_token() {
    let original = std::env::var("OP_SERVICE_ACCOUNT_TOKEN").ok();
    std::env::set_var("OP_SERVICE_ACCOUNT_TOKEN", "");

    let result = OnePassword::from_env();
    assert!(matches!(result, Err(Error::MissingAuthToken)));

    // Restore
    match original {
        Some(val) => std::env::set_var("OP_SERVICE_ACCOUNT_TOKEN", val),
        None => std::env::remove_var("OP_SERVICE_ACCOUNT_TOKEN"),
    }
}

#[test]
fn test_from_token_rejects_invalid() {
    // Invalid tokens should be rejected with InvalidToken error
    let result = OnePassword::from_token("test-token");
    assert!(matches!(result, Err(Error::InvalidToken)));
}

#[test]
fn test_builder_integration_method() {
    // Use a valid token format for testing builder functionality
    use base64::Engine;
    let valid_payload = base64::engine::general_purpose::URL_SAFE_NO_PAD
        .encode(r#"{"signInAddress":"example.com","email":"test@test.com","deviceUuid":"123"}"#);
    let token = format!("ops_{valid_payload}");

    let builder = OnePassword::from_token(&token)
        .expect("valid token format should be accepted")
        .integration("test-app", "1.0.0");
    // Verify chaining works
    let _ = builder;
}

#[tokio::test]
#[ignore = "requires valid OP_SERVICE_ACCOUNT_TOKEN"]
async fn test_connect_with_valid_token() {
    skip_if_no_token!();

    let result = OnePassword::from_env()
        .expect("token should be set")
        .integration("integration-test", "0.1.0")
        .connect()
        .await;

    assert!(result.is_ok(), "Failed to connect: {:?}", result.err());
}

#[test]
fn test_invalid_token_rejected_at_parse_time() {
    // Invalid tokens should be rejected immediately, not at connect time
    let result = OnePassword::from_token("invalid-token-12345");
    assert!(matches!(result, Err(Error::InvalidToken)));
}

#[tokio::test]
#[ignore = "requires valid OP_SERVICE_ACCOUNT_TOKEN"]
async fn test_connect_with_wrong_credentials() {
    // This tests that a syntactically valid but incorrect token
    // fails at connection time with an authentication error
    use base64::Engine;
    let fake_payload = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(
        r#"{"signInAddress":"example.com","email":"test@test.com","deviceUuid":"123","secretKey":"fake","srpX":"fake"}"#
    );
    let fake_token = format!("ops_{fake_payload}");

    let result = OnePassword::from_token(&fake_token)
        .expect("valid format should be accepted")
        .integration("integration-test", "0.1.0")
        .connect()
        .await;

    assert!(result.is_err(), "Should fail with wrong credentials");

    if let Err(e) = result {
        // Should be an authentication or SDK error
        let error_str = format!("{e:?}");
        assert!(
            error_str.contains("Authentication")
                || error_str.contains("auth")
                || error_str.contains("Sdk")
                || error_str.contains("invalid"),
            "Expected auth/sdk error, got: {error_str}"
        );
    }
}

#[tokio::test]
#[ignore = "requires valid OP_SERVICE_ACCOUNT_TOKEN"]
async fn test_client_is_send_sync() {
    skip_if_no_token!();

    let client = OnePassword::from_env()
        .expect("token should be set")
        .connect()
        .await
        .expect("connect should succeed");

    // Verify Send + Sync by moving to another task
    let client = std::sync::Arc::new(client);
    let client_clone = client.clone();

    let handle = tokio::spawn(async move {
        // If this compiles, the client is Send + Sync
        let _ = &client_clone;
    });

    handle.await.unwrap();
}

#[cfg(feature = "blocking")]
#[test]
#[ignore = "requires valid OP_SERVICE_ACCOUNT_TOKEN"]
fn test_connect_blocking() {
    if !should_run_integration_tests() {
        eprintln!("Skipping: OP_SERVICE_ACCOUNT_TOKEN not set");
        return;
    }

    let result = OnePassword::from_env()
        .expect("token should be set")
        .integration("blocking-test", "0.1.0")
        .connect_blocking();

    assert!(result.is_ok(), "Failed to connect: {:?}", result.err());
}