| 1 |
|
| 2 |
|
| 3 |
|
| 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 |
|
| 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("SPECTRA_HOME", dir.join("owner-home")) |
| 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 |
|
| 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 |
} |