verifex 0.2.0

Official Rust SDK for the Verifex sanctions screening API
Documentation
use verifex::{ScreenRequest, Verifex};

// ── Live API Tests ─────────────────────────────────────────────────────

#[tokio::test]
async fn test_health_live() {
    let client = Verifex::new("dummy").with_base_url("https://api.verifex.dev");
    let h = client.health().await.expect("health should succeed");

    assert_eq!(h.status, "ok");
    assert_eq!(h.database, "connected");
    assert_eq!(h.redis, "connected");
    assert!(h.is_healthy());
    assert!(h.total_entities() > 900_000, "expected >900K entities, got {}", h.total_entities());
}

#[tokio::test]
async fn test_auth_error_live() {
    let client = Verifex::new("invalid_key").with_base_url("https://api.verifex.dev");
    let err = client
        .screen(ScreenRequest { name: "test".into(), ..Default::default() })
        .await
        .expect_err("should fail with invalid key");

    assert!(err.is_auth(), "expected auth error, got: {err}");
}

// ── Mock Server Tests ──────────────────────────────────────────────────

#[tokio::test]
async fn test_screen_mock() {
    let mut server = mockito::Server::new_async().await;

    let mock = server
        .mock("POST", "/v1/screen")
        .match_header("authorization", "Bearer test_key")
        .with_status(200)
        .with_header("content-type", "application/json")
        .with_body(
            r#"{
                "query": {"name": "Vladimir Putin"},
                "matches": [{
                    "id": "1",
                    "name": "PUTIN, Vladimir Vladimirovich",
                    "aliases": [],
                    "source": "OFAC",
                    "entity_type": "person",
                    "confidence": 98,
                    "risk_level": "critical",
                    "match_type": "EXACT"
                }],
                "total_matches": 1,
                "risk_level": "critical",
                "screened_at": "2026-01-01T00:00:00Z",
                "request_id": "test-123",
                "lists_checked": ["OFAC"],
                "api_version": "v1"
            }"#,
        )
        .create_async()
        .await;

    let client = Verifex::new("test_key").with_base_url(&server.url());
    let result = client
        .screen(ScreenRequest { name: "Vladimir Putin".into(), ..Default::default() })
        .await
        .expect("screen should succeed");

    assert_eq!(result.risk_level, "critical");
    assert_eq!(result.total_matches, 1);
    assert!(result.is_match());
    assert!(!result.is_clear());
    assert_eq!(result.highest_confidence(), 98);
    assert_eq!(result.matches[0].name, "PUTIN, Vladimir Vladimirovich");
    assert_eq!(result.matches[0].source, "OFAC");

    mock.assert_async().await;
}

#[tokio::test]
async fn test_clear_result_mock() {
    let mut server = mockito::Server::new_async().await;

    server
        .mock("POST", "/v1/screen")
        .with_status(200)
        .with_header("content-type", "application/json")
        .with_body(
            r#"{
                "query": {"name": "Nobody"},
                "matches": [],
                "total_matches": 0,
                "risk_level": "clear",
                "screened_at": "2026-01-01T00:00:00Z",
                "request_id": "test-456",
                "lists_checked": [],
                "api_version": "v1"
            }"#,
        )
        .create_async()
        .await;

    let client = Verifex::new("key").with_base_url(&server.url());
    let result = client
        .screen(ScreenRequest { name: "Nobody".into(), ..Default::default() })
        .await
        .unwrap();

    assert!(result.is_clear());
    assert!(!result.is_match());
    assert_eq!(result.highest_confidence(), 0);
}

#[tokio::test]
async fn test_batch_screen_mock() {
    let mut server = mockito::Server::new_async().await;

    server
        .mock("POST", "/v1/screen/batch")
        .with_status(200)
        .with_header("content-type", "application/json")
        .with_body(
            r#"{
                "results": [
                    {"query":{"name":"A"},"matches":[],"total_matches":0,"risk_level":"clear","screened_at":"2026-01-01","request_id":"r1","api_version":"v1"},
                    {"query":{"name":"B"},"matches":[],"total_matches":0,"risk_level":"clear","screened_at":"2026-01-01","request_id":"r2","api_version":"v1"}
                ],
                "total_duration_ms": 42
            }"#,
        )
        .create_async()
        .await;

    let client = Verifex::new("key").with_base_url(&server.url());
    let result = client
        .batch_screen(vec![
            ScreenRequest { name: "A".into(), ..Default::default() },
            ScreenRequest { name: "B".into(), ..Default::default() },
        ])
        .await
        .unwrap();

    assert_eq!(result.results.len(), 2);
    assert_eq!(result.total_duration_ms, 42);
}

#[tokio::test]
async fn test_rate_limit_error() {
    let mut server = mockito::Server::new_async().await;

    server
        .mock("POST", "/v1/screen")
        .with_status(429)
        .with_header("content-type", "application/json")
        .with_body(r#"{"error":"Rate limit exceeded","code":"RATE_LIMIT_EXCEEDED","request_id":"rl-1"}"#)
        .create_async()
        .await;

    let client = Verifex::new("key").with_base_url(&server.url());
    let err = client
        .screen(ScreenRequest { name: "test".into(), ..Default::default() })
        .await
        .unwrap_err();

    assert!(err.is_rate_limit());
}

#[tokio::test]
async fn test_quota_exceeded_error() {
    let mut server = mockito::Server::new_async().await;

    server
        .mock("POST", "/v1/screen")
        .with_status(402)
        .with_header("content-type", "application/json")
        .with_body(r#"{"error":"Quota exceeded","code":"QUOTA_EXCEEDED"}"#)
        .create_async()
        .await;

    let client = Verifex::new("key").with_base_url(&server.url());
    let err = client
        .screen(ScreenRequest { name: "test".into(), ..Default::default() })
        .await
        .unwrap_err();

    assert!(err.is_quota_exceeded());
}

#[tokio::test]
async fn test_builder_pattern() {
    let client = Verifex::new("key")
        .with_base_url("https://custom.api.com")
        .with_timeout(std::time::Duration::from_secs(5));

    // Verify it compiles and can call health (will fail on network, that's fine)
    let _ = client.health().await;
}