291 lines
7.2 KiB
Rust
291 lines
7.2 KiB
Rust
|
|
use std::{
|
||
|
|
fs::{self, Permissions},
|
||
|
|
os::unix::fs::PermissionsExt,
|
||
|
|
path::Path,
|
||
|
|
time::Duration,
|
||
|
|
};
|
||
|
|
|
||
|
|
use robotnik::command::CommandDir;
|
||
|
|
use tempfile::TempDir;
|
||
|
|
|
||
|
|
/// Helper to create executable test scripts
|
||
|
|
fn create_command(dir: &Path, name: &str, script: &str) {
|
||
|
|
let path = dir.join(name);
|
||
|
|
fs::write(&path, script).unwrap();
|
||
|
|
fs::set_permissions(&path, Permissions::from_mode(0o755)).unwrap();
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Parse a bot message like "!weather 73135" into (command_name, argument)
|
||
|
|
fn parse_bot_message(message: &str) -> Option<(&str, &str)> {
|
||
|
|
if !message.starts_with('!') {
|
||
|
|
return None;
|
||
|
|
}
|
||
|
|
let without_prefix = &message[1..];
|
||
|
|
let mut parts = without_prefix.splitn(2, ' ');
|
||
|
|
let command = parts.next()?;
|
||
|
|
let arg = parts.next().unwrap_or("");
|
||
|
|
Some((command, arg))
|
||
|
|
}
|
||
|
|
|
||
|
|
#[tokio::test]
|
||
|
|
async fn test_bot_message_finds_and_runs_command() {
|
||
|
|
let temp = TempDir::new().unwrap();
|
||
|
|
|
||
|
|
// Create a weather command that echoes the zip code
|
||
|
|
create_command(
|
||
|
|
temp.path(),
|
||
|
|
"weather",
|
||
|
|
r#"#!/bin/bash
|
||
|
|
echo "Weather for $1: Sunny, 72°F"
|
||
|
|
"#,
|
||
|
|
);
|
||
|
|
|
||
|
|
let cmd_dir = CommandDir::new(temp.path());
|
||
|
|
let message = "!weather 73135";
|
||
|
|
|
||
|
|
// Parse the message
|
||
|
|
let (command_name, arg) = parse_bot_message(message).unwrap();
|
||
|
|
assert_eq!(command_name, "weather");
|
||
|
|
assert_eq!(arg, "73135");
|
||
|
|
|
||
|
|
// Find and run the command
|
||
|
|
let result = cmd_dir.run_command(command_name, arg).await;
|
||
|
|
|
||
|
|
assert!(result.is_ok());
|
||
|
|
let bytes = result.unwrap();
|
||
|
|
let output = String::from_utf8_lossy(&bytes);
|
||
|
|
assert!(output.contains("Weather for 73135"));
|
||
|
|
assert!(output.contains("Sunny"));
|
||
|
|
}
|
||
|
|
|
||
|
|
#[tokio::test]
|
||
|
|
async fn test_bot_message_command_not_found() {
|
||
|
|
let temp = TempDir::new().unwrap();
|
||
|
|
let cmd_dir = CommandDir::new(temp.path());
|
||
|
|
|
||
|
|
let message = "!nonexistent arg";
|
||
|
|
let (command_name, arg) = parse_bot_message(message).unwrap();
|
||
|
|
|
||
|
|
let result = cmd_dir.run_command(command_name, arg).await;
|
||
|
|
|
||
|
|
assert!(result.is_err());
|
||
|
|
assert!(result.unwrap_err().to_string().contains("Not found"));
|
||
|
|
}
|
||
|
|
|
||
|
|
#[tokio::test]
|
||
|
|
async fn test_bot_message_with_multiple_arguments() {
|
||
|
|
let temp = TempDir::new().unwrap();
|
||
|
|
|
||
|
|
// Create a command that handles multiple words as a single argument
|
||
|
|
create_command(
|
||
|
|
temp.path(),
|
||
|
|
"echo",
|
||
|
|
r#"#!/bin/bash
|
||
|
|
echo "You said: $1"
|
||
|
|
"#,
|
||
|
|
);
|
||
|
|
|
||
|
|
let cmd_dir = CommandDir::new(temp.path());
|
||
|
|
let message = "!echo hello world how are you";
|
||
|
|
|
||
|
|
let (command_name, arg) = parse_bot_message(message).unwrap();
|
||
|
|
assert_eq!(command_name, "echo");
|
||
|
|
assert_eq!(arg, "hello world how are you");
|
||
|
|
|
||
|
|
let result = cmd_dir.run_command(command_name, arg).await;
|
||
|
|
|
||
|
|
assert!(result.is_ok());
|
||
|
|
let bytes = result.unwrap();
|
||
|
|
let output = String::from_utf8_lossy(&bytes);
|
||
|
|
assert!(output.contains("hello world how are you"));
|
||
|
|
}
|
||
|
|
|
||
|
|
#[tokio::test]
|
||
|
|
async fn test_bot_message_without_argument() {
|
||
|
|
let temp = TempDir::new().unwrap();
|
||
|
|
|
||
|
|
create_command(
|
||
|
|
temp.path(),
|
||
|
|
"help",
|
||
|
|
r#"#!/bin/bash
|
||
|
|
echo "Available commands: weather, echo, help"
|
||
|
|
"#,
|
||
|
|
);
|
||
|
|
|
||
|
|
let cmd_dir = CommandDir::new(temp.path());
|
||
|
|
let message = "!help";
|
||
|
|
|
||
|
|
let (command_name, arg) = parse_bot_message(message).unwrap();
|
||
|
|
assert_eq!(command_name, "help");
|
||
|
|
assert_eq!(arg, "");
|
||
|
|
|
||
|
|
let result = cmd_dir.run_command(command_name, arg).await;
|
||
|
|
|
||
|
|
assert!(result.is_ok());
|
||
|
|
let bytes = result.unwrap();
|
||
|
|
let output = String::from_utf8_lossy(&bytes);
|
||
|
|
assert!(output.contains("Available commands"));
|
||
|
|
}
|
||
|
|
|
||
|
|
#[tokio::test]
|
||
|
|
async fn test_bot_message_command_returns_error_exit_code() {
|
||
|
|
let temp = TempDir::new().unwrap();
|
||
|
|
|
||
|
|
// Create a command that fails for invalid input
|
||
|
|
create_command(
|
||
|
|
temp.path(),
|
||
|
|
"validate",
|
||
|
|
r#"#!/bin/bash
|
||
|
|
if [ -z "$1" ]; then
|
||
|
|
echo "Error: Input required" >&2
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
echo "Valid: $1"
|
||
|
|
"#,
|
||
|
|
);
|
||
|
|
|
||
|
|
let cmd_dir = CommandDir::new(temp.path());
|
||
|
|
let message = "!validate";
|
||
|
|
|
||
|
|
let (command_name, arg) = parse_bot_message(message).unwrap();
|
||
|
|
let result = cmd_dir.run_command(command_name, arg).await;
|
||
|
|
|
||
|
|
assert!(result.is_err());
|
||
|
|
assert!(result.unwrap_err().to_string().contains("Error running"));
|
||
|
|
}
|
||
|
|
|
||
|
|
#[tokio::test]
|
||
|
|
async fn test_bot_message_with_timeout() {
|
||
|
|
let temp = TempDir::new().unwrap();
|
||
|
|
|
||
|
|
create_command(
|
||
|
|
temp.path(),
|
||
|
|
"quick",
|
||
|
|
r#"#!/bin/bash
|
||
|
|
echo "Result: $1"
|
||
|
|
"#,
|
||
|
|
);
|
||
|
|
|
||
|
|
let cmd_dir = CommandDir::new(temp.path());
|
||
|
|
let message = "!quick test";
|
||
|
|
|
||
|
|
let (command_name, arg) = parse_bot_message(message).unwrap();
|
||
|
|
let result = cmd_dir
|
||
|
|
.run_command_with_timeout(command_name, arg, Duration::from_secs(5))
|
||
|
|
.await;
|
||
|
|
|
||
|
|
assert!(result.is_ok());
|
||
|
|
}
|
||
|
|
|
||
|
|
#[tokio::test]
|
||
|
|
async fn test_bot_message_command_times_out() {
|
||
|
|
let temp = TempDir::new().unwrap();
|
||
|
|
|
||
|
|
create_command(
|
||
|
|
temp.path(),
|
||
|
|
"slow",
|
||
|
|
r#"#!/bin/bash
|
||
|
|
sleep 10
|
||
|
|
echo "Done"
|
||
|
|
"#,
|
||
|
|
);
|
||
|
|
|
||
|
|
let cmd_dir = CommandDir::new(temp.path());
|
||
|
|
let message = "!slow arg";
|
||
|
|
|
||
|
|
let (command_name, arg) = parse_bot_message(message).unwrap();
|
||
|
|
let result = cmd_dir
|
||
|
|
.run_command_with_timeout(command_name, arg, Duration::from_millis(100))
|
||
|
|
.await;
|
||
|
|
|
||
|
|
assert!(result.is_err());
|
||
|
|
}
|
||
|
|
|
||
|
|
#[tokio::test]
|
||
|
|
async fn test_multiple_commands_in_directory() {
|
||
|
|
let temp = TempDir::new().unwrap();
|
||
|
|
|
||
|
|
create_command(
|
||
|
|
temp.path(),
|
||
|
|
"weather",
|
||
|
|
r#"#!/bin/bash
|
||
|
|
echo "Weather: Sunny"
|
||
|
|
"#,
|
||
|
|
);
|
||
|
|
|
||
|
|
create_command(
|
||
|
|
temp.path(),
|
||
|
|
"time",
|
||
|
|
r#"#!/bin/bash
|
||
|
|
echo "Time: 12:00"
|
||
|
|
"#,
|
||
|
|
);
|
||
|
|
|
||
|
|
create_command(
|
||
|
|
temp.path(),
|
||
|
|
"joke",
|
||
|
|
r#"#!/bin/bash
|
||
|
|
echo "Why did the robot go on vacation? To recharge!"
|
||
|
|
"#,
|
||
|
|
);
|
||
|
|
|
||
|
|
let cmd_dir = CommandDir::new(temp.path());
|
||
|
|
|
||
|
|
// Test each command
|
||
|
|
let messages = ["!weather", "!time", "!joke"];
|
||
|
|
let expected = ["Sunny", "12:00", "recharge"];
|
||
|
|
|
||
|
|
for (message, expect) in messages.iter().zip(expected.iter()) {
|
||
|
|
let (command_name, arg) = parse_bot_message(message).unwrap();
|
||
|
|
let result = cmd_dir.run_command(command_name, arg).await;
|
||
|
|
assert!(result.is_ok());
|
||
|
|
let bytes = result.unwrap();
|
||
|
|
let output = String::from_utf8_lossy(&bytes);
|
||
|
|
assert!(
|
||
|
|
output.contains(expect),
|
||
|
|
"Expected '{}' in '{}'",
|
||
|
|
expect,
|
||
|
|
output
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[tokio::test]
|
||
|
|
async fn test_non_bot_message_ignored() {
|
||
|
|
// Messages not starting with ! should be ignored
|
||
|
|
let messages = ["hello world", "weather 73135", "?help", "/command", ""];
|
||
|
|
|
||
|
|
for message in messages {
|
||
|
|
assert!(
|
||
|
|
parse_bot_message(message).is_none(),
|
||
|
|
"Should ignore: {}",
|
||
|
|
message
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[tokio::test]
|
||
|
|
async fn test_command_output_is_bytes() {
|
||
|
|
let temp = TempDir::new().unwrap();
|
||
|
|
|
||
|
|
// Create a command that outputs binary-safe content
|
||
|
|
create_command(
|
||
|
|
temp.path(),
|
||
|
|
"binary",
|
||
|
|
r#"#!/bin/bash
|
||
|
|
printf "Hello\x00World"
|
||
|
|
"#,
|
||
|
|
);
|
||
|
|
|
||
|
|
let cmd_dir = CommandDir::new(temp.path());
|
||
|
|
let message = "!binary test";
|
||
|
|
|
||
|
|
let (command_name, arg) = parse_bot_message(message).unwrap();
|
||
|
|
let result = cmd_dir.run_command(command_name, arg).await;
|
||
|
|
|
||
|
|
assert!(result.is_ok());
|
||
|
|
let output = result.unwrap();
|
||
|
|
// Should preserve the null byte
|
||
|
|
assert_eq!(&output[..], b"Hello\x00World");
|
||
|
|
}
|