110 lines
3.7 KiB
Rust
110 lines
3.7 KiB
Rust
use rustbot::karma_web::{parse_board, parse_events, render};
|
|
|
|
fn board_with_events(net: &str, store: &str, log: &str) -> rustbot::karma_web::Board {
|
|
let mut b = parse_board(net, store);
|
|
b.events = parse_events(log);
|
|
b
|
|
}
|
|
|
|
#[test]
|
|
fn parses_flat_number_format() {
|
|
let b = parse_board("libera", r#"{"romaka":2}"#);
|
|
assert_eq!(b.rows.len(), 1);
|
|
assert_eq!(b.rows[0].thing, "romaka");
|
|
assert_eq!(b.rows[0].count, 2);
|
|
}
|
|
|
|
#[test]
|
|
fn parses_object_format_with_reason_and_by() {
|
|
let b = parse_board("t", r#"{"rust":{"n":5,"why":"clean PR","by":"alice"}}"#);
|
|
assert_eq!(b.rows[0].count, 5);
|
|
assert_eq!(b.rows[0].reason.as_deref(), Some("clean PR"));
|
|
assert_eq!(b.rows[0].by.as_deref(), Some("alice"));
|
|
}
|
|
|
|
#[test]
|
|
fn sorts_descending_by_count() {
|
|
let b = parse_board("t", r#"{"a":1,"b":9,"c":-3}"#);
|
|
let order: Vec<&str> = b.rows.iter().map(|r| r.thing.as_str()).collect();
|
|
assert_eq!(order, vec!["b", "a", "c"]);
|
|
}
|
|
|
|
#[test]
|
|
fn parses_event_log_lines() {
|
|
let log = "{\"t\":100,\"o\":\"rust\",\"d\":1,\"by\":\"alice\",\"why\":\"clean PR\"}\n\
|
|
{\"t\":200,\"o\":\"rust\",\"d\":-1,\"by\":\"bob\"}\n\
|
|
\n\
|
|
garbage line\n";
|
|
let ev = parse_events(log);
|
|
assert_eq!(ev.len(), 2);
|
|
assert_eq!(ev[0].thing, "rust");
|
|
assert_eq!(ev[0].delta, 1);
|
|
assert_eq!(ev[0].by.as_deref(), Some("alice"));
|
|
assert_eq!(ev[0].why.as_deref(), Some("clean PR"));
|
|
assert_eq!(ev[1].delta, -1);
|
|
assert!(ev[1].why.is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn renders_hero_podium_and_scores() {
|
|
let b = parse_board(
|
|
"libera",
|
|
r#"{"rust":{"n":5,"why":"clean PR","by":"alice"}}"#,
|
|
);
|
|
let html = render(&[b], 0);
|
|
assert!(
|
|
html.contains(">karma<"),
|
|
"wordmark missing: has no karma mark"
|
|
);
|
|
assert!(html.contains("podium"), "no podium");
|
|
assert!(html.contains(">rust<"), "champion nick missing");
|
|
assert!(html.contains(">+5<"), "score missing");
|
|
assert!(html.contains("clean PR"), "reason missing");
|
|
}
|
|
|
|
#[test]
|
|
fn renders_activity_and_givers_from_events() {
|
|
let log = "{\"t\":100,\"o\":\"rust\",\"d\":1,\"by\":\"alice\",\"why\":\"clean PR\"}\n\
|
|
{\"t\":200,\"o\":\"docs\",\"d\":1,\"by\":\"alice\"}\n";
|
|
let b = board_with_events("libera", r#"{"rust":1,"docs":1}"#, log);
|
|
let html = render(&[b], 0);
|
|
assert!(html.contains("recent activity"), "no activity section");
|
|
assert!(html.contains("top givers"), "no givers section");
|
|
assert!(html.contains("gave"), "no feed verb");
|
|
// alice made two gifts -> shown in givers panel
|
|
assert!(
|
|
html.contains("2 gifts"),
|
|
"giver count wrong: {}",
|
|
html.contains("gift")
|
|
);
|
|
assert!(
|
|
html.contains("data-t=\"200\""),
|
|
"feed sorted/newest-first missing"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn empty_states_are_friendly() {
|
|
// no karma at all
|
|
let html = render(&[], 0);
|
|
assert!(html.contains("No karma yet"), "{html}");
|
|
// karma but no event history yet
|
|
let b = parse_board("libera", r#"{"romaka":2}"#);
|
|
let html = render(&[b], 0);
|
|
assert!(html.contains("Nothing yet"), "empty activity state missing");
|
|
assert!(html.contains("Be the first"), "empty givers state missing");
|
|
}
|
|
|
|
#[test]
|
|
fn escapes_untrusted_irc_input_everywhere() {
|
|
let log = "{\"t\":1,\"o\":\"<script>\",\"d\":1,\"by\":\"a&b\",\"why\":\"\\\"><img src=x>\"}\n";
|
|
let b = board_with_events(
|
|
"t",
|
|
r#"{"<script>":{"n":1,"why":"\"><img src=x>","by":"a&b"}}"#,
|
|
log,
|
|
);
|
|
let html = render(&[b], 0);
|
|
assert!(!html.contains("<img"), "raw img tag leaked");
|
|
assert!(html.contains("<script>"), "nick not escaped");
|
|
assert!(html.contains("a&b"), "giver not escaped");
|
|
}
|