haematite-cli 0.21.10

The haematite command: serve a store, its door and Studio, answer MCP, query with jq, and host the store's assistant seat
Documentation
#![forbid(unsafe_code)]
#![cfg(unix)]

#[path = "../../haematite/tests/common/runtime_options.rs"]
mod runtime_options;

use std::collections::BTreeMap;
use std::error::Error;
use std::fs;
use std::os::unix::fs::PermissionsExt;
use std::path::{Path, PathBuf};
use std::process::{Command, Output};

use haematite::{LeafLog, NodeCacheBudget};

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

fn files(path: &Path) -> Result<BTreeMap<PathBuf, Vec<u8>>, Box<dyn Error>> {
    let mut found = BTreeMap::new();
    let mut pending = vec![path.to_path_buf()];
    while let Some(directory) = pending.pop() {
        for entry in fs::read_dir(directory)? {
            let entry = entry?;
            let kind = entry.file_type()?;
            if kind.is_dir() {
                pending.push(entry.path());
            } else if kind.is_file() {
                found.insert(
                    entry.path().strip_prefix(path)?.to_path_buf(),
                    fs::read(entry.path())?,
                );
            } else {
                return Err("fixture contains a non-file entry".into());
            }
        }
    }
    Ok(found)
}

fn refused(output: &Output) -> TestResult {
    let stderr = std::str::from_utf8(&output.stderr)?;
    assert_eq!(output.status.code(), Some(1), "{stderr}");
    assert!(stderr.contains("LeafLogStore"), "{stderr}");
    assert!(output.stdout.is_empty());
    Ok(())
}

#[test]
fn serve_refuses_the_leaf_mark_before_creating_a_lock_or_socket() -> TestResult {
    let directory = tempfile::tempdir()?;
    fs::set_permissions(directory.path(), fs::Permissions::from_mode(0o700))?;
    let path = directory.path().join("log");
    drop(LeafLog::create(
        &path,
        b"origin",
        NodeCacheBudget::Unlimited,
        runtime_options::runtime_options(),
    )?);
    fs::remove_file(path.join("writer.lock"))?;
    let socket = directory.path().join("service.sock");
    let config = directory.path().join("serve.json");
    fs::write(
        &config,
        serde_json::to_vec(&serde_json::json!({
            "data_dir": path,
            "socket": socket,
            "branch_cache_bytes_per_shard": 65_536,
            "max_frame": 1_048_576,
            "max_clients": 1,
            "max_page": 1,
            "timeout_ms": 1_000,
            "create": {"shards": 1, "cache_bytes_total": 65_536, "executor_threads": 1}
        }))?,
    )?;
    let before = files(&path)?;
    for create in [false, true] {
        let mut command = Command::new(env!("CARGO_BIN_EXE_haematite"));
        command.arg("serve").arg("--config").arg(&config);
        if create {
            command.arg("--create");
        }
        refused(&command.output()?)?;
        assert_eq!(files(&path)?, before);
        assert!(!path.join("writer.lock").try_exists()?);
        assert!(!socket.try_exists()?);
    }
    Ok(())
}

#[test]
fn migration_and_abort_refuse_the_leaf_mark_without_creating_a_lock() -> TestResult {
    let directory = tempfile::tempdir()?;
    let path = directory.path().join("log");
    drop(LeafLog::create(
        &path,
        b"origin",
        NodeCacheBudget::Unlimited,
        runtime_options::runtime_options(),
    )?);
    fs::remove_file(path.join("writer.lock"))?;
    let before = files(&path)?;
    for direction in ["--migrate", "--abort"] {
        let output = Command::new(env!("CARGO_BIN_EXE_haematite"))
            .arg("migrate-chunking")
            .arg(&path)
            .arg(direction)
            .arg("--json")
            .output()?;
        refused(&output)?;
        assert_eq!(files(&path)?, before);
        assert!(!path.join("writer.lock").try_exists()?);
    }
    Ok(())
}

#[test]
fn stats_and_offline_vacuum_dry_run_still_answer_for_a_leaf_log() -> TestResult {
    let directory = tempfile::tempdir()?;
    let path = directory.path().join("log");
    drop(LeafLog::create(
        &path,
        b"origin",
        NodeCacheBudget::Unlimited,
        runtime_options::runtime_options(),
    )?);
    let before = files(&path)?;
    for operation in ["stats", "vacuum"] {
        let mut command = Command::new(env!("CARGO_BIN_EXE_haematite"));
        command.arg(operation).arg(&path).arg("--json");
        if operation == "vacuum" {
            command.arg("--dry-run").arg("--attest-metadata-complete");
        }
        let output = command.output()?;
        assert_eq!(
            output.status.code(),
            Some(0),
            "{}",
            std::str::from_utf8(&output.stderr)?
        );
        let report: serde_json::Value = serde_json::from_slice(&output.stdout)?;
        assert_eq!(report["shard_count"], 1);
        assert_eq!(
            report["mode"],
            if operation == "stats" {
                "stats"
            } else {
                "sweep"
            }
        );
        assert_eq!(files(&path)?, before);
        if operation == "vacuum" {
            assert_eq!(report["sweep"]["executed"], false);
        }
    }
    let (log, _) = LeafLog::open(&path, runtime_options::runtime_options())?;
    assert_eq!(log.origin(), b"origin");
    assert_eq!(log.extent(), 0);
    Ok(())
}