use moniof::config::MoniOFConfig;
use moniof::core::stats::QueryStats;
use moniof::observability::of::find_suspects;
fn seed(stats: &mut QueryStats, key: &str, count: usize, ms: u128) {
for _ in 0..count {
stats.record(key);
stats.record_latency(key, ms);
}
}
fn cfg() -> MoniOFConfig {
MoniOFConfig {
of_mode: true,
n_plus_one_min_count: 5,
n_plus_one_min_total_ms: Some(5),
..Default::default()
}
}
#[test]
fn no_suspects_when_of_mode_disabled() {
let mut s = QueryStats::new();
seed(&mut s, "sql/select 1", 100, 10);
let c = MoniOFConfig { of_mode: false, ..cfg() };
assert!(find_suspects(&s, &c).is_empty());
}
#[test]
fn no_suspects_below_min_count() {
let mut s = QueryStats::new();
seed(&mut s, "sql/select 1", 4, 100);
assert!(find_suspects(&s, &cfg()).is_empty());
}
#[test]
fn suspect_at_exactly_min_count() {
let mut s = QueryStats::new();
seed(&mut s, "sql/select 1", 5, 2);
let found = find_suspects(&s, &cfg());
assert_eq!(found.len(), 1);
assert_eq!(found[0].key, "sql/select 1");
assert_eq!(found[0].count, 5);
assert_eq!(found[0].total_latency_ms, 10);
}
#[test]
fn latency_gate_rejects_fast_repeated_queries() {
let mut s = QueryStats::new();
seed(&mut s, "sql/select 1", 20, 0);
assert!(
find_suspects(&s, &cfg()).is_empty(),
"a key with zero recorded latency must not trip the latency gate"
);
}
#[test]
fn latency_gate_is_skipped_when_unset() {
let mut s = QueryStats::new();
seed(&mut s, "sql/select 1", 20, 0);
let c = MoniOFConfig { n_plus_one_min_total_ms: None, ..cfg() };
let found = find_suspects(&s, &c);
assert_eq!(found.len(), 1);
assert_eq!(found[0].count, 20);
}
#[test]
fn suspects_sorted_by_count_descending() {
let mut s = QueryStats::new();
seed(&mut s, "sql/low", 6, 10);
seed(&mut s, "sql/high", 30, 10);
seed(&mut s, "sql/mid", 12, 10);
let found = find_suspects(&s, &cfg());
let keys: Vec<&str> = found.iter().map(|f| f.key.as_str()).collect();
assert_eq!(keys, ["sql/high", "sql/mid", "sql/low"]);
}
#[test]
fn ties_on_count_broken_by_total_latency_descending() {
let mut s = QueryStats::new();
seed(&mut s, "sql/cheap", 10, 1); seed(&mut s, "sql/pricey", 10, 9);
let found = find_suspects(&s, &cfg());
assert_eq!(found[0].key, "sql/pricey");
assert_eq!(found[1].key, "sql/cheap");
}
#[test]
fn suspects_are_capped_at_three() {
let mut s = QueryStats::new();
for i in 0..10 {
seed(&mut s, &format!("sql/q{i}"), 6 + i, 10);
}
let found = find_suspects(&s, &cfg());
assert_eq!(found.len(), 3, "finder must return at most the top 3 suspects");
assert_eq!(found[0].key, "sql/q9");
assert_eq!(found[1].key, "sql/q8");
assert_eq!(found[2].key, "sql/q7");
}
#[test]
fn counts_reported_are_the_recorded_counts() {
let mut s = QueryStats::new();
seed(&mut s, "mongo/users/find", 7, 3);
let found = find_suspects(&s, &cfg());
assert_eq!(found[0].count, 7);
assert_eq!(found[0].total_latency_ms, 21);
}