rskit-database 0.1.0-alpha.2

Database contracts with in-memory default and opt-in adapter backends
Documentation
use std::num::NonZeroUsize;
use std::time::Duration;

use rskit_database::{
    DatabaseClient, DatabaseConfig, DatabaseQuery, DatabaseRegistry, FindOpts, InMemoryDatabase,
    MemoryDatabaseConfig, TenantScope, register_memory,
};

#[test]
fn config_defaults_to_memory_backend() {
    let cfg = DatabaseConfig::default();

    assert_eq!(cfg.backend, "memory");
    assert_eq!(cfg.memory.name, "default");
    assert_eq!(cfg.max_connections, 10);
    assert_eq!(cfg.min_connections, 1);
    assert_eq!(cfg.connect_timeout, Duration::from_secs(30));
}

#[tokio::test]
async fn registry_builds_memory_backend_from_config() {
    let mut registry = DatabaseRegistry::new();
    register_memory(&mut registry).unwrap();

    let db = registry.build(&DatabaseConfig::default()).await.unwrap();
    let result = db.execute(DatabaseQuery::new("SELECT 1")).await.unwrap();

    assert_eq!(result.rows_affected, 1);
}

#[tokio::test]
async fn unregistered_backend_returns_error() {
    let registry = DatabaseRegistry::new();

    let err = registry
        .build(&DatabaseConfig::default())
        .await
        .err()
        .unwrap();

    assert!(err.to_string().contains("not registered"));
}

#[tokio::test]
async fn memory_backend_rejects_empty_statement() {
    let db = InMemoryDatabase::new(MemoryDatabaseConfig::default());

    let err = db.execute(DatabaseQuery::new(" ")).await.err().unwrap();

    assert!(err.to_string().contains("statement is required"));
}

#[tokio::test]
async fn memory_backend_records_bounded_history() {
    let db = InMemoryDatabase::new(MemoryDatabaseConfig {
        name: "test".into(),
        statement_history: 1,
    });

    db.execute(DatabaseQuery::new("SELECT 1")).await.unwrap();
    db.execute(DatabaseQuery::new("SELECT 2")).await.unwrap();

    let history = db.recorded_queries();
    assert_eq!(history.len(), 1);
    assert_eq!(history[0].statement, "SELECT 2");
}

#[test]
fn find_opts_builder_sets_values() {
    let opts = FindOpts::default()
        .with_limit(10)
        .with_offset(20)
        .order_by("created_at DESC")
        .filter("status", "active");

    assert_eq!(opts.limit, Some(10));
    assert_eq!(opts.offset, Some(20));
    assert_eq!(opts.order_by, vec!["created_at DESC"]);
    assert_eq!(opts.filters.len(), 1);
}

#[test]
fn tenant_scope_builds_predicates() {
    let scope = TenantScope::new("workspace_id", "ws-1").unwrap();
    let first_param = NonZeroUsize::new(1).unwrap();

    assert_eq!(scope.where_clause(first_param), "workspace_id = $1");
    assert_eq!(
        scope.apply("SELECT * FROM tasks", first_param),
        "SELECT * FROM tasks WHERE workspace_id = $1"
    );
    assert_eq!(scope.value(), "ws-1");
}