OXYGENHUB
# e071e91a14ef spectra / tests / cli_admin.rs
3.0 KiB · Rust · last changed Identities instead of tokens: init, owned agents as delegates, verified replay of identities, request authentication, one writer (#4) 2 days ago source blame history raw
1
//! `spectra admin verify|check|rebuild` against a real ledger, run as the
2
//! built binary in a hermetic environment: a temporary data directory and
3
//! `OXY_HOME`, never the user's own.
4
5
use spectra_core::event::NewEvent;
6
use spectra_core::keys::KeyPair;
7
use spectra_core::ledger::Ledger;
8
use std::path::Path;
9
use std::process::Command;
10
11
/// The binary with every directory it might read pointed at `dir`.
12
fn hermetic(dir: &Path) -> Command {
13
    let mut c = Command::new(env!("CARGO_BIN_EXE_spectra"));
14
    c.env("SPECTRA_DATA", dir.join("data"))
15
        .env("OXY_HOME", dir.join("oxy"))
16
        .env("HOME", dir)
17
        .env("XDG_DATA_HOME", dir.join("xdg"))
18
        .env("NO_COLOR", "1");
19
    c
20
}
21
22
async fn ledger_with_events(data: &Path, n: usize) {
23
    std::fs::create_dir_all(data).unwrap();
24
    let instance = KeyPair::instance(data, "test").unwrap();
25
    let ledger = Ledger::open(&data.join("spectra.db"), Some(instance.clone())).await.unwrap();
26
    // As `spectra init` records it: signed by the instance key.
27
    ledger
28
        .append_one(spectra_core::principals::signed_event(
29
            &instance,
30
            "system",
31
            "identity.instance",
32
            spectra_core::principals::identity_payload(&instance.identity),
33
        ))
34
        .await
35
        .unwrap();
36
    for i in 0..n {
37
        ledger.append_one(NewEvent::system("test.message", serde_json::json!({ "n": i }))).await.unwrap();
38
    }
39
    ledger.checkpoint().await.unwrap();
40
}
41
42
#[tokio::test]
43
async fn verify_check_and_rebuild_on_a_clean_and_a_tampered_ledger() {
44
    let dir = tempfile::tempdir().unwrap();
45
    let data = dir.path().join("data");
46
    ledger_with_events(&data, 40).await;
47
48
    let out = hermetic(dir.path()).args(["admin", "verify", "--json"]).output().unwrap();
49
    assert!(out.status.success(), "{}", String::from_utf8_lossy(&out.stderr));
50
    let v: serde_json::Value = serde_json::from_slice(&out.stdout).expect("one JSON document on stdout");
51
    assert_eq!(v["events"], 42);
52
    assert_eq!(v["findings"].as_array().unwrap().len(), 0);
53
    assert_eq!(v["uncovered"], 0);
54
55
    assert!(hermetic(dir.path()).args(["admin", "check"]).status().unwrap().success());
56
    assert!(hermetic(dir.path()).args(["admin", "rebuild"]).status().unwrap().success());
57
58
    let pool = sqlx::SqlitePool::connect(&format!("sqlite://{}", data.join("spectra.db").display())).await.unwrap();
59
    sqlx::query("UPDATE events SET actor = 'agent:mallory' WHERE seq = 10").execute(&pool).await.unwrap();
60
    pool.close().await;
61
    let out = hermetic(dir.path()).args(["admin", "verify"]).output().unwrap();
62
    assert_eq!(out.status.code(), Some(1));
63
    let text = String::from_utf8_lossy(&out.stdout);
64
    assert!(text.contains("does not verify") && text.contains("#10") && text.contains("HashMismatch"), "{text}");
65
}
66
67
#[test]
68
fn a_missing_data_directory_says_what_to_do() {
69
    let dir = tempfile::tempdir().unwrap();
70
    let out = hermetic(dir.path()).args(["admin", "verify"]).output().unwrap();
71
    assert_eq!(out.status.code(), Some(1));
72
    assert!(String::from_utf8_lossy(&out.stderr).contains("spectra init"));
73
}