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::io::{self, Write};
use std::os::unix::fs::symlink;

use super::{USAGE, canonical_binary, write_outcome, write_report};
use crate::install::tests::{Fixture, TestResult};

struct FailedWriter;
impl Write for FailedWriter {
    fn write(&mut self, _: &[u8]) -> io::Result<usize> {
        Err(io::Error::other("fixture output refused"))
    }
    fn flush(&mut self) -> io::Result<()> {
        Ok(())
    }
}

#[test]
fn a_symlinked_operator_is_canonicalised_before_regular_file_validation() -> TestResult {
    let fixture = Fixture::new()?;
    let link = fixture.directory.path().join("operator-link");
    symlink(&fixture.binary, &link)?;
    let source = canonical_binary(&link)?;
    assert_eq!(source, fixture.binary);
    crate::install::install(&source, &fixture.config, &fixture.bundle)?;
    assert_eq!(
        std::fs::read(fixture.bundle.join("bin/haematite"))?,
        b"operator"
    );
    Ok(())
}

#[test]
fn a_missing_operator_target_is_named_before_installing() -> TestResult {
    let fixture = Fixture::new()?;
    let error = canonical_binary(&fixture.directory.path().join("missing"))
        .err()
        .ok_or("missing binary accepted")?;
    assert_eq!(error.code, "bundle_source_invalid");
    assert!(!fixture.bundle.exists());
    Ok(())
}

#[test]
fn a_failed_report_write_is_named_after_installing() -> TestResult {
    let fixture = Fixture::new()?;
    let report = crate::install::install(&fixture.binary, &fixture.config, &fixture.bundle)?;
    let error = write_report(&mut FailedWriter, &report)
        .err()
        .ok_or("output failure accepted")?;
    assert_eq!(error.code, "bundle_report_write_failed");
    assert!(fixture.bundle.join("bundle.json").is_file());
    Ok(())
}

#[test]
fn usage_explains_the_config_snapshot_and_conflicting_repeat() {
    assert!(USAGE.contains("snapshot"));
    assert!(USAGE.contains("socket or data_dir"));
    assert!(USAGE.contains("new bundle directory"));
    assert!(USAGE.contains("bundle_conflict"));
}

#[test]
fn every_outcome_names_its_stage_list_on_stderr_and_only_success_on_stdout() -> TestResult {
    for success in [false, true] {
        let fixture = Fixture::new()?;
        let path = fixture.directory.path().join(".haem-stage-prior");
        std::fs::create_dir(&path)?;
        let path = std::fs::canonicalize(&path)?;
        let stages = vec![path.to_str().ok_or("stage path")?.to_owned()];
        if !success {
            std::fs::remove_file(&fixture.binary)?;
        }
        let outcome = crate::install::install(&fixture.binary, &fixture.config, &fixture.bundle);
        let mut output = Vec::new();
        let mut diagnostics = Vec::new();
        let result = write_outcome(&mut output, &mut diagnostics, outcome);
        let text = std::str::from_utf8(&diagnostics)?;
        assert_eq!(text.lines().count(), 1);
        let list: Vec<String> = serde_json::from_str(
            text.strip_prefix("bundle_stale_stages: ")
                .ok_or("stage diagnostic missing")?,
        )?;
        assert_eq!(list, stages);
        if success {
            result?;
            let report: serde_json::Value = serde_json::from_slice(&output)?;
            assert_eq!(report["stale_stages"], serde_json::json!(stages));
        } else {
            let error = result.err().ok_or("failed install succeeded")?;
            assert_eq!(error.code, "bundle_source_invalid");
            assert_eq!(error.stale_stages, stages);
            assert!(output.is_empty());
        }
        assert!(path.is_dir());
    }
    let mut output = Vec::new();
    let mut diagnostics = Vec::new();
    let result = write_outcome(
        &mut output,
        &mut diagnostics,
        Err(crate::install::Error::new(
            "bundle_arguments_invalid",
            "invalid arguments",
        )),
    );
    assert_eq!(
        result.err().ok_or("invalid arguments accepted")?.code,
        "bundle_arguments_invalid"
    );
    assert_eq!(diagnostics, b"bundle_stale_stages: []\n");
    assert!(output.is_empty());
    Ok(())
}

#[test]
fn stderr_failure_preserves_the_primary_refusal_and_stage_metadata() -> TestResult {
    let stages = vec![".haem-stage-prior".to_owned()];
    let original = crate::install::Error::new("bundle_source_invalid", "source missing")
        .with_stale_stages(stages.clone());
    let mut output = Vec::new();
    let error = write_outcome(&mut output, &mut FailedWriter, Err(original))
        .err()
        .ok_or("stderr failure accepted")?;
    assert_eq!(error.code, "bundle_source_invalid");
    assert_eq!(error.stale_stages, stages);
    assert!(error.to_string().contains("bundle_report_write_failed"));
    assert!(output.is_empty());
    Ok(())
}

#[test]
fn stdout_failure_retains_the_reported_stages() -> TestResult {
    let fixture = Fixture::new()?;
    let path = fixture.directory.path().join(".haem-stage-prior");
    std::fs::create_dir(&path)?;
    let path = std::fs::canonicalize(&path)?;
    let report = crate::install::install(&fixture.binary, &fixture.config, &fixture.bundle)?;
    let mut diagnostics = Vec::new();
    let error = write_outcome(&mut FailedWriter, &mut diagnostics, Ok(report))
        .err()
        .ok_or("stdout failure accepted")?;
    assert_eq!(error.code, "bundle_report_write_failed");
    assert_eq!(error.stale_stages, [path.to_str().ok_or("stage path")?]);
    assert!(!diagnostics.is_empty());
    assert!(path.is_dir());
    Ok(())
}

#[test]
fn current_executable_resolution_failure_carries_the_existing_stages() -> TestResult {
    let fixture = Fixture::new()?;
    let path = fixture.directory.path().join(".haem-stage-prior");
    std::fs::create_dir(&path)?;
    let path = std::fs::canonicalize(&path)?;
    let error =
        crate::install::bundle::install_current(&fixture.config, &fixture.bundle, None, || {
            Err(crate::install::Error::new(
                "bundle_source_invalid",
                "current executable missing",
            ))
        })
        .err()
        .ok_or("source failure accepted")?;
    assert_eq!(error.code, "bundle_source_invalid");
    assert_eq!(error.stale_stages, [path.to_str().ok_or("stage path")?]);
    let mut diagnostics = Vec::new();
    let result = write_outcome(&mut Vec::new(), &mut diagnostics, Err(error));
    assert!(result.is_err());
    assert!(std::str::from_utf8(&diagnostics)?.contains(".haem-stage-prior"));
    assert!(path.is_dir());
    assert!(!fixture.bundle.exists());
    Ok(())
}

#[test]
fn install_accepts_the_complete_agent_proof() -> TestResult {
    let key = tempfile::NamedTempFile::new()?;
    super::parse(
        [
            "--bundle",
            "bundle",
            "--config",
            "service.json",
            "--lys-issuer",
            "https://lys.example",
            "--lys-agent",
            "agent-1",
            "--key-file",
            key.path().to_str().ok_or("key path")?,
        ]
        .into_iter()
        .map(str::to_owned),
    )?;
    Ok(())
}

#[test]
fn install_refuses_every_partial_agent_group_by_name() -> TestResult {
    let options = [
        ["--lys-issuer", "https://lys.example"],
        ["--lys-agent", "agent-1"],
        ["--key-file", "/key.json"],
    ];
    for mask in [1, 2, 3, 4, 5] {
        let mut args = vec!["--bundle", "bundle", "--config", "service.json"];
        for (index, option) in options.iter().enumerate() {
            if mask & (1 << index) != 0 {
                args.extend(option);
            }
        }
        let error = super::parse(args.into_iter().map(str::to_owned))
            .err()
            .ok_or("partial proof accepted")?;
        assert_eq!(error.code, "bundle_lys_agent_incomplete");
        for flag in ["--lys-issuer", "--lys-agent", "--key-file"] {
            assert!(error.message.contains(flag));
        }
    }
    Ok(())
}