use qualifier::annotation::{self, Annotation, AnnotationBody, Kind, Record};
use qualifier::compact::{self, filter_superseded};
use qualifier::qual_file::{self, QualFile};
use qualifier::threads::build_threads;
use chrono::Utc;
use std::path::PathBuf;
fn make_att(subject: &str, kind: Kind, summary: &str) -> Annotation {
annotation::finalize(Annotation {
metabox: "1".into(),
record_type: "annotation".into(),
subject: subject.into(),
issuer: "mailto:test@test.com".into(),
issuer_type: None,
created_at: chrono::DateTime::parse_from_rfc3339("2026-02-24T10:00:00Z")
.unwrap()
.with_timezone(&Utc),
id: String::new(),
body: AnnotationBody {
detail: None,
kind,
r#ref: None,
references: None,
span: None,
suggested_fix: None,
summary: summary.into(),
supersedes: None,
tags: vec![],
},
})
}
fn make_record(subject: &str, kind: Kind, summary: &str) -> Record {
Record::Annotation(Box::new(make_att(subject, kind, summary)))
}
#[test]
fn test_golden_annotation_id() {
let att = annotation::finalize(Annotation {
metabox: "1".into(),
record_type: "annotation".into(),
subject: "src/parser.rs".into(),
issuer: "mailto:alice@example.com".into(),
issuer_type: None,
created_at: chrono::DateTime::parse_from_rfc3339("2026-02-24T10:00:00Z")
.unwrap()
.with_timezone(&Utc),
id: String::new(),
body: AnnotationBody {
detail: None,
kind: Kind::Concern,
r#ref: None,
references: None,
span: None,
suggested_fix: None,
summary: "Panics on malformed input".into(),
supersedes: None,
tags: vec![],
},
});
assert_eq!(annotation::generate_id(&att), att.id);
assert_eq!(att.id.len(), 64);
}
#[test]
fn test_golden_epoch_id() {
use qualifier::annotation::{self, Epoch, EpochBody, IssuerType};
let epoch = annotation::finalize_epoch(Epoch {
metabox: "1".into(),
record_type: "epoch".into(),
subject: "src/parser.rs".into(),
issuer: "urn:qualifier:compact".into(),
issuer_type: Some(IssuerType::Tool),
created_at: chrono::DateTime::parse_from_rfc3339("2026-02-25T12:00:00Z")
.unwrap()
.with_timezone(&Utc),
id: String::new(),
body: EpochBody {
refs: vec!["aaa".into(), "bbb".into(), "ccc".into()],
span: None,
summary: "Compacted from 3 annotations".into(),
},
});
assert_eq!(epoch.id.len(), 64);
}
#[test]
fn test_golden_dependency_id() {
use qualifier::annotation::{self, DependencyBody, DependencyRecord};
let dep = annotation::finalize_record(Record::Dependency(DependencyRecord {
metabox: "1".into(),
record_type: "dependency".into(),
subject: "bin/server".into(),
issuer: "https://build.example.com".into(),
issuer_type: None,
created_at: chrono::DateTime::parse_from_rfc3339("2026-02-25T10:00:00Z")
.unwrap()
.with_timezone(&Utc),
id: String::new(),
body: DependencyBody {
depends_on: vec!["lib/auth".into(), "lib/http".into()],
},
}));
assert_eq!(
dep.id(),
"dc97e9f3fa9b8d1f0c70e1a8aae30ddf305dba6f6c1d9720ef7e9d5db57eacfe",
"Golden dependency ID changed! Canonical form or hashing is broken."
);
}
#[test]
fn test_annotation_lifecycle_write_parse_roundtrip() {
let dir = tempfile::tempdir().unwrap();
let qual_path = dir.path().join("src/parser.rs.qual");
std::fs::create_dir_all(qual_path.parent().unwrap()).unwrap();
let r1 = make_record("src/parser.rs", Kind::Concern, "Panics on bad input");
let r2 = make_record("src/parser.rs", Kind::Praise, "Good test coverage");
qual_file::append(&qual_path, &r1).unwrap();
qual_file::append(&qual_path, &r2).unwrap();
let qf = qual_file::parse(&qual_path).unwrap();
assert_eq!(qf.records.len(), 2);
assert_eq!(qf.records[0].id(), r1.id());
assert_eq!(qf.records[1].id(), r2.id());
let att1 = r1.as_annotation().unwrap();
let att2 = r2.as_annotation().unwrap();
assert_eq!(annotation::generate_id(att1), att1.id);
assert_eq!(annotation::generate_id(att2), att2.id);
}
#[test]
fn test_annotation_id_is_content_addressed() {
let att1 = make_att("foo.rs", Kind::Pass, "ok");
let att2 = make_att("foo.rs", Kind::Pass, "ok");
assert_eq!(att1.id, att2.id);
let att3 = make_att("foo.rs", Kind::Pass, "ok with extra commentary");
assert_ne!(att1.id, att3.id);
}
#[test]
fn test_compaction_prune_removes_superseded() {
let original = make_record("mod.rs", Kind::Concern, "bad");
let fix = Record::Annotation(Box::new(annotation::finalize(Annotation {
metabox: "1".into(),
record_type: "annotation".into(),
subject: "mod.rs".into(),
issuer: "mailto:test@test.com".into(),
issuer_type: None,
created_at: chrono::DateTime::parse_from_rfc3339("2026-02-24T11:00:00Z")
.unwrap()
.with_timezone(&Utc),
id: String::new(),
body: AnnotationBody {
detail: None,
kind: Kind::Pass,
r#ref: None,
references: None,
span: None,
suggested_fix: None,
summary: "fixed".into(),
supersedes: Some(original.id().to_string()),
tags: vec![],
},
})));
let extra = make_record("mod.rs", Kind::Praise, "nice");
let qf = QualFile {
path: PathBuf::from("mod.rs.qual"),
subject: "mod.rs".into(),
records: vec![original, fix.clone(), extra.clone()],
};
let (pruned, _) = compact::prune(&qf);
assert_eq!(pruned.records.len(), 2);
assert!(pruned.records.iter().any(|r| r.id() == fix.id()));
assert!(pruned.records.iter().any(|r| r.id() == extra.id()));
let (snapped, _) = compact::snapshot(&qf);
assert_eq!(snapped.records.len(), 1);
assert!(snapped.records[0].as_epoch().is_some());
}
#[test]
fn test_discovery_walks_tree() {
let dir = tempfile::tempdir().unwrap();
let paths = [
"src/lib.rs.qual",
"src/parser.rs.qual",
"src/util/helpers.rs.qual",
];
for p in &paths {
let full = dir.path().join(p);
std::fs::create_dir_all(full.parent().unwrap()).unwrap();
std::fs::write(&full, "").unwrap();
}
let hidden = dir.path().join(".git/objects/foo.qual");
std::fs::create_dir_all(hidden.parent().unwrap()).unwrap();
std::fs::write(&hidden, "").unwrap();
let found = qual_file::discover(dir.path(), true).unwrap();
assert_eq!(found.len(), 3);
let subjects: Vec<&str> = found.iter().map(|qf| qf.subject.as_str()).collect();
assert!(subjects.iter().any(|a| a.ends_with("src/lib.rs")));
assert!(subjects.iter().any(|a| a.ends_with("src/parser.rs")));
assert!(subjects.iter().any(|a| a.ends_with("src/util/helpers.rs")));
}
#[test]
fn test_supersession_cycle_detected() {
let now = Utc::now();
let a = Record::Annotation(Box::new(Annotation {
metabox: "1".into(),
record_type: "annotation".into(),
subject: "x".into(),
issuer: "mailto:test@test.com".into(),
issuer_type: None,
created_at: now,
id: "aaa".into(),
body: AnnotationBody {
detail: None,
kind: Kind::Pass,
r#ref: None,
references: None,
span: None,
suggested_fix: None,
summary: "a".into(),
supersedes: Some("bbb".into()),
tags: vec![],
},
}));
let b = Record::Annotation(Box::new(Annotation {
metabox: "1".into(),
record_type: "annotation".into(),
subject: "x".into(),
issuer: "mailto:test@test.com".into(),
issuer_type: None,
created_at: now,
id: "bbb".into(),
body: AnnotationBody {
detail: None,
kind: Kind::Pass,
r#ref: None,
references: None,
span: None,
suggested_fix: None,
summary: "b".into(),
supersedes: Some("aaa".into()),
tags: vec![],
},
}));
let result = annotation::check_supersession_cycles(&[a, b]);
assert!(result.is_err());
}
#[test]
fn test_cross_artifact_supersession_rejected() {
let a = make_record("foo.rs", Kind::Concern, "issue in foo");
let b = Record::Annotation(Box::new(annotation::finalize(Annotation {
metabox: "1".into(),
record_type: "annotation".into(),
subject: "bar.rs".into(),
issuer: "mailto:test@test.com".into(),
issuer_type: None,
created_at: chrono::DateTime::parse_from_rfc3339("2026-02-24T11:00:00Z")
.unwrap()
.with_timezone(&Utc),
id: String::new(),
body: AnnotationBody {
detail: None,
kind: Kind::Pass,
r#ref: None,
references: None,
span: None,
suggested_fix: None,
summary: "fix in bar".into(),
supersedes: Some(a.id().to_string()),
tags: vec![],
},
})));
let result = annotation::validate_supersession_targets(&[a, b]);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("cross-subject"));
}
#[test]
fn test_kind_typo_detected_in_validation() {
let att = annotation::finalize(Annotation {
metabox: "1".into(),
record_type: "annotation".into(),
subject: "x.rs".into(),
issuer: "mailto:test@test.com".into(),
issuer_type: None,
created_at: Utc::now(),
id: String::new(),
body: AnnotationBody {
detail: None,
kind: Kind::Custom("pss".into()),
r#ref: None,
references: None,
span: None,
suggested_fix: None,
summary: "oops".into(),
supersedes: None,
tags: vec![],
},
});
let errors = annotation::validate(&att);
assert!(
errors.iter().any(|e| e.contains("did you mean 'pass'")),
"expected typo warning, got: {:?}",
errors
);
}
#[test]
fn test_parse_qual_file_only_comments() {
let content = "// This is a comment\n// Another comment\n\n";
let records = qual_file::parse_str(content).unwrap();
assert!(records.is_empty());
}
#[test]
fn test_metabox_roundtrip() {
use qualifier::annotation::IssuerType;
let dir = tempfile::tempdir().unwrap();
let qual_path = dir.path().join("test.rs.qual");
let att = annotation::finalize(Annotation {
metabox: "1".into(),
record_type: "annotation".into(),
subject: "test.rs".into(),
issuer: "mailto:alice@example.com".into(),
issuer_type: Some(IssuerType::Human),
created_at: chrono::DateTime::parse_from_rfc3339("2026-02-24T10:00:00Z")
.unwrap()
.with_timezone(&Utc),
id: String::new(),
body: AnnotationBody {
detail: None,
kind: Kind::Praise,
r#ref: Some("git:3aba500".into()),
references: None,
span: None,
suggested_fix: None,
summary: "Great code".into(),
supersedes: None,
tags: vec!["quality".into()],
},
});
assert_eq!(att.metabox, "1");
qual_file::append(&qual_path, &Record::Annotation(Box::new(att.clone()))).unwrap();
let qf = qual_file::parse(&qual_path).unwrap();
assert_eq!(qf.records.len(), 1);
let parsed = qf.records[0].as_annotation().unwrap();
assert_eq!(parsed.metabox, "1");
assert_eq!(parsed.issuer_type, Some(IssuerType::Human));
assert_eq!(parsed.body.r#ref.as_deref(), Some("git:3aba500"));
assert_eq!(parsed.id, att.id);
}
#[test]
fn test_compact_snapshot_produces_epoch() {
use qualifier::annotation::IssuerType;
let records = vec![
make_record("src/a.rs", Kind::Praise, "good"),
make_record("src/a.rs", Kind::Concern, "meh"),
];
let qf = QualFile {
path: PathBuf::from("src/.qual"),
subject: "src/".into(),
records,
};
let (snapped, _) = compact::snapshot(&qf);
assert_eq!(snapped.records.len(), 1);
let epoch = snapped.records[0].as_epoch().unwrap();
assert_eq!(epoch.metabox, "1");
assert_eq!(epoch.issuer_type, Some(IssuerType::Tool));
assert_eq!(epoch.body.refs.len(), 2);
}
#[test]
fn test_supersession_filter() {
let original = make_record("mod.rs", Kind::Concern, "problem");
let replacement = Record::Annotation(Box::new(annotation::finalize(Annotation {
metabox: "1".into(),
record_type: "annotation".into(),
subject: "mod.rs".into(),
issuer: "mailto:test@test.com".into(),
issuer_type: Some(qualifier::annotation::IssuerType::Human),
created_at: chrono::DateTime::parse_from_rfc3339("2026-02-24T11:00:00Z")
.unwrap()
.with_timezone(&Utc),
id: String::new(),
body: AnnotationBody {
detail: None,
kind: Kind::Pass,
r#ref: Some("git:abc123".into()),
references: None,
span: None,
suggested_fix: None,
summary: "fixed it".into(),
supersedes: Some(original.id().to_string()),
tags: vec![],
},
})));
let all = vec![original.clone(), replacement.clone()];
let active = filter_superseded(&all);
assert_eq!(active.len(), 1);
assert_eq!(active[0].id(), replacement.id());
}
fn at(secs: i64) -> chrono::DateTime<Utc> {
chrono::DateTime::from_timestamp(1_780_000_000 + secs, 0).unwrap()
}
fn ann(
subject: &str,
kind: Kind,
summary: &str,
secs: i64,
references: Option<&str>,
supersedes: Option<&str>,
) -> Record {
let mut a = make_att(subject, kind, summary);
a.created_at = at(secs);
a.body.references = references.map(String::from);
a.body.supersedes = supersedes.map(String::from);
Record::Annotation(Box::new(annotation::finalize(a)))
}
fn summary_of(r: &Record) -> &str {
&r.as_annotation().unwrap().body.summary
}
#[test]
fn test_threads_single_open_root() {
let root = ann("a.rs", Kind::Concern, "root", 0, None, None);
let records = vec![root.clone()];
let threads = build_threads(&records);
assert_eq!(threads.len(), 1);
assert!(threads[0].open);
assert_eq!(threads[0].origin, root.id());
assert!(threads[0].replies.is_empty());
assert!(threads[0].closed_by.is_none());
}
#[test]
fn test_threads_nested_replies_join_root() {
let root = ann("a.rs", Kind::Concern, "root", 0, None, None);
let r1 = ann("a.rs", Kind::Comment, "reply", 10, Some(root.id()), None);
let r2 = ann(
"a.rs",
Kind::Comment,
"reply to reply",
20,
Some(r1.id()),
None,
);
let records = vec![r2, root, r1];
let threads = build_threads(&records);
assert_eq!(threads.len(), 1);
let summaries: Vec<&str> = threads[0]
.replies
.iter()
.map(|e| summary_of(e.record))
.collect();
assert_eq!(summaries, vec!["reply", "reply to reply"], "oldest first");
assert_eq!(threads[0].latest_at, at(20));
}
#[test]
fn test_threads_resolved_root_is_closed() {
let root = ann("a.rs", Kind::Concern, "root", 0, None, None);
let fix = ann("a.rs", Kind::Resolve, "fixed", 10, None, Some(root.id()));
let records = vec![root.clone(), fix.clone()];
let threads = build_threads(&records);
assert_eq!(threads.len(), 1);
assert!(!threads[0].open);
assert_eq!(threads[0].root.id(), root.id());
assert_eq!(threads[0].closed_by.map(|r| r.id()), Some(fix.id()));
}
#[test]
fn test_threads_rerecorded_root_keeps_replies() {
let a = ann("a.rs", Kind::Concern, "v1", 0, None, None);
let reply = ann("a.rs", Kind::Comment, "on v1", 5, Some(a.id()), None);
let b = ann("a.rs", Kind::Concern, "v2", 10, None, Some(a.id()));
let records = vec![a.clone(), reply, b.clone()];
let threads = build_threads(&records);
assert_eq!(threads.len(), 1, "a re-recorded root stays one thread");
assert!(threads[0].open);
assert_eq!(threads[0].root.id(), b.id(), "root is the live head");
assert_eq!(threads[0].origin, a.id());
assert_eq!(threads[0].replies.len(), 1);
let history_ids: Vec<&str> = threads[0].history.iter().map(|r| r.id()).collect();
assert_eq!(history_ids, vec![a.id()]);
}
#[test]
fn test_threads_edited_reply_marks_old_inactive() {
let root = ann("a.rs", Kind::Concern, "root", 0, None, None);
let r1 = ann("a.rs", Kind::Comment, "draft", 10, Some(root.id()), None);
let r2 = ann(
"a.rs",
Kind::Comment,
"final",
20,
Some(root.id()),
Some(r1.id()),
);
let records = vec![root, r1, r2];
let threads = build_threads(&records);
let entries: Vec<(&str, bool)> = threads[0]
.replies
.iter()
.map(|e| (summary_of(e.record), e.active))
.collect();
assert_eq!(entries, vec![("draft", false), ("final", true)]);
}
#[test]
fn test_threads_resolving_a_reply_keeps_thread_open() {
let root = ann("a.rs", Kind::Concern, "root", 0, None, None);
let r1 = ann(
"a.rs",
Kind::Comment,
"wrong claim",
10,
Some(root.id()),
None,
);
let close_reply = ann("a.rs", Kind::Resolve, "retracted", 20, None, Some(r1.id()));
let records = vec![root, r1, close_reply];
let threads = build_threads(&records);
assert_eq!(threads.len(), 1);
assert!(
threads[0].open,
"resolving a reply must not close the thread"
);
assert_eq!(threads[0].replies.len(), 2);
}
#[test]
fn test_threads_ignore_epochs() {
let qf = QualFile {
path: PathBuf::from("a.rs.qual"),
subject: "a.rs".into(),
records: vec![
ann("a.rs", Kind::Concern, "one", 0, None, None),
ann("a.rs", Kind::Suggestion, "two", 10, None, None),
],
};
let (snap, _) = compact::snapshot(&qf);
let mut records = snap.records.clone();
records.push(ann("a.rs", Kind::Blocker, "after snapshot", 20, None, None));
let threads = build_threads(&records);
assert_eq!(threads.len(), 1);
assert_eq!(summary_of(threads[0].root), "after snapshot");
assert!(threads[0].replies.is_empty());
assert!(threads[0].history.is_empty());
}
#[test]
fn test_threads_ordered_by_subject_then_line() {
let mut b = make_att("b.rs", Kind::Concern, "b");
b.created_at = at(0);
let mut a2 = make_att("a.rs", Kind::Concern, "a line 20");
a2.created_at = at(1);
a2.body.span = Some(annotation::parse_span("20").unwrap());
let mut a1 = make_att("a.rs", Kind::Concern, "a line 5");
a1.created_at = at(2);
a1.body.span = Some(annotation::parse_span("5").unwrap());
let records: Vec<Record> = [b, a2, a1]
.into_iter()
.map(|a| Record::Annotation(Box::new(annotation::finalize(a))))
.collect();
let threads = build_threads(&records);
let order: Vec<&str> = threads.iter().map(|t| summary_of(t.root)).collect();
assert_eq!(order, vec!["a line 5", "a line 20", "b"]);
}
#[test]
fn test_threads_reply_supersedes_root_stays_a_reply() {
let a = ann("a.rs", Kind::Concern, "root", 0, None, None);
let r = ann(
"a.rs",
Kind::Comment,
"reply that supersedes",
10,
Some(a.id()),
Some(a.id()),
);
let records = vec![a.clone(), r.clone()];
let threads = build_threads(&records);
assert_eq!(threads.len(), 1);
assert!(threads[0].open);
assert_eq!(threads[0].root.id(), a.id());
assert_eq!(threads[0].replies.len(), 1);
assert!(threads[0].history.is_empty());
}
#[test]
fn test_threads_fork_open_when_any_non_resolve_tip() {
let a = ann("a.rs", Kind::Concern, "a", 0, None, None);
let b = ann("a.rs", Kind::Concern, "b", 10, None, Some(a.id()));
let c = ann("a.rs", Kind::Resolve, "c", 20, None, Some(a.id()));
let records = vec![a.clone(), b.clone(), c.clone()];
let threads = build_threads(&records);
assert_eq!(threads.len(), 1);
assert!(
threads[0].open,
"a live non-resolve tip keeps the thread open"
);
assert_eq!(threads[0].root.id(), b.id());
assert!(threads[0].closed_by.is_none());
let history_ids: Vec<&str> = threads[0].history.iter().map(|r| r.id()).collect();
assert_eq!(history_ids, vec![a.id(), c.id()]);
}
#[test]
fn test_threads_closed_when_all_tips_resolve() {
let a = ann("a.rs", Kind::Concern, "a", 0, None, None);
let b = ann("a.rs", Kind::Concern, "b", 10, None, Some(a.id()));
let r1 = ann("a.rs", Kind::Resolve, "closed", 20, None, Some(b.id()));
let records = vec![a.clone(), b.clone(), r1.clone()];
let threads = build_threads(&records);
assert_eq!(threads.len(), 1);
assert!(!threads[0].open);
assert_eq!(threads[0].closed_by.map(|r| r.id()), Some(r1.id()));
assert_eq!(threads[0].root.id(), b.id());
let history_ids: Vec<&str> = threads[0].history.iter().map(|r| r.id()).collect();
assert_eq!(history_ids, vec![a.id()]);
}
#[test]
fn test_threads_dangling_targets_form_own_thread() {
let fake_id = "nonexistent_id_12345";
let r = ann(
"a.rs",
Kind::Comment,
"orphan",
0,
Some(fake_id),
Some(fake_id),
);
let records = vec![r.clone()];
let threads = build_threads(&records);
assert_eq!(threads.len(), 1);
assert_eq!(threads[0].origin, r.id());
assert_eq!(threads[0].root.id(), r.id());
assert!(threads[0].replies.is_empty());
assert!(threads[0].history.is_empty());
}
#[test]
fn test_threads_tie_break_by_origin_id_is_deterministic() {
let a = ann("a.rs", Kind::Concern, "root one", 0, None, None);
let b = ann("a.rs", Kind::Concern, "root two", 0, None, None);
let forward = vec![a.clone(), b.clone()];
let backward = vec![b.clone(), a.clone()];
let order_forward: Vec<&str> = build_threads(&forward).iter().map(|t| t.origin).collect();
let threads_backward = build_threads(&backward);
let order_backward: Vec<&str> = threads_backward.iter().map(|t| t.origin).collect();
let mut expected = vec![a.id(), b.id()];
expected.sort();
assert_eq!(order_forward, expected);
assert_eq!(order_backward, expected);
}