udb 0.3.0

Universal Data Broker — a Rust gRPC broker over multiple databases (Postgres, MySQL, SQLite, MongoDB, ClickHouse, Cassandra, MSSQL, Redis, Qdrant, S3, Neo4j, …) with per-tenant RLS, 2PC, sagas, and CDC.
Documentation
use super::support::*;
use crate::proto::udb::core::apikey::entity::v1 as apikey_entity_pb;
use crate::proto::udb::core::apikey::services::v1 as apikey_pb;
use crate::proto::udb::core::apikey::services::v1::api_key_service_server::ApiKeyService;
use crate::proto::udb::core::common::v1 as common_pb;
use tonic::Request;
use uuid::Uuid;

#[tokio::test]
#[ignore = "requires live Postgres; run with UDB_LIVE_AUTH_TESTS=1 cargo test --lib live_postgres_apikey_roundtrip -- --ignored --nocapture"]
async fn live_postgres_apikey_roundtrip() {
    let _guard = live_auth_db_lock().lock().await;
    let pool = live_pg_pool().await;
    migrate_native_auth_db(&pool).await;
    let svc = api_key_service(pool.clone());
    let principal_id = Uuid::new_v4().to_string();

    let created = svc
        .create_api_key(Request::new(apikey_pb::CreateApiKeyRequest {
            name: "live-key".to_string(),
            owner_id: principal_id.clone(),
            scopes: vec!["data:read".to_string()],
            context: Some(common_pb::RequestContext {
                principal_id: principal_id.clone(),
                tenant: Some(common_pb::TenantContext {
                    tenant_id: "acme".to_string(),
                    project_id: "billing".to_string(),
                    ..Default::default()
                }),
                ..Default::default()
            }),
            ..Default::default()
        }))
        .await
        .expect("create API key through Postgres store")
        .into_inner();
    let key = created.key.expect("created API key");
    assert!(created.plain_key.starts_with("udbk_"));

    let valid = svc
        .validate_api_key(Request::new(apikey_pb::ValidateApiKeyRequest {
            plain_key: created.plain_key.clone(),
            required_scope: "data:read".to_string(),
            ..Default::default()
        }))
        .await
        .expect("validate API key through Postgres store")
        .into_inner();
    assert!(valid.valid);
    assert_eq!(valid.owner_id, principal_id);

    svc.revoke_api_key(Request::new(apikey_pb::RevokeApiKeyRequest {
        key_id: key.key_id,
        revoke_reason: "live_test".to_string(),
        ..Default::default()
    }))
    .await
    .expect("revoke API key through Postgres store");

    let invalid = svc
        .validate_api_key(Request::new(apikey_pb::ValidateApiKeyRequest {
            plain_key: created.plain_key,
            ..Default::default()
        }))
        .await
        .expect("validate revoked API key")
        .into_inner();
    assert!(!invalid.valid);

    cleanup_native_auth_db(&pool).await;
}

#[tokio::test]
#[ignore = "requires live Postgres; run with UDB_LIVE_AUTH_TESTS=1 cargo test --lib live_postgres_apikey_admin_lifecycle -- --ignored --nocapture"]
async fn live_postgres_apikey_admin_lifecycle() {
    let _guard = live_auth_db_lock().lock().await;
    let pool = live_pg_pool().await;
    migrate_native_auth_db(&pool).await;
    let svc = api_key_service(pool.clone());
    let owner_id = Uuid::new_v4().to_string();

    let created = svc
        .create_api_key(Request::new(apikey_pb::CreateApiKeyRequest {
            name: "admin-live-key".to_string(),
            owner_type: apikey_entity_pb::ApiKeyOwnerType::ServiceAccount as i32,
            owner_id: owner_id.clone(),
            scopes: vec!["data:read".to_string()],
            context: Some(common_pb::RequestContext {
                principal_id: owner_id.clone(),
                tenant: Some(common_pb::TenantContext {
                    tenant_id: "acme".to_string(),
                    project_id: "billing".to_string(),
                    ..Default::default()
                }),
                ..Default::default()
            }),
            ..Default::default()
        }))
        .await
        .expect("create admin API key")
        .into_inner();
    let key_id = created.key.as_ref().expect("created key").key_id.clone();

    let missing_scope = svc
        .validate_api_key(Request::new(apikey_pb::ValidateApiKeyRequest {
            plain_key: created.plain_key.clone(),
            required_scope: "data:write".to_string(),
            ..Default::default()
        }))
        .await
        .expect("validate missing scope")
        .into_inner();
    assert!(!missing_scope.valid);

    let listed = svc
        .list_api_keys(Request::new(apikey_pb::ListApiKeysRequest {
            owner_id: owner_id.clone(),
            status: apikey_entity_pb::ApiKeyStatus::Active as i32,
            ..Default::default()
        }))
        .await
        .expect("list active API keys")
        .into_inner();
    assert_eq!(listed.keys.len(), 1);
    assert_eq!(listed.keys[0].key_id, key_id);

    let updated = svc
        .update_api_key(Request::new(apikey_pb::UpdateApiKeyRequest {
            key_id: key_id.clone(),
            scopes: vec!["data:read".to_string(), "data:write".to_string()],
            ..Default::default()
        }))
        .await
        .expect("update API key scopes")
        .into_inner()
        .key
        .expect("updated key");
    assert!(updated.scopes_json.contains("data:write"));

    let write_ok = svc
        .validate_api_key(Request::new(apikey_pb::ValidateApiKeyRequest {
            plain_key: created.plain_key,
            required_scope: "data:write".to_string(),
            ..Default::default()
        }))
        .await
        .expect("validate updated scope")
        .into_inner();
    assert!(write_ok.valid);
    assert_eq!(write_ok.owner_id, owner_id);

    let got = svc
        .get_api_key(Request::new(apikey_pb::GetApiKeyRequest { key_id }))
        .await
        .expect("get API key")
        .into_inner()
        .key
        .expect("got key");
    assert_eq!(got.owner_id, owner_id);

    cleanup_native_auth_db(&pool).await;
}

#[tokio::test]
#[ignore = "requires live Postgres; run with UDB_LIVE_AUTH_TESTS=1 cargo test --lib live_postgres_apikey_usage_stats_contract -- --ignored --nocapture"]
async fn live_postgres_apikey_usage_stats_contract() {
    let _guard = live_auth_db_lock().lock().await;
    let pool = live_pg_pool().await;
    migrate_native_auth_db(&pool).await;
    let svc = api_key_service(pool.clone());

    let stats = svc
        .get_api_key_usage_stats(Request::new(apikey_pb::GetApiKeyUsageStatsRequest {
            key_id: Uuid::new_v4().to_string(),
            ..Default::default()
        }))
        .await
        .expect("usage stats endpoint reads the live Postgres usage table")
        .into_inner();
    assert_eq!(stats.total_requests, 0);
    assert!(stats.stats.is_empty());

    cleanup_native_auth_db(&pool).await;
}