rubot/tests/karma.rs
reverse e801b04d6e
All checks were successful
ci / check (push) Successful in 47s
karma: reasons, milestones, no-highlight replies
2026-07-29 22:29:01 +00:00

168 lines
4.9 KiB
Rust

use rustbot::bot::module::{Action, Chat, Command, Module};
use rustbot::bot::modules::karma::{extract_reason, parse_changes, Karma};
fn chat<'a>(sender: &'a str, text: &'a str) -> Chat<'a> {
Chat {
sender,
reply_to: "#c",
text,
prefix: ">",
network: "t",
}
}
fn visible(s: &str) -> String {
s.replace('\u{200B}', "")
}
fn reply_of(k: &mut Karma, name: &str, args: &[&str]) -> String {
let cmd = Command {
sender: "z",
reply_to: "#c",
name,
args,
prefix: ">",
network: "t",
};
match k.on_command(&cmd).as_slice() {
[Action::Reply(s)] => visible(s),
_ => panic!("expected one reply"),
}
}
#[test]
fn parses_increments_and_decrements() {
assert_eq!(
parse_changes("foo++ bar-- baz", "someone"),
vec![("foo".to_string(), 1), ("bar".to_string(), -1)]
);
}
#[test]
fn strips_trailing_punctuation_and_parens() {
let c = parse_changes("nice work rust++, and (team)++!", "someone");
assert!(c.contains(&("rust".to_string(), 1)), "{c:?}");
assert!(c.contains(&("team".to_string(), 1)), "{c:?}");
}
#[test]
fn no_self_karma_case_insensitive() {
assert!(parse_changes("me++", "me").is_empty());
assert!(parse_changes("Me++", "me").is_empty());
}
#[test]
fn dedupes_within_a_message() {
assert_eq!(
parse_changes("foo++ foo++ foo++", "x"),
vec![("foo".to_string(), 1)]
);
}
#[test]
fn ignores_single_char_and_numeric_targets() {
assert_eq!(
parse_changes("i++ 5++ x-- ok++", "s"),
vec![("ok".to_string(), 1)]
);
}
#[test]
fn extract_reason_after_single_bump() {
assert_eq!(
extract_reason("rust++ for the clean PR").as_deref(),
Some("the clean PR")
);
assert_eq!(
extract_reason("rust++ because it's fast").as_deref(),
Some("it's fast")
);
assert_eq!(
extract_reason("rust++: solid docs").as_deref(),
Some("solid docs")
);
}
#[test]
fn extract_reason_none_when_ambiguous_or_absent() {
assert_eq!(extract_reason("great work rust++"), None);
assert_eq!(extract_reason("foo++ bar++ nice"), None);
assert_eq!(extract_reason("just chatting"), None);
}
#[test]
fn bumps_persist_case_insensitively() {
let path = std::env::temp_dir().join("rubot-test-karma-a.json");
let _ = std::fs::remove_file(&path);
let mut k = Karma::with_path(path.clone());
k.on_message(&chat("alice", "Rust++ rust++ python--"));
let reloaded = Karma::with_path(path.clone());
assert_eq!(reloaded.get("rust"), 1);
assert_eq!(reloaded.get("RUST"), 1);
assert_eq!(reloaded.get("python"), -1);
let _ = std::fs::remove_file(&path);
}
#[test]
fn report_shows_rank_and_reason() {
let path = std::env::temp_dir().join("rubot-test-karma-rank.json");
let _ = std::fs::remove_file(&path);
let mut k = Karma::with_path(path.clone());
k.on_message(&chat("alice", "rust++ for the clean PR"));
k.on_message(&chat("carol", "rust++")); // no reason -> keeps alice's
k.on_message(&chat("bob", "beta++"));
let r = reply_of(&mut k, "karma", &["rust"]);
assert!(r.contains("rust has 2 karma (#1 of 2)"), "{r}");
assert!(r.contains("last: \"the clean PR\" (alice)"), "{r}");
let _ = std::fs::remove_file(&path);
}
#[test]
fn unseen_thing_reports_no_karma() {
let path = std::env::temp_dir().join("rubot-test-karma-unseen.json");
let _ = std::fs::remove_file(&path);
let mut k = Karma::with_path(path.clone());
assert!(reply_of(&mut k, "karma", &["ghost"]).contains("no karma for 'ghost' yet"));
let _ = std::fs::remove_file(&path);
}
#[test]
fn leaderboard_top_and_bottom() {
let path = std::env::temp_dir().join("rubot-test-karma-top.json");
let _ = std::fs::remove_file(&path);
let mut k = Karma::with_path(path.clone());
k.on_message(&chat("a", "high++ mid++ low--"));
k.on_message(&chat("b", "high++"));
let top = reply_of(&mut k, "karma", &[]);
assert!(
top.starts_with("karma top:") && top.contains("high (2)"),
"{top}"
);
let bottom = reply_of(&mut k, "karmabottom", &[]);
assert!(
bottom.starts_with("karma bottom:") && bottom.contains("low (-1)"),
"{bottom}"
);
let _ = std::fs::remove_file(&path);
}
#[test]
fn milestone_announced_at_ten() {
let path = std::env::temp_dir().join("rubot-test-karma-ms.json");
let _ = std::fs::remove_file(&path);
let mut k = Karma::with_path(path.clone());
for i in 0..9 {
let s = format!("u{i}");
assert!(
k.on_message(&chat(&s, "rust++")).is_empty(),
"announce at {i}"
);
}
let a = k.on_message(&chat("u9", "rust++"));
let msg = match a.as_slice() {
[Action::Reply(s)] => visible(s),
_ => panic!("expected a milestone announce"),
};
assert!(msg.contains("rust reached 10 karma"), "{msg}");
let _ = std::fs::remove_file(&path);
}