use std::path::Path;
use std::process::Command;
use tempfile::TempDir;
fn aristo_bin() -> &'static str {
env!("CARGO_BIN_EXE_aristo")
}
fn aristo_in(workspace: &Path) -> Command {
let mut c = Command::new(aristo_bin());
c.env_clear();
if let Ok(path) = std::env::var("PATH") {
c.env("PATH", path);
}
#[cfg(target_os = "macos")]
if let Ok(p) = std::env::var("DYLD_FALLBACK_LIBRARY_PATH") {
c.env("DYLD_FALLBACK_LIBRARY_PATH", p);
}
let home = workspace.join("home");
std::fs::create_dir_all(&home).unwrap();
c.env("HOME", &home);
c.env("XDG_CONFIG_HOME", home.join("xdg"));
c.current_dir(workspace);
c
}
fn setup_workspace(workspace_text: &str, source_body: &str) -> TempDir {
let tmp = TempDir::new().unwrap();
std::fs::write(tmp.path().join("aristo.toml"), workspace_text).unwrap();
std::fs::create_dir_all(tmp.path().join("src")).unwrap();
std::fs::create_dir_all(tmp.path().join(".aristo")).unwrap();
std::fs::write(tmp.path().join("src").join("lib.rs"), source_body).unwrap();
std::fs::write(
tmp.path().join("Cargo.toml"),
r#"[package]
name = "sandbox"
version = "0.0.1"
edition = "2021"
"#,
)
.unwrap();
tmp
}
fn write_match_fixture_one_match(fixture_dir: &Path) {
std::fs::create_dir_all(fixture_dir).unwrap();
let body = r#"
effective_scopes = [":vanilla"]
canon_version = "v0.2.0"
matched_at = "2026-06-15T09:14:22Z"
results = [
[
{ canon_id = "cell_written_exactly_once_per_page_edit", version = "v0.2.1", canonical_text = "edit_page writes each cell exactly once", confidence = 0.92, scope = ":vanilla", prefix_tier = "aristos:", backed_by = "specialized neural checker", linked = "arta_a1b2c3d4", verification = { coverage_level = "tight", test_binaries = ["monotonicity_property"] } }
]
]
"#;
std::fs::write(fixture_dir.join("match.toml"), body).unwrap();
}
fn write_match_fixture_no_matches(fixture_dir: &Path) {
std::fs::create_dir_all(fixture_dir).unwrap();
let body = r#"
effective_scopes = [":vanilla"]
canon_version = "v0.2.0"
matched_at = "2026-06-15T09:14:22Z"
results = [[]]
"#;
std::fs::write(fixture_dir.join("match.toml"), body).unwrap();
}
const ARISTO_TOML_DEFAULT: &str = "";
const ARISTO_TOML_CANON_DISABLED: &str = "[canon]\nenabled = false\n";
const SOURCE_WITH_ONE_INTENT: &str = r#"
#[aristo::intent(
"each cell should be written exactly once per page edit",
id = "edit_page_cell_write_invariant"
)]
pub fn edit_page() {}
"#;
#[test]
fn stamp_surfaces_high_confidence_match() {
let ws = setup_workspace(ARISTO_TOML_DEFAULT, SOURCE_WITH_ONE_INTENT);
let fixture = ws.path().join("fixtures/canon");
write_match_fixture_one_match(&fixture);
let out = aristo_in(ws.path())
.env("ARISTO_CANON_FIXTURE", &fixture)
.args(["stamp"])
.output()
.expect("run aristo stamp");
assert!(
out.status.success(),
"stamp failed: {}",
String::from_utf8_lossy(&out.stderr)
);
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(
stdout.contains("canon-match: 1 new finding"),
"stdout: {stdout}"
);
assert!(stdout.contains("canon v0.2.0"), "stdout: {stdout}");
assert!(
stdout.contains("cell_written_exactly_once_per_page_edit v0.2.1"),
"stdout: {stdout}"
);
assert!(stdout.contains("conf 0.92"), "stdout: {stdout}");
assert!(stdout.contains("aristos: tier"), "stdout: {stdout}");
assert!(
stdout.contains("backed by: specialized neural checker"),
"stdout: {stdout}"
);
assert!(
stdout.contains("review with `aristo critique"),
"stdout: {stdout}"
);
let cache_path = ws.path().join(".aristo/canon-matches.toml");
assert!(cache_path.exists(), "expected canon-matches.toml on disk");
let cache_body = std::fs::read_to_string(&cache_path).unwrap();
assert!(cache_body.contains("cell_written_exactly_once_per_page_edit"));
assert!(cache_body.contains("canon_version = \"v0.2.0\""));
assert!(cache_body.contains("disposition = \"open\""));
}
#[test]
fn stamp_free_tier_skips_canon_with_nudge() {
let ws = setup_workspace(ARISTO_TOML_DEFAULT, SOURCE_WITH_ONE_INTENT);
let out = aristo_in(ws.path())
.args(["stamp"])
.output()
.expect("run aristo stamp");
assert!(
out.status.success(),
"stamp failed: {}",
String::from_utf8_lossy(&out.stderr)
);
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(
stdout.contains("canon-match: skipped (Pro feature)"),
"expected free-tier nudge, got: {stdout}"
);
assert!(stdout.contains("aristo auth login"), "stdout: {stdout}");
assert!(!ws.path().join(".aristo/canon-matches.toml").exists());
}
#[test]
fn stamp_skip_canon_flag_honored() {
let ws = setup_workspace(ARISTO_TOML_DEFAULT, SOURCE_WITH_ONE_INTENT);
let fixture = ws.path().join("fixtures/canon");
write_match_fixture_one_match(&fixture);
let out = aristo_in(ws.path())
.env("ARISTO_CANON_FIXTURE", &fixture)
.args(["stamp", "--skip-canon"])
.output()
.unwrap();
assert!(out.status.success());
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(
stdout.contains("canon-match: skipped (`--skip-canon`)"),
"stdout: {stdout}"
);
assert!(!ws.path().join(".aristo/canon-matches.toml").exists());
}
#[test]
fn stamp_canon_disabled_in_config() {
let ws = setup_workspace(ARISTO_TOML_CANON_DISABLED, SOURCE_WITH_ONE_INTENT);
let fixture = ws.path().join("fixtures/canon");
write_match_fixture_one_match(&fixture);
let out = aristo_in(ws.path())
.env("ARISTO_CANON_FIXTURE", &fixture)
.args(["stamp"])
.output()
.unwrap();
assert!(out.status.success());
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(
stdout.contains("canon-match: skipped (disabled"),
"stdout: {stdout}"
);
assert!(
stdout.contains("[canon] enabled = false"),
"stdout: {stdout}"
);
}
#[test]
fn stamp_cache_hit_skips_api_call() {
let ws = setup_workspace(ARISTO_TOML_DEFAULT, SOURCE_WITH_ONE_INTENT);
let fixture = ws.path().join("fixtures/canon");
write_match_fixture_one_match(&fixture);
let out = aristo_in(ws.path())
.env("ARISTO_CANON_FIXTURE", &fixture)
.args(["stamp"])
.output()
.unwrap();
assert!(out.status.success());
std::fs::remove_file(fixture.join("match.toml")).unwrap();
let out2 = aristo_in(ws.path())
.env("ARISTO_CANON_FIXTURE", &fixture)
.args(["stamp"])
.output()
.unwrap();
assert!(
out2.status.success(),
"second stamp should succeed via cache hit; stderr: {}",
String::from_utf8_lossy(&out2.stderr)
);
let stdout = String::from_utf8_lossy(&out2.stdout);
assert!(
stdout.contains("canon-match: cache hit"),
"expected cache-hit summary, got: {stdout}"
);
}
#[test]
fn stamp_refresh_canon_invalidates_cache() {
let ws = setup_workspace(ARISTO_TOML_DEFAULT, SOURCE_WITH_ONE_INTENT);
let fixture = ws.path().join("fixtures/canon");
write_match_fixture_one_match(&fixture);
let _ = aristo_in(ws.path())
.env("ARISTO_CANON_FIXTURE", &fixture)
.args(["stamp"])
.output()
.unwrap();
assert!(ws.path().join(".aristo/canon-matches.toml").exists());
write_match_fixture_no_matches(&fixture);
let out2 = aristo_in(ws.path())
.env("ARISTO_CANON_FIXTURE", &fixture)
.args(["stamp", "--refresh-canon"])
.output()
.unwrap();
assert!(out2.status.success());
let stdout = String::from_utf8_lossy(&out2.stdout);
assert!(
stdout.contains("canon-match: 0 new finding"),
"expected refresh to re-query and find 0 matches, got: {stdout}"
);
}
#[test]
fn stamp_unreachable_server_retains_cache() {
let ws = setup_workspace(ARISTO_TOML_DEFAULT, SOURCE_WITH_ONE_INTENT);
let fixture = ws.path().join("fixtures/canon");
write_match_fixture_one_match(&fixture);
let _ = aristo_in(ws.path())
.env("ARISTO_CANON_FIXTURE", &fixture)
.args(["stamp"])
.output()
.unwrap();
let cache_before = std::fs::read_to_string(ws.path().join(".aristo/canon-matches.toml"))
.expect("cache should exist after first stamp");
std::fs::write(
ws.path().join("src/lib.rs"),
r#"
#[aristo::intent(
"each cell is written exactly one time during a page edit",
id = "edit_page_cell_write_invariant"
)]
pub fn edit_page() {}
"#,
)
.unwrap();
std::fs::remove_file(fixture.join("match.toml")).unwrap();
let out = aristo_in(ws.path())
.env("ARISTO_CANON_FIXTURE", &fixture)
.args(["stamp"])
.output()
.expect("run stamp");
assert!(
out.status.success(),
"graceful degradation should not fail stamp; stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(
stdout.contains("canon-match: skipped"),
"expected degraded-skip, got: {stdout}"
);
assert!(
stdout.contains("cached matches retained"),
"stdout: {stdout}"
);
let cache_after = std::fs::read_to_string(ws.path().join(".aristo/canon-matches.toml"))
.expect("cache should be retained");
assert_eq!(cache_before, cache_after, "cache must survive API failure");
}
#[test]
fn stamp_check_mode_does_not_call_canon() {
let ws = setup_workspace(ARISTO_TOML_DEFAULT, SOURCE_WITH_ONE_INTENT);
let fixture = ws.path().join("fixtures/canon");
write_match_fixture_one_match(&fixture);
let _ = aristo_in(ws.path())
.args(["stamp", "--skip-canon"]) .output()
.unwrap();
let _ = std::fs::remove_file(ws.path().join(".aristo/canon-matches.toml"));
let out = aristo_in(ws.path())
.env("ARISTO_CANON_FIXTURE", &fixture)
.args(["stamp", "--check"])
.output()
.expect("run stamp --check");
assert!(
out.status.success(),
"check mode failed: {}",
String::from_utf8_lossy(&out.stderr)
);
assert!(
!ws.path().join(".aristo/canon-matches.toml").exists(),
"--check must not write canon-matches.toml"
);
}
#[test]
fn stamp_prunes_cache_entry_for_removed_annotation() {
let ws = setup_workspace(ARISTO_TOML_DEFAULT, SOURCE_WITH_ONE_INTENT);
let fixture = ws.path().join("fixtures/canon");
write_match_fixture_one_match(&fixture);
let out = aristo_in(ws.path())
.env("ARISTO_CANON_FIXTURE", &fixture)
.args(["stamp"])
.output()
.unwrap();
assert!(
out.status.success(),
"first stamp failed: {}",
String::from_utf8_lossy(&out.stderr)
);
let cache_path = ws.path().join(".aristo/canon-matches.toml");
assert!(std::fs::read_to_string(&cache_path)
.unwrap()
.contains("edit_page_cell_write_invariant"));
std::fs::write(ws.path().join("src/lib.rs"), "pub fn edit_page() {}\n").unwrap();
let out2 = aristo_in(ws.path()).args(["stamp"]).output().unwrap();
assert!(
out2.status.success(),
"second stamp failed: {}",
String::from_utf8_lossy(&out2.stderr)
);
let stderr = String::from_utf8_lossy(&out2.stderr);
assert!(
stderr.contains(
"canon-matches: pruned 1 stale entry whose annotation id is no \
longer in source: edit_page_cell_write_invariant"
),
"stderr: {stderr}"
);
let cache_body = std::fs::read_to_string(&cache_path).unwrap();
assert!(
!cache_body.contains("edit_page_cell_write_invariant"),
"stale entry must be pruned, got: {cache_body}"
);
assert!(
cache_body.contains("schema_version"),
"__meta__ must survive the prune, got: {cache_body}"
);
let before = std::fs::read_to_string(&cache_path).unwrap();
let out3 = aristo_in(ws.path()).args(["stamp"]).output().unwrap();
assert!(out3.status.success());
let stderr3 = String::from_utf8_lossy(&out3.stderr);
assert!(
!stderr3.contains("canon-matches: pruned"),
"second reconcile must find nothing, stderr: {stderr3}"
);
assert_eq!(
before,
std::fs::read_to_string(&cache_path).unwrap(),
"no-op reconcile must not rewrite the committed file"
);
}
#[test]
fn stamp_demotes_live_bare_id_carrying_accepted_matches() {
let ws = setup_workspace(ARISTO_TOML_DEFAULT, SOURCE_WITH_ONE_INTENT);
let cache_body = r#"[__meta__]
schema_version = 1
canon_version = "v0.2.0"
[edit_page_cell_write_invariant]
last_match_text_hash = "blake3:stale"
canon_fetched_at = "2026-06-15T09:14:22Z"
[[edit_page_cell_write_invariant.accepted_matches]]
canon_id = "cell_written_exactly_once_per_page_edit"
version = "v0.2.1"
canonical_text = "edit_page writes each cell exactly once"
canon_version = "v0.2.0"
confidence = 0.92
prefix_tier = "aristos:"
accepted_at = "2026-06-15T09:20:00Z"
bound_at = "2026-06-15T09:20:00Z"
"#;
let cache_path = ws.path().join(".aristo/canon-matches.toml");
std::fs::write(&cache_path, cache_body).unwrap();
let out = aristo_in(ws.path())
.args(["stamp", "--skip-canon"])
.output()
.unwrap();
assert!(
out.status.success(),
"stamp failed: {}",
String::from_utf8_lossy(&out.stderr)
);
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains(
"canon-matches: demoted `edit_page_cell_write_invariant` \
(source id lost its canon prefix)"
),
"stderr: {stderr}"
);
let cache_after = std::fs::read_to_string(&cache_path).unwrap();
assert!(
!cache_after.contains("accepted_matches"),
"accepted bucket must be dropped, got: {cache_after}"
);
assert!(
cache_after.contains("[edit_page_cell_write_invariant]"),
"the rest of the row must survive, got: {cache_after}"
);
assert!(
cache_after.contains("canon_version = \"v0.2.0\""),
"__meta__ must survive the demote, got: {cache_after}"
);
}
#[test]
fn stamp_reconcile_keeps_rows_for_excluded_annotations() {
let ws = setup_workspace(ARISTO_TOML_DEFAULT, SOURCE_WITH_ONE_INTENT);
let fixture = ws.path().join("fixtures/canon");
write_match_fixture_one_match(&fixture);
let out = aristo_in(ws.path())
.env("ARISTO_CANON_FIXTURE", &fixture)
.args(["stamp"])
.output()
.unwrap();
assert!(
out.status.success(),
"first stamp failed: {}",
String::from_utf8_lossy(&out.stderr)
);
let cache_path = ws.path().join(".aristo/canon-matches.toml");
assert!(std::fs::read_to_string(&cache_path)
.unwrap()
.contains("edit_page_cell_write_invariant"));
std::fs::write(
ws.path().join("aristo.toml"),
"[index]\nexclude = [\"src/**\"]\n",
)
.unwrap();
let out2 = aristo_in(ws.path()).args(["stamp"]).output().unwrap();
assert!(
out2.status.success(),
"second stamp failed: {}",
String::from_utf8_lossy(&out2.stderr)
);
let stderr = String::from_utf8_lossy(&out2.stderr);
assert!(
!stderr.contains("canon-matches: pruned"),
"an excluded annotation is still in source — nothing to prune, \
stderr: {stderr}"
);
assert!(
std::fs::read_to_string(&cache_path)
.unwrap()
.contains("edit_page_cell_write_invariant"),
"the row must survive an [index].exclude narrowing"
);
}
#[test]
fn stamp_reconcile_keeps_row_for_validation_skipped_annotation() {
let ws = setup_workspace(ARISTO_TOML_DEFAULT, SOURCE_WITH_ONE_INTENT);
let fixture = ws.path().join("fixtures/canon");
write_match_fixture_one_match(&fixture);
let out = aristo_in(ws.path())
.env("ARISTO_CANON_FIXTURE", &fixture)
.args(["stamp"])
.output()
.unwrap();
assert!(
out.status.success(),
"first stamp failed: {}",
String::from_utf8_lossy(&out.stderr)
);
let cache_path = ws.path().join(".aristo/canon-matches.toml");
assert!(std::fs::read_to_string(&cache_path)
.unwrap()
.contains("edit_page_cell_write_invariant"));
std::fs::write(
ws.path().join("src/lib.rs"),
r#"
#[aristo::intent(
"each cell should be written exactly once per page edit",
id = "edit_page_cell_write_invariant",
parent = "Bad Parent Typo!!"
)]
pub fn edit_page() {}
"#,
)
.unwrap();
let out2 = aristo_in(ws.path()).args(["stamp"]).output().unwrap();
assert!(
out2.status.success(),
"second stamp failed: {}",
String::from_utf8_lossy(&out2.stderr)
);
let stderr = String::from_utf8_lossy(&out2.stderr);
assert!(
stderr.contains("invalid parent id"),
"precondition: the annotation must have been skipped with a \
warning, stderr: {stderr}"
);
assert!(
!stderr.contains("canon-matches: pruned"),
"a validation-skipped annotation is still in source — nothing \
to prune, stderr: {stderr}"
);
assert!(
std::fs::read_to_string(&cache_path)
.unwrap()
.contains("edit_page_cell_write_invariant"),
"the row must survive a warn-level validation skip"
);
}
#[test]
fn stamp_warns_louder_when_prune_discards_accepted_bindings() {
let ws = setup_workspace(ARISTO_TOML_DEFAULT, SOURCE_WITH_ONE_INTENT);
let cache_body = r#"[__meta__]
schema_version = 1
["aristos:some_gone_binding"]
last_match_text_hash = "blake3:stale"
canon_fetched_at = "2026-06-15T09:14:22Z"
[["aristos:some_gone_binding".accepted_matches]]
canon_id = "some_gone_binding"
version = "v0.2.1"
canonical_text = "a binding whose annotation is gone"
canon_version = "v0.2.0"
confidence = 0.92
prefix_tier = "aristos:"
accepted_at = "2026-06-15T09:20:00Z"
bound_at = "2026-06-15T09:20:00Z"
"#;
let cache_path = ws.path().join(".aristo/canon-matches.toml");
std::fs::write(&cache_path, cache_body).unwrap();
let out = aristo_in(ws.path())
.args(["stamp", "--skip-canon"])
.output()
.unwrap();
assert!(
out.status.success(),
"stamp failed: {}",
String::from_utf8_lossy(&out.stderr)
);
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains(
"canon-matches: pruned 1 stale entry whose annotation id is no \
longer in source: aristos:some_gone_binding"
),
"stderr: {stderr}"
);
assert!(
stderr.contains(
"warning: canon-matches: 1 pruned entry was carrying accepted \
canon bindings: aristos:some_gone_binding"
),
"stderr: {stderr}"
);
assert!(
!std::fs::read_to_string(&cache_path)
.unwrap()
.contains("some_gone_binding"),
"the dead bound row is still pruned — the warning is loud, not \
a veto"
);
}
const STALE_BOUND_CACHE: &str = r#"[__meta__]
schema_version = 1
["aristos:some_gone_binding"]
last_match_text_hash = "blake3:stale"
canon_fetched_at = "2026-06-15T09:14:22Z"
[["aristos:some_gone_binding".accepted_matches]]
canon_id = "some_gone_binding"
version = "v0.2.1"
canonical_text = "a binding whose annotation is gone"
canon_version = "v0.2.0"
confidence = 0.92
prefix_tier = "aristos:"
accepted_at = "2026-06-15T09:20:00Z"
bound_at = "2026-06-15T09:20:00Z"
"#;
#[test]
fn stamp_check_fails_on_stale_canon_matches() {
let ws = setup_workspace(ARISTO_TOML_DEFAULT, SOURCE_WITH_ONE_INTENT);
let out = aristo_in(ws.path())
.args(["stamp", "--skip-canon"])
.output()
.unwrap();
assert!(
out.status.success(),
"setup stamp failed: {}",
String::from_utf8_lossy(&out.stderr)
);
let cache_path = ws.path().join(".aristo/canon-matches.toml");
std::fs::write(&cache_path, STALE_BOUND_CACHE).unwrap();
let index_path = ws.path().join(".aristo/index.toml");
let index_before = std::fs::read(&index_path).unwrap();
let out2 = aristo_in(ws.path())
.args(["stamp", "--check"])
.output()
.unwrap();
assert!(
!out2.status.success(),
"--check must exit non-zero on canon-matches drift"
);
let stderr = String::from_utf8_lossy(&out2.stderr);
assert!(
stderr.contains(
"canon-matches.toml is out of sync with source (1 stale entry, \
0 demotions). Run `aristo stamp` (without --check) to \
reconcile, then commit."
),
"stderr: {stderr}"
);
assert!(
stderr.contains(
"canon-matches: pruned 1 stale entry whose annotation id is no \
longer in source: aristos:some_gone_binding"
),
"stderr: {stderr}"
);
assert_eq!(
std::fs::read(&cache_path).unwrap(),
STALE_BOUND_CACHE.as_bytes(),
"--check must not write canon-matches.toml"
);
assert_eq!(
std::fs::read(&index_path).unwrap(),
index_before,
"--check must not write the index"
);
}
#[test]
fn stamp_check_passes_when_canon_matches_in_sync() {
let ws = setup_workspace(ARISTO_TOML_DEFAULT, SOURCE_WITH_ONE_INTENT);
let fixture = ws.path().join("fixtures/canon");
write_match_fixture_one_match(&fixture);
let out = aristo_in(ws.path())
.env("ARISTO_CANON_FIXTURE", &fixture)
.args(["stamp"])
.output()
.unwrap();
assert!(
out.status.success(),
"setup stamp failed: {}",
String::from_utf8_lossy(&out.stderr)
);
assert!(
std::fs::read_to_string(ws.path().join(".aristo/canon-matches.toml"))
.unwrap()
.contains("edit_page_cell_write_invariant")
);
let out2 = aristo_in(ws.path())
.args(["stamp", "--check"])
.output()
.unwrap();
let stderr = String::from_utf8_lossy(&out2.stderr);
assert!(
out2.status.success(),
"--check must pass on an in-sync workspace, stderr: {stderr}"
);
assert!(
!stderr.contains("canon-matches.toml is out of sync"),
"stderr: {stderr}"
);
assert!(
String::from_utf8_lossy(&out2.stdout).contains("ok: index is up to date"),
"the index-ok line still prints"
);
}
#[test]
fn stamp_check_skips_canon_matches_drift_when_authority_unreliable() {
let ws = setup_workspace(
ARISTO_TOML_DEFAULT,
r#"
#[aristo::intent(
"each cell should be written exactly once per page edit",
id = "edit_page_cell_write_invariant"
)]
pub fn edit_page() {}
#[aristo::intent(
"an invariant whose explicit id cannot parse",
id = "Not A Valid Id!"
)]
pub fn save_page() {}
"#,
);
let out = aristo_in(ws.path())
.args(["stamp", "--skip-canon"])
.output()
.unwrap();
assert!(
out.status.success(),
"setup stamp failed: {}",
String::from_utf8_lossy(&out.stderr)
);
let cache_path = ws.path().join(".aristo/canon-matches.toml");
std::fs::write(&cache_path, STALE_BOUND_CACHE).unwrap();
let out2 = aristo_in(ws.path())
.args(["stamp", "--check"])
.output()
.unwrap();
let stderr = String::from_utf8_lossy(&out2.stderr);
assert!(
out2.status.success(),
"--check must not fail when the reconcile authority is \
unreliable, stderr: {stderr}"
);
assert!(
stderr.contains(
"canon-matches: reconcile skipped (1 annotation(s) have \
unparseable ids"
),
"stderr: {stderr}"
);
assert!(
!stderr.contains("canon-matches.toml is out of sync"),
"stderr: {stderr}"
);
assert_eq!(
std::fs::read(&cache_path).unwrap(),
STALE_BOUND_CACHE.as_bytes(),
"--check must not write canon-matches.toml"
);
}
#[test]
fn stamp_zero_matches_is_success_with_zero_findings() {
let ws = setup_workspace(ARISTO_TOML_DEFAULT, SOURCE_WITH_ONE_INTENT);
let fixture = ws.path().join("fixtures/canon");
write_match_fixture_no_matches(&fixture);
let out = aristo_in(ws.path())
.env("ARISTO_CANON_FIXTURE", &fixture)
.args(["stamp"])
.output()
.unwrap();
assert!(out.status.success());
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(
stdout.contains("canon-match: 0 new finding"),
"stdout: {stdout}"
);
let cache_path = ws.path().join(".aristo/canon-matches.toml");
assert!(cache_path.exists());
}