haematite-cli 0.21.3

The haematite command: serve a store, its door and Studio, answer MCP, query with jq, and host the store's assistant seat
Documentation
use std::error::Error;
use std::num::{NonZeroU32, NonZeroUsize};
use std::time::Duration;

use haematite::{DatabaseConfig, NodeCacheBudget, Retention};

use super::{Limits, Listener, Service};
use crate::serve::storage::Storage;

type TestResult = Result<(), Box<dyn Error>>;

fn service(directory: &std::path::Path) -> Result<Service, Box<dyn Error>> {
    let storage = Storage::create(
        DatabaseConfig {
            data_dir: directory.join("database"),
            shard_count: 1,
            distributed: None,
            executor_threads: Some(1),
            node_cache_budget: Some(NodeCacheBudget::Unlimited),
            retention: Retention::reclaim(0),
        },
        NodeCacheBudget::Unlimited,
        Duration::from_secs(1),
    )?;
    Ok(Service::new(storage, NonZeroU32::MIN)?)
}

fn limits() -> Limits {
    Limits {
        max_frame: NonZeroU32::MIN,
        max_clients: NonZeroUsize::MIN,
        frame_timeout: Duration::from_secs(1),
    }
}

#[test]
fn a_refused_bind_does_not_activate_the_store() -> TestResult {
    let directory = tempfile::tempdir()?;
    let service = service(directory.path())?;
    let socket = directory.path().join("service.socket");
    let first = Listener::bind(&socket)?;
    assert!(Listener::bind(&socket).is_err());
    let status = service.reclaim_status()?;
    assert_eq!(status.shards.len(), 1);
    match &status.shards[0] {
        haematite::db::ShardReclaimAnswer::Answered { status, .. } => {
            assert_eq!(status.passes_finished, 0);
            assert_eq!(status.given_back_nodes, 0);
            assert_eq!(status.phase, haematite::db::ReclaimPhase::Idle);
        }
        haematite::db::ShardReclaimAnswer::NotAnswering { reason, .. } => {
            return Err(reason.clone().into());
        }
    }
    drop(first);
    Ok(())
}

#[test]
fn listener_readiness_activates_only_after_setup_and_is_idempotent() -> TestResult {
    let directory = tempfile::tempdir()?;
    let service = service(directory.path())?;
    let listener = Listener::bind(&directory.path().join("service.socket"))?;
    let mut invalid = limits();
    invalid.frame_timeout = Duration::ZERO;
    assert!(Listener::begin_serving(&service, invalid).is_err());
    assert!(service.quiesce_reclaim().is_err());
    Listener::begin_serving(&service, limits())?;
    Listener::begin_serving(&service, limits())?;
    service.quiesce_reclaim()?;
    let status = service.reclaim_status()?;
    assert_eq!(status.shards.len(), 1);
    match &status.shards[0] {
        haematite::db::ShardReclaimAnswer::Answered { status, .. } => {
            assert_eq!(status.phase, haematite::db::ReclaimPhase::Idle);
            assert_eq!(status.passes_finished, 1);
        }
        haematite::db::ShardReclaimAnswer::NotAnswering { reason, .. } => {
            return Err(reason.clone().into());
        }
    }
    Ok(())
}