use super::*;
#[test]
fn test_parse_marker_hash() {
let hash = parse_marker_hash("<<<CCR:abc123def456|text|100>>>");
assert_eq!(hash, Some("abc123def456".into()));
let hash = parse_marker_hash("<<<CCR:abc|code_rust|5000>>>");
assert_eq!(hash, Some("abc".into()));
}
#[test]
fn test_parse_marker_invalid() {
assert_eq!(parse_marker_hash("not a marker"), None);
assert_eq!(parse_marker_hash("<<<CCR:"), None);
}
#[test]
fn test_find_markers_single() {
let content = "hello <<<CCR:abc|text|100>>> world";
let markers = find_markers(content);
assert_eq!(markers.len(), 1);
assert_eq!(markers[0].0, "<<<CCR:abc|text|100>>>");
assert_eq!(markers[0].1, "abc");
}
#[test]
fn test_find_markers_multiple() {
let content = "<<<CCR:a|t|1>>> middle <<<CCR:b|t|2>>>";
let markers = find_markers(content);
assert_eq!(markers.len(), 2);
}
#[test]
fn test_find_markers_none() {
assert!(find_markers("plain text").is_empty());
}
#[test]
fn test_filter_empty_query() {
assert_eq!(filter_lines("hello\nworld", ""), "hello\nworld");
}
#[test]
fn test_filter_matching() {
let content = "line one\nerror: broke\nline two\nERROR: fatal\n";
let result = filter_lines(content, "error");
assert!(result.contains("error: broke"));
assert!(result.contains("ERROR: fatal"));
assert!(!result.contains("line one"));
}
#[test]
fn test_filter_no_match() {
let content = "hello\nworld\n";
let result = filter_lines(content, "nonexistent");
assert!(result.contains("[aphrodite: no lines matched"));
}
#[test]
fn test_resolve_one_inline() {
let mut s = AphroditeState::default();
s.inline_store_put("abc123".into(), "hello world".into());
assert_eq!(resolve_one(&mut s, "abc123"), Some("hello world".into()));
}
#[test]
fn test_resolve_one_missing() {
let mut s = AphroditeState::default();
assert_eq!(resolve_one(&mut s, "nonexistent"), None);
}
#[test]
fn test_resolve_one_never_shadowed_by_stage2_key() {
let mut s = AphroditeState::default();
s.inline_store_put("h".into(), "ORIGINAL".into());
s.inline_store_put("h#stage2".into(), "REDUCED".into());
assert_eq!(resolve_one(&mut s, "h"), Some("ORIGINAL".to_string()));
}
#[test]
fn regression_resolve_one_tolerates_pipe_suffixed_hash() {
let mut s = AphroditeState::default();
s.inline_store_put("abc123".into(), "<the real content>".into());
assert_eq!(resolve_one(&mut s, "abc123"), Some("<the real content>".into()));
assert_eq!(
resolve_one(&mut s, "abc123|tool|1024"),
Some("<the real content>".into()),
"a pipe-suffixed hash (as an LLM might echo a full marker body) must still resolve"
);
assert_eq!(
resolve_one(&mut s, " abc123 "),
Some("<the real content>".into()),
"whitespace around the hash must be tolerated too"
);
}
#[test]
fn test_resolve_one_i_prefix() {
let mut s = AphroditeState::default();
s.inline_store_put("i:abc123def".into(), "inline content".into());
assert_eq!(resolve_one(&mut s, "i:abc123def"), Some("inline content".into()));
}
#[test]
fn test_expand_simple() {
let mut s = AphroditeState::default();
s.inline_store_put("simple".into(), "just content".into());
assert_eq!(expand(&mut s, "simple"), Some("just content".into()));
}
#[test]
fn test_expand_with_nesting() {
let mut s = AphroditeState::default();
let inner = "<<<CCR:inner123|text|10>>>";
s.inline_store_put("outer".into(), format!("before {} after", inner));
s.inline_store_put("inner123".into(), "RESOLVED".into());
let result = expand(&mut s, "outer");
assert_eq!(result, Some("before RESOLVED after".into()));
}
#[test]
fn test_expand_unresolved() {
let mut s = AphroditeState::default();
s.inline_store_put("outer".into(), "before <<<CCR:missing|text|10>>> after".into());
let result = expand(&mut s, "outer");
assert_eq!(result, Some("before <<<CCR:missing|text|10>>> after".to_string()));
}
#[test]
fn test_expand_missing_hash() {
let mut s = AphroditeState::default();
assert_eq!(expand(&mut s, "nope"), None);
}
#[test]
fn test_expand_deep_nesting_respects_limit() {
let mut s = AphroditeState::default();
s.inline_store_put("h0".into(), "<<<CCR:h1|t|1>>>".into());
s.inline_store_put("h1".into(), "<<<CCR:h2|t|1>>>".into());
s.inline_store_put("h2".into(), "<<<CCR:h3|t|1>>>".into());
s.inline_store_put("h3".into(), "<<<CCR:h4|t|1>>>".into());
s.inline_store_put("h4".into(), "<<<CCR:h5|t|1>>>".into());
s.inline_store_put("h5".into(), "DEEP".into());
let result = expand(&mut s, "h0");
assert_eq!(result, Some("DEEP".to_string()));
}
#[test]
fn test_expand_cycle_safe() {
let mut s = AphroditeState::default();
s.inline_store_put("hA".into(), "<<<CCR:hB|t|1>>>".into());
s.inline_store_put("hB".into(), "<<<CCR:hA|t|1>>>".into());
let result = expand(&mut s, "hA").unwrap();
assert!(
!result.contains("[CCR_UNRESOLVED"),
"cycle must not surface as unresolved: {result}"
);
assert_eq!(result, "<<<CCR:hB|t|1>>>");
}
#[test]
fn test_find_markers_nested_unclosed_is_skipped() {
let content = "<<<CCR:a<<<CCR:b|t|1>>>";
let markers = find_markers(content);
assert_eq!(markers.len(), 1);
assert_eq!(markers[0].1, "a<<<CCR:b");
}
#[test]
fn test_find_markers_unterminated_no_panic() {
assert!(find_markers("<<<CCR:").is_empty());
assert!(find_markers("text <<<CCR:abc|t|1").is_empty());
}
#[test]
fn test_parse_marker_hash_empty_hash() {
assert_eq!(parse_marker_hash("<<<CCR:>>>"), Some(String::new()));
}
use proptest::{prop_assert_eq, proptest};
proptest! {
#[test]
fn prop_find_markers_never_panics_and_roundtrips(s in ".*") {
let markers = find_markers(&s);
for (marker, hash) in markers {
let parsed = parse_marker_hash(&marker);
prop_assert_eq!(parsed, Some(hash));
}
}
}
#[test]
fn test_expand_never_writes_back_over_the_original_key() {
let mut s = AphroditeState::default();
let original = "before <<<CCR:inner|t|1>>> after";
s.inline_store_put("outer".into(), original.to_string());
s.inline_store_put("inner".into(), "X".to_string());
let _ = expand(&mut s, "outer");
assert_eq!(resolve_one(&mut s, "outer"), Some(original.to_string()));
}
#[test]
fn test_expand_is_idempotent() {
let mut s = AphroditeState::default();
s.inline_store_put("outer".into(), "<<<CCR:inner|t|1>>>".into());
s.inline_store_put("inner".into(), "X".into());
let first = expand(&mut s, "outer");
let second = expand(&mut s, "outer");
assert_eq!(first, second);
assert_eq!(first, Some("X".to_string()));
}
#[test]
fn test_expand_preserves_literal_marker_shaped_text_in_content() {
let mut s = AphroditeState::default();
let content = "see the format <<<CCR:deadbeef|text|4>>> for reference";
s.inline_store_put("doc".into(), content.to_string());
let result = expand(&mut s, "doc");
assert_eq!(result, Some(content.to_string()));
}
#[test]
fn test_expand_diamond_nesting_fully_expands() {
let mut s = AphroditeState::default();
s.inline_store_put("outer".into(), "<<<CCR:a|t|1>>> and <<<CCR:b|t|1>>>".into());
s.inline_store_put("a".into(), "A".into());
s.inline_store_put("b".into(), "wraps <<<CCR:a|t|1>>>".into());
let result = expand(&mut s, "outer").unwrap();
assert!(!result.contains("<<<CCR:"), "no raw marker should remain: {result}");
assert_eq!(result, "A and wraps A");
}