use crate::Fixture;
use assert_cmd::prelude::*;
use predicates::prelude::*;
use std::fs;
use std::process::Command;
use tempfile::tempdir;
struct StoredFixture {
casdir: tempfile::TempDir,
ept: Fixture,
}
fn store_sample(word: &str) -> StoredFixture {
let casdir = tempdir().unwrap();
let ept = Fixture::copy("sample/test.ept");
Command::cargo_bin("enprot")
.unwrap()
.arg("-c")
.arg(casdir.path())
.arg("store")
.arg("-w")
.arg(word)
.arg(&ept.path)
.assert()
.success();
StoredFixture { casdir, ept }
}
const HASH_BOND: &str = "d094e230861eb0ab43b895b8ecdeeb9e3a7e4a88239341a81da832ac181feaab";
const HASH_SECRET: &str = "575d69f5b0034279bc3ef164e94287e6366e9df76729895a302a66a8817cf306";
fn write_orphan(casdir: &std::path::Path, hash: &str, content: &[u8]) {
fs::write(casdir.join(hash), content).unwrap();
}
const ORPHAN_HASH_A: &str = "aaaa1100000000000000000000000000000000000000000000000000000000aa";
const ORPHAN_HASH_B: &str = "bbbb2200000000000000000000000000000000000000000000000000000000bb";
#[test]
fn cas_gc_keeps_referenced_blobs() {
let s = store_sample("Agent_007");
Command::cargo_bin("enprot")
.unwrap()
.arg("-c")
.arg(s.casdir.path())
.arg("cas")
.arg("gc")
.arg(&s.ept.path)
.assert()
.success()
.stderr(predicates::str::contains("0 deleted").or(predicates::str::contains("deleted 0")));
assert!(s.casdir.path().join(HASH_BOND).exists());
assert!(s.casdir.path().join(HASH_SECRET).exists());
}
#[test]
fn cas_gc_deletes_orphans() {
let s = store_sample("Agent_007");
write_orphan(s.casdir.path(), ORPHAN_HASH_A, b"orphan content a");
Command::cargo_bin("enprot")
.unwrap()
.arg("-c")
.arg(s.casdir.path())
.arg("cas")
.arg("gc")
.arg(&s.ept.path)
.assert()
.success()
.stderr(predicates::str::contains("1 deleted").or(predicates::str::contains("deleted 1")));
assert!(s.casdir.path().join(HASH_BOND).exists());
assert!(s.casdir.path().join(HASH_SECRET).exists());
assert!(!s.casdir.path().join(ORPHAN_HASH_A).exists());
}
#[test]
fn cas_gc_dry_run_preserves_everything() {
let s = store_sample("Agent_007");
write_orphan(s.casdir.path(), ORPHAN_HASH_A, b"orphan content a");
write_orphan(s.casdir.path(), ORPHAN_HASH_B, b"orphan content b");
Command::cargo_bin("enprot")
.unwrap()
.arg("--dry-run")
.arg("-c")
.arg(s.casdir.path())
.arg("cas")
.arg("gc")
.arg(&s.ept.path)
.assert()
.success()
.stderr(predicates::str::contains("would delete 2"));
assert!(s.casdir.path().join(ORPHAN_HASH_A).exists());
assert!(s.casdir.path().join(ORPHAN_HASH_B).exists());
}
#[test]
fn cas_gc_min_age_protests_young_blobs() {
let s = store_sample("Agent_007");
write_orphan(s.casdir.path(), ORPHAN_HASH_A, b"orphan content a");
Command::cargo_bin("enprot")
.unwrap()
.arg("-c")
.arg(s.casdir.path())
.arg("cas")
.arg("gc")
.arg("--min-age")
.arg("3600")
.arg(&s.ept.path)
.assert()
.success()
.stderr(predicates::str::contains("0 deleted").or(predicates::str::contains("deleted 0")));
assert!(s.casdir.path().join(ORPHAN_HASH_A).exists());
}
#[test]
fn cas_gc_json_output() {
let s = store_sample("Agent_007");
write_orphan(s.casdir.path(), ORPHAN_HASH_A, b"orphan content a");
Command::cargo_bin("enprot")
.unwrap()
.arg("-c")
.arg(s.casdir.path())
.arg("--format")
.arg("json")
.arg("cas")
.arg("gc")
.arg(&s.ept.path)
.assert()
.success()
.stdout(
predicates::str::contains("\"$schema\": \"enprot/v1\"")
.and(predicates::str::contains("\"total\": 3"))
.and(predicates::str::contains("\"kept\": 2"))
.and(predicates::str::contains("\"deleted\": 1"))
.and(predicates::str::contains(ORPHAN_HASH_A))
.and(predicates::str::contains("\"dry_run\": false")),
);
}
#[test]
fn cas_gc_ignores_non_blob_files() {
let s = store_sample("Agent_007");
fs::write(s.casdir.path().join("README.txt"), b"not a blob").unwrap();
fs::write(
s.casdir
.path()
.join("ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789"),
b"uppercase",
)
.unwrap();
Command::cargo_bin("enprot")
.unwrap()
.arg("-c")
.arg(s.casdir.path())
.arg("cas")
.arg("gc")
.arg(&s.ept.path)
.assert()
.success();
assert!(s.casdir.path().join("README.txt").exists());
}