modules: add fun (8ball/rps/generators/actions), funweb (joke/fact/catfact/advice/worldtime), dict (RFC 2229 lookups); crypto coins; weather fc
All checks were successful
ci / check (push) Successful in 45s
All checks were successful
ci / check (push) Successful in 45s
This commit is contained in:
parent
3c2154bbc1
commit
ae7d49dff8
8 changed files with 874 additions and 7 deletions
127
tests/skybot.rs
Normal file
127
tests/skybot.rs
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
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"),
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue