Wire centralized HELP into the remaining services

ChanServ, BotServ, HostServ, MemoServ, OperServ, and StatServ now route
HELP through the shared renderer with per-command topic tables, so every
service supports HELP <command>. The renderer matches slash-grouped
names (OP/DEOP/VOICE/DEVOICE) so a paired command resolves as one topic.
Adds unit tests for the renderer.
This commit is contained in:
Jean Chevronnet 2026-07-14 17:23:40 +00:00
parent c9e392630b
commit 69a93198ff
No known key found for this signature in database
7 changed files with 180 additions and 22 deletions

View file

@ -1150,7 +1150,7 @@ pub fn help(me: &str, from: &Sender, ctx: &mut ServiceCtx, blurb: &str, topics:
}
ctx.notice(me, from.uid, "Type \x02HELP <command>\x02 for detail on one command.");
}
Some(t) => match topics.iter().find(|e| e.cmd.eq_ignore_ascii_case(t)) {
Some(t) => match topics.iter().find(|e| e.cmd.split('/').any(|c| c.eq_ignore_ascii_case(t))) {
Some(e) => {
for line in e.detail.lines() {
ctx.notice(me, from.uid, line);
@ -1214,3 +1214,62 @@ mod tests {
assert!(!Privs::default().any(), "empty set is not an oper");
}
}
#[cfg(test)]
mod help_tests {
use super::*;
fn texts(ctx: &ServiceCtx) -> Vec<String> {
ctx.actions
.iter()
.filter_map(|a| match a {
NetAction::Notice { text, .. } => Some(text.clone()),
_ => None,
})
.collect()
}
const T: &[HelpEntry] = &[
HelpEntry { cmd: "REGISTER", summary: "register your nick", detail: "Syntax: REGISTER <password>\nRegisters your nick." },
HelpEntry { cmd: "OP/DEOP", summary: "give or take op", detail: "Syntax: OP <#chan>\nGives or takes op." },
];
fn sender<'a>() -> Sender<'a> {
Sender { uid: "u1", nick: "nick", account: None, privs: Privs::default() }
}
#[test]
fn overview_lists_every_command() {
let mut ctx = ServiceCtx::default();
help("Svc", &sender(), &mut ctx, "the blurb", T, None);
let t = texts(&ctx);
assert_eq!(t[0], "the blurb");
assert!(t.iter().any(|l| l.contains("REGISTER") && l.contains("register your nick")));
assert!(t.iter().any(|l| l.contains("OP/DEOP")));
assert!(t.last().unwrap().contains("HELP <command>"));
}
#[test]
fn topic_shows_detail_lines_only() {
let mut ctx = ServiceCtx::default();
help("Svc", &sender(), &mut ctx, "the blurb", T, Some("register"));
let t = texts(&ctx);
assert!(t[0].contains("Syntax: REGISTER"));
assert!(t.iter().any(|l| l == "Registers your nick."));
assert!(!t.iter().any(|l| l == "the blurb"));
}
#[test]
fn topic_matches_a_slash_alias() {
let mut ctx = ServiceCtx::default();
help("Svc", &sender(), &mut ctx, "b", T, Some("DEOP"));
assert!(texts(&ctx)[0].contains("Syntax: OP"));
}
#[test]
fn unknown_topic_reports_no_help() {
let mut ctx = ServiceCtx::default();
help("Svc", &sender(), &mut ctx, "b", T, Some("nope"));
assert!(texts(&ctx)[0].contains("No help"));
}
}