use m1nd_core::semantic::SynonymExpander;
fn fetch_and_store_expander() -> SynonymExpander {
SynonymExpander::build(vec![
vec!["fetch", "get", "retrieve"],
vec!["store", "save", "persist"],
])
.expect("building from non-overlapping groups must succeed")
}
#[test]
fn expand_is_case_insensitive_and_includes_self_and_group() {
let expander = fetch_and_store_expander();
let from_upper = expander.expand("GET");
let from_lower = expander.expand("get");
assert!(
from_upper.contains(&"get".to_string()),
"expand must always include the (lowercased) term itself, got {from_upper:?}"
);
for member in ["fetch", "retrieve"] {
assert!(
from_upper.contains(&member.to_string()),
"expand(\"GET\") must include group member {member:?}, got {from_upper:?}"
);
}
let mut upper_sorted = from_upper.clone();
let mut lower_sorted = from_lower.clone();
upper_sorted.sort();
lower_sorted.sort();
assert_eq!(
upper_sorted, lower_sorted,
"expand must be case-insensitive: \"GET\" and \"get\" should expand identically"
);
assert_eq!(
from_upper.len(),
3,
"expand of a 3-member group must yield exactly its 3 members, got {from_upper:?}"
);
}
#[test]
fn expand_dedups_and_is_self_first() {
let expander = SynonymExpander::build(vec![vec!["alpha", "Alpha", "beta"]])
.expect("build with case-duplicated terms must succeed");
let expanded = expander.expand("alpha");
assert_eq!(
expanded.first(),
Some(&"alpha".to_string()),
"expand must place the queried term first, got {expanded:?}"
);
let mut seen = expanded.clone();
seen.sort();
let before = seen.len();
seen.dedup();
assert_eq!(
before,
seen.len(),
"expand must not return duplicate synonyms, got {expanded:?}"
);
assert_eq!(
seen,
vec!["alpha".to_string(), "beta".to_string()],
"expand must collapse case-duplicate members, got {expanded:?}"
);
}
#[test]
fn expand_unknown_term_returns_only_itself() {
let expander = fetch_and_store_expander();
let expanded = expander.expand("UNKNOWN");
assert_eq!(
expanded,
vec!["unknown".to_string()],
"expand of an unknown term must return exactly [lowercased term], got {expanded:?}"
);
}
#[test]
fn are_synonyms_is_reflexive_even_for_unknown_terms() {
let expander = fetch_and_store_expander();
assert!(
expander.are_synonyms("get", "get"),
"are_synonyms must be reflexive for known terms"
);
assert!(
expander.are_synonyms("zzz_unknown", "zzz_unknown"),
"are_synonyms must be reflexive even for terms not in any group"
);
assert!(
expander.are_synonyms("GET", "get"),
"are_synonyms must treat case-different spellings of one term as equal"
);
}
#[test]
fn are_synonyms_is_symmetric_and_intra_group_true() {
let expander = fetch_and_store_expander();
assert!(
expander.are_synonyms("get", "retrieve"),
"are_synonyms must be true within a group"
);
assert!(
expander.are_synonyms("retrieve", "get"),
"are_synonyms must be symmetric within a group"
);
assert!(
expander.are_synonyms("FETCH", "Retrieve"),
"are_synonyms must be case-insensitive and symmetric"
);
}
#[test]
fn are_synonyms_is_false_across_groups() {
let expander = fetch_and_store_expander();
assert!(
!expander.are_synonyms("get", "save"),
"are_synonyms must be false for terms in different groups"
);
assert!(
!expander.are_synonyms("save", "get"),
"are_synonyms must be false (symmetric) for cross-group terms"
);
assert!(
!expander.are_synonyms("get", "absent_term"),
"are_synonyms must be false when one term is unknown"
);
}
#[test]
fn build_default_groups_known_code_synonyms() {
let expander =
SynonymExpander::build_default().expect("build_default must succeed with bundled groups");
assert!(
expander.are_synonyms("function", "method"),
"default groups must keep function/method synonymous"
);
let expanded = expander.expand("FUNCTION");
for member in ["function", "fn", "method", "func"] {
assert!(
expanded.contains(&member.to_string()),
"default expand(\"FUNCTION\") must include {member:?}, got {expanded:?}"
);
}
assert!(
!expander.are_synonyms("function", "class"),
"default groups must keep function and class in separate groups"
);
}