rubot/tests/skybot.rs

127 lines
4 KiB
Rust

use rustbot::bot::module::{Action, Command, Module};
use rustbot::bot::modules::crypto::format_coins;
use rustbot::bot::modules::dict::parse_definition;
use rustbot::bot::modules::fun::Fun;
use rustbot::bot::modules::funweb::{
format_advice, format_catfact, format_fact, format_joke, format_worldtime,
};
use rustbot::bot::modules::weather::format_forecast;
fn cmd<'a>(name: &'a str, args: &'a [&'a str]) -> Command<'a> {
Command {
sender: "u",
reply_to: "#c",
name,
args,
prefix: ">",
network: "t",
}
}
// ---- funweb formatters ----
#[test]
fn joke_single_and_twopart() {
assert_eq!(
format_joke(r#"{"type":"single","joke":"UDP joke."}"#),
"UDP joke."
);
assert_eq!(
format_joke(r#"{"type":"twopart","setup":"knock knock","delivery":"who?"}"#),
"knock knock … who?"
);
}
#[test]
fn fact_catfact_advice() {
assert_eq!(
format_fact(r#"{"text":"the moon is dry"}"#),
"the moon is dry"
);
assert_eq!(format_catfact(r#"{"fact":"cats purr"}"#), "🐱 cats purr");
assert_eq!(
format_advice(r#"{"slip":{"id":1,"advice":"drink water"}}"#),
"💡 drink water"
);
}
#[test]
fn worldtime_formats_time_and_day() {
let out = format_worldtime(
"Europe/Paris",
r#"{"time":"04:21","dayOfWeek":"Thursday","timeZone":"Europe/Paris"}"#,
);
assert!(out.contains("Europe/Paris"), "{out}");
assert!(out.contains("04:21") && out.contains("Thursday"), "{out}");
}
// ---- crypto: coins ----
#[test]
fn coins_lists_symbols_and_prices() {
let out = format_coins(
r#"[{"symbol":"btc","current_price":64183},{"symbol":"eth","current_price":3200},{"symbol":"xrp","current_price":0.5123}]"#,
);
assert!(out.starts_with("top coins:"), "{out}");
assert!(out.contains("BTC $64183"), "{out}");
assert!(out.contains("ETH $3200"), "{out}");
assert!(out.contains("XRP $0.5123"), "{out}"); // sub-$1 keeps 4 decimals
}
// ---- weather: forecast ----
#[test]
fn forecast_summarizes_days() {
let body = r#"{"weather":[{"date":"2026-07-30","maxtempC":"34","mintempC":"22","hourly":[{},{},{},{},{"weatherDesc":[{"value":"Sunny"}]}]}]}"#;
let out = format_forecast(body, "Paris");
assert!(out.starts_with("Paris:"), "{out}");
assert!(out.contains("07-30"), "{out}");
assert!(out.contains("Sunny") && out.contains("34/22"), "{out}");
}
// ---- dict: RFC 2229 parsing ----
#[test]
fn dict_parses_first_definition() {
let resp = "220 banner\r\n150 1 definitions retrieved\r\n151 \"cat\" wn \"WordNet\"\r\ncat\r\n n 1: feline mammal [syn: {cat}, {true cat}]\r\n.\r\n250 ok\r\n";
let def = parse_definition(resp).expect("a definition");
assert!(def.contains("feline mammal"), "{def}");
assert!(!def.contains('{'), "braces should be stripped: {def}");
}
#[test]
fn dict_no_match_and_no_header_are_none() {
assert!(parse_definition("220 banner\r\n552 no match\r\n250 ok\r\n").is_none());
assert!(parse_definition("220 banner\r\n250 ok\r\n").is_none());
}
// ---- fun (local) smoke tests ----
fn one_reply(actions: Vec<Action>) -> String {
match actions.as_slice() {
[Action::Reply(s)] => s.clone(),
_ => panic!("expected one reply"),
}
}
#[test]
fn fun_basics() {
let mut f = Fun::new();
assert!(one_reply(f.on_command(&cmd("8ball", &["will it work?"]))).contains("🎱"));
assert!(one_reply(f.on_command(&cmd("drinks", &[]))).contains("coffee"));
assert!(one_reply(f.on_command(&cmd("catgif", &[]))).contains("cataas"));
let rps = one_reply(f.on_command(&cmd("rock", &[])));
assert!(rps.contains("you:") && rps.contains("me:"), "{rps}");
}
#[test]
fn coffee_is_a_ctcp_action() {
let mut f = Fun::new();
match f.on_command(&cmd("coffee", &["alice"])).as_slice() {
[Action::Raw(line)] => {
assert!(line.starts_with("PRIVMSG #c :\u{1}ACTION "), "{line}");
assert!(line.contains("alice") && line.contains("coffee"), "{line}");
}
_ => panic!("expected a raw ACTION"),
}
}