Files
storkit/server/src/chat/transport/matrix/commands/help.rs

114 lines
3.9 KiB
Rust
Raw Normal View History

//! Handler for the `help` command.
use super::{commands, CommandContext};
pub(super) fn handle_help(ctx: &CommandContext) -> Option<String> {
let mut output = format!("**{} Commands**\n\n", ctx.bot_name);
let mut sorted: Vec<_> = commands().iter().collect();
sorted.sort_by_key(|c| c.name);
for cmd in sorted {
output.push_str(&format!("- **{}** — {}\n", cmd.name, cmd.description));
}
Some(output)
}
#[cfg(test)]
mod tests {
use super::super::tests::{try_cmd_addressed, commands};
#[test]
fn help_command_matches() {
let result = try_cmd_addressed("Timmy", "@timmy:homeserver.local", "@timmy help");
assert!(result.is_some(), "help command should match");
}
#[test]
fn help_command_case_insensitive() {
let result = try_cmd_addressed("Timmy", "@timmy:homeserver.local", "@timmy HELP");
assert!(result.is_some(), "HELP should match case-insensitively");
}
#[test]
fn help_output_contains_all_commands() {
let result = try_cmd_addressed("Timmy", "@timmy:homeserver.local", "@timmy help");
let output = result.unwrap();
for cmd in commands() {
assert!(
output.contains(cmd.name),
"help output must include command '{}'",
cmd.name
);
assert!(
output.contains(cmd.description),
"help output must include description for '{}'",
cmd.name
);
}
}
#[test]
fn help_output_uses_bot_name() {
let result = try_cmd_addressed("HAL", "@hal:example.com", "@hal help");
let output = result.unwrap();
assert!(
output.contains("HAL Commands"),
"help output should use bot name: {output}"
);
}
#[test]
fn help_output_formatted_as_markdown() {
let result = try_cmd_addressed("Timmy", "@timmy:homeserver.local", "@timmy help");
let output = result.unwrap();
assert!(
output.contains("**help**"),
"command name should be bold: {output}"
);
assert!(
output.contains("- **"),
"commands should be in a list: {output}"
);
}
#[test]
fn help_output_includes_status() {
let result = try_cmd_addressed("Timmy", "@timmy:homeserver.local", "@timmy help");
let output = result.unwrap();
assert!(output.contains("status"), "help should list status command: {output}");
}
#[test]
fn help_output_is_alphabetical() {
let result = try_cmd_addressed("Timmy", "@timmy:homeserver.local", "@timmy help");
let output = result.unwrap();
// Search for **name** (bold markdown) to avoid substring matches in descriptions.
let mut positions: Vec<(usize, &str)> = commands()
.iter()
.map(|c| {
let marker = format!("**{}**", c.name);
let pos = output.find(&marker).expect("command must appear in help as **name**");
(pos, c.name)
})
.collect();
positions.sort_by_key(|(pos, _)| *pos);
let names_in_order: Vec<&str> = positions.iter().map(|(_, n)| *n).collect();
let mut sorted = names_in_order.clone();
sorted.sort();
assert_eq!(names_in_order, sorted, "commands must appear in alphabetical order");
}
#[test]
fn help_output_includes_ambient() {
let result = try_cmd_addressed("Timmy", "@timmy:homeserver.local", "@timmy help");
let output = result.unwrap();
assert!(output.contains("ambient"), "help should list ambient command: {output}");
}
#[test]
fn help_output_includes_htop() {
let result = try_cmd_addressed("Timmy", "@timmy:homeserver.local", "@timmy help");
let output = result.unwrap();
assert!(output.contains("htop"), "help should list htop command: {output}");
}
}