test: REPL command tests and CLI flag tests

This commit is contained in:
2026-05-01 11:57:17 -06:00
parent 5168eb6781
commit 27ceefdb40
8 changed files with 796 additions and 43 deletions
+217
View File
@@ -176,3 +176,220 @@ impl Cli {
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use clap::Parser;
fn parse(args: &[&str]) -> Cli {
let mut full_args = vec!["loki"];
full_args.extend_from_slice(args);
Cli::try_parse_from(full_args).unwrap()
}
#[test]
fn parse_no_args_defaults() {
let cli = parse(&[]);
assert!(cli.model.is_none());
assert!(cli.role.is_none());
assert!(cli.session.is_none());
assert!(cli.agent.is_none());
assert!(!cli.execute);
assert!(!cli.code);
assert!(!cli.no_stream);
assert!(!cli.dry_run);
assert!(!cli.info);
assert!(!cli.build_tools);
assert!(cli.file.is_empty());
assert!(cli.text.is_empty());
}
#[test]
fn parse_model_flag() {
let cli = parse(&["--model", "gpt-4o"]);
assert_eq!(cli.model, Some("gpt-4o".to_string()));
}
#[test]
fn parse_model_short_flag() {
let cli = parse(&["-m", "gpt-4o"]);
assert_eq!(cli.model, Some("gpt-4o".to_string()));
}
#[test]
fn parse_role_flag() {
let cli = parse(&["--role", "coder"]);
assert_eq!(cli.role, Some("coder".to_string()));
}
#[test]
fn parse_session_with_name() {
let cli = parse(&["--session", "my-session"]);
assert_eq!(cli.session, Some(Some("my-session".to_string())));
}
#[test]
fn parse_agent_flag() {
let cli = parse(&["--agent", "sisyphus"]);
assert_eq!(cli.agent, Some("sisyphus".to_string()));
}
#[test]
fn parse_agent_short_flag() {
let cli = parse(&["-a", "sisyphus"]);
assert_eq!(cli.agent, Some("sisyphus".to_string()));
}
#[test]
fn parse_execute_flag() {
let cli = parse(&["-e", "list files"]);
assert!(cli.execute);
}
#[test]
fn parse_code_flag() {
let cli = parse(&["-c", "hello world"]);
assert!(cli.code);
}
#[test]
fn parse_no_stream_flag() {
let cli = parse(&["-S", "test"]);
assert!(cli.no_stream);
}
#[test]
fn parse_dry_run_flag() {
let cli = parse(&["--dry-run", "test"]);
assert!(cli.dry_run);
}
#[test]
fn parse_info_flag() {
let cli = parse(&["--info"]);
assert!(cli.info);
}
#[test]
fn parse_list_flags() {
assert!(parse(&["--list-models"]).list_models);
assert!(parse(&["--list-roles"]).list_roles);
assert!(parse(&["--list-sessions"]).list_sessions);
assert!(parse(&["--list-agents"]).list_agents);
assert!(parse(&["--list-rags"]).list_rags);
assert!(parse(&["--list-macros"]).list_macros);
}
#[test]
fn parse_file_flag_single() {
let cli = parse(&["-f", "file.txt", "question"]);
assert_eq!(cli.file, vec!["file.txt"]);
}
#[test]
fn parse_file_flag_multiple() {
let cli = parse(&["-f", "a.txt", "-f", "b.txt", "question"]);
assert_eq!(cli.file, vec!["a.txt", "b.txt"]);
}
#[test]
fn parse_trailing_text() {
let cli = parse(&["hello", "world"]);
assert_eq!(cli.text, vec!["hello", "world"]);
}
#[test]
fn parse_prompt_flag() {
let cli = parse(&["--prompt", "be a pirate"]);
assert_eq!(cli.prompt, Some("be a pirate".to_string()));
}
#[test]
fn parse_empty_session_flag() {
let cli = parse(&["--session", "s", "--empty-session"]);
assert!(cli.empty_session);
}
#[test]
fn parse_save_session_flag() {
let cli = parse(&["--session", "s", "--save-session"]);
assert!(cli.save_session);
}
#[test]
fn parse_build_tools_flag() {
let cli = parse(&["--build-tools"]);
assert!(cli.build_tools);
}
#[test]
fn parse_sync_models_flag() {
let cli = parse(&["--sync-models"]);
assert!(cli.sync_models);
}
#[test]
fn parse_model_with_role() {
let cli = parse(&["-m", "gpt-4o", "-r", "coder"]);
assert_eq!(cli.model, Some("gpt-4o".to_string()));
assert_eq!(cli.role, Some("coder".to_string()));
}
#[test]
fn parse_agent_with_file_and_text() {
let cli = parse(&["-a", "sisyphus", "-f", "code.rs", "explain", "this"]);
assert_eq!(cli.agent, Some("sisyphus".to_string()));
assert_eq!(cli.file, vec!["code.rs"]);
assert_eq!(cli.text, vec!["explain", "this"]);
}
#[test]
fn parse_role_with_session() {
let cli = parse(&["-r", "coder", "-s", "dev-session"]);
assert_eq!(cli.role, Some("coder".to_string()));
assert_eq!(cli.session, Some(Some("dev-session".to_string())));
}
#[test]
fn cli_text_returns_none_when_no_text_no_stdin() {
let cli = parse(&[]);
assert!(cli.text().unwrap().is_none());
}
#[test]
fn cli_text_joins_trailing_args() {
let cli = parse(&["hello", "world"]);
assert_eq!(cli.text().unwrap(), Some("hello world".to_string()));
}
#[test]
fn parse_add_secret_flag() {
let cli = parse(&["--add-secret", "MY_KEY"]);
assert_eq!(cli.add_secret, Some("MY_KEY".to_string()));
}
#[test]
fn parse_get_secret_flag() {
let cli = parse(&["--get-secret", "MY_KEY"]);
assert_eq!(cli.get_secret, Some("MY_KEY".to_string()));
}
#[test]
fn parse_list_secrets_flag() {
let cli = parse(&["--list-secrets"]);
assert!(cli.list_secrets);
}
#[test]
fn parse_rag_flag() {
let cli = parse(&["--rag", "my-rag"]);
assert_eq!(cli.rag, Some("my-rag".to_string()));
}
#[test]
fn parse_macro_flag() {
let cli = parse(&["--macro", "my-macro"]);
assert_eq!(cli.macro_name, Some("my-macro".to_string()));
}
}
+74
View File
@@ -596,4 +596,78 @@ mod tests {
assert!(cfg.enabled_tools.is_none());
assert!(cfg.enabled_mcp_servers.is_none());
}
#[test]
fn assert_state_pass_always_true() {
let pass = AssertState::pass();
assert!(pass.assert(StateFlags::empty()));
assert!(pass.assert(StateFlags::ROLE));
assert!(pass.assert(StateFlags::SESSION | StateFlags::AGENT));
assert!(pass.assert(StateFlags::all()));
}
#[test]
fn assert_state_bare_only_empty() {
let bare = AssertState::bare();
assert!(bare.assert(StateFlags::empty()));
assert!(!bare.assert(StateFlags::ROLE));
assert!(!bare.assert(StateFlags::SESSION));
}
#[test]
fn assert_state_true_requires_flag_present() {
let state = AssertState::True(StateFlags::ROLE);
assert!(state.assert(StateFlags::ROLE));
assert!(state.assert(StateFlags::ROLE | StateFlags::SESSION));
assert!(!state.assert(StateFlags::empty()));
assert!(!state.assert(StateFlags::SESSION));
}
#[test]
fn assert_state_true_with_multiple_flags_any_match() {
let state = AssertState::True(StateFlags::SESSION_EMPTY | StateFlags::SESSION);
assert!(state.assert(StateFlags::SESSION_EMPTY));
assert!(state.assert(StateFlags::SESSION));
assert!(state.assert(StateFlags::SESSION | StateFlags::ROLE));
assert!(!state.assert(StateFlags::ROLE));
assert!(!state.assert(StateFlags::empty()));
}
#[test]
fn assert_state_false_requires_flag_absent() {
let state = AssertState::False(StateFlags::AGENT);
assert!(state.assert(StateFlags::empty()));
assert!(state.assert(StateFlags::ROLE));
assert!(!state.assert(StateFlags::AGENT));
assert!(!state.assert(StateFlags::AGENT | StateFlags::ROLE));
}
#[test]
fn assert_state_false_with_multiple_flags() {
let state = AssertState::False(StateFlags::SESSION | StateFlags::AGENT);
assert!(state.assert(StateFlags::empty()));
assert!(state.assert(StateFlags::ROLE));
assert!(!state.assert(StateFlags::SESSION));
assert!(!state.assert(StateFlags::AGENT));
assert!(!state.assert(StateFlags::SESSION | StateFlags::AGENT));
}
#[test]
fn assert_state_truefalse_requires_true_present_and_false_absent() {
let state = AssertState::TrueFalse(StateFlags::ROLE, StateFlags::SESSION);
assert!(state.assert(StateFlags::ROLE));
assert!(state.assert(StateFlags::ROLE | StateFlags::RAG));
assert!(!state.assert(StateFlags::empty()));
assert!(!state.assert(StateFlags::SESSION));
assert!(!state.assert(StateFlags::ROLE | StateFlags::SESSION));
}
#[test]
fn assert_state_equal_exact_match() {
let state = AssertState::Equal(StateFlags::ROLE | StateFlags::SESSION);
assert!(state.assert(StateFlags::ROLE | StateFlags::SESSION));
assert!(!state.assert(StateFlags::ROLE));
assert!(!state.assert(StateFlags::SESSION));
assert!(!state.assert(StateFlags::empty()));
}
}
+1 -1
View File
@@ -168,8 +168,8 @@ impl McpRuntime {
#[cfg(test)]
mod tests {
use crate::function::ToolCall;
use super::*;
use crate::function::ToolCall;
#[test]
fn mcp_runtime_new_is_empty() {
+244
View File
@@ -1186,4 +1186,248 @@ mod tests {
(vec![".\\file.txt".into(), "C:\\dir\\file.txt".into()], "")
);
}
#[test]
fn repl_commands_has_39_entries() {
assert_eq!(REPL_COMMANDS.len(), 39);
}
#[test]
fn repl_commands_all_start_with_dot() {
for cmd in REPL_COMMANDS.iter() {
assert!(
cmd.name.starts_with('.'),
"Command '{}' should start with '.'",
cmd.name
);
}
}
#[test]
fn repl_commands_no_empty_descriptions() {
for cmd in REPL_COMMANDS.iter() {
assert!(
!cmd.description.is_empty(),
"Command '{}' has empty description",
cmd.name
);
}
}
#[test]
fn repl_commands_help_is_always_available() {
let help = REPL_COMMANDS.iter().find(|c| c.name == ".help").unwrap();
assert!(help.is_valid(StateFlags::empty()));
assert!(help.is_valid(StateFlags::ROLE));
assert!(help.is_valid(StateFlags::AGENT));
}
#[test]
fn repl_commands_exit_is_always_available() {
let exit = REPL_COMMANDS.iter().find(|c| c.name == ".exit").unwrap();
assert!(exit.is_valid(StateFlags::empty()));
assert!(exit.is_valid(StateFlags::all()));
}
#[test]
fn repl_commands_info_role_requires_role() {
let cmd = REPL_COMMANDS
.iter()
.find(|c| c.name == ".info role")
.unwrap();
assert!(cmd.is_valid(StateFlags::ROLE));
assert!(!cmd.is_valid(StateFlags::empty()));
assert!(!cmd.is_valid(StateFlags::SESSION_EMPTY));
}
#[test]
fn repl_commands_session_blocked_when_already_in_session() {
let cmd = REPL_COMMANDS.iter().find(|c| c.name == ".session").unwrap();
assert!(cmd.is_valid(StateFlags::empty()));
assert!(!cmd.is_valid(StateFlags::SESSION));
assert!(!cmd.is_valid(StateFlags::SESSION_EMPTY));
}
#[test]
fn repl_commands_exit_session_requires_session() {
let cmd = REPL_COMMANDS
.iter()
.find(|c| c.name == ".exit session")
.unwrap();
assert!(cmd.is_valid(StateFlags::SESSION));
assert!(cmd.is_valid(StateFlags::SESSION_EMPTY));
assert!(!cmd.is_valid(StateFlags::empty()));
}
#[test]
fn repl_commands_exit_agent_requires_agent() {
let cmd = REPL_COMMANDS
.iter()
.find(|c| c.name == ".exit agent")
.unwrap();
assert!(cmd.is_valid(StateFlags::AGENT));
assert!(!cmd.is_valid(StateFlags::empty()));
}
#[test]
fn repl_commands_agent_only_when_bare() {
let cmd = REPL_COMMANDS.iter().find(|c| c.name == ".agent").unwrap();
assert!(cmd.is_valid(StateFlags::empty()));
assert!(!cmd.is_valid(StateFlags::ROLE));
assert!(!cmd.is_valid(StateFlags::SESSION));
assert!(!cmd.is_valid(StateFlags::AGENT));
}
#[test]
fn repl_commands_role_blocked_in_session_or_agent() {
let cmd = REPL_COMMANDS.iter().find(|c| c.name == ".role").unwrap();
assert!(cmd.is_valid(StateFlags::empty()));
assert!(!cmd.is_valid(StateFlags::SESSION));
assert!(!cmd.is_valid(StateFlags::AGENT));
}
#[test]
fn repl_commands_prompt_blocked_in_session_or_agent() {
let cmd = REPL_COMMANDS.iter().find(|c| c.name == ".prompt").unwrap();
assert!(cmd.is_valid(StateFlags::empty()));
assert!(cmd.is_valid(StateFlags::ROLE));
assert!(!cmd.is_valid(StateFlags::SESSION));
assert!(!cmd.is_valid(StateFlags::AGENT));
}
#[test]
fn repl_commands_rag_blocked_in_agent() {
let cmd = REPL_COMMANDS.iter().find(|c| c.name == ".rag").unwrap();
assert!(cmd.is_valid(StateFlags::empty()));
assert!(cmd.is_valid(StateFlags::ROLE));
assert!(!cmd.is_valid(StateFlags::AGENT));
}
#[test]
fn repl_commands_starter_requires_agent() {
let cmd = REPL_COMMANDS.iter().find(|c| c.name == ".starter").unwrap();
assert!(cmd.is_valid(StateFlags::AGENT));
assert!(!cmd.is_valid(StateFlags::empty()));
}
#[test]
fn repl_commands_clear_todo_requires_agent() {
let cmd = REPL_COMMANDS
.iter()
.find(|c| c.name == ".clear todo")
.unwrap();
assert!(cmd.is_valid(StateFlags::AGENT));
assert!(!cmd.is_valid(StateFlags::empty()));
}
#[test]
fn repl_commands_edit_role_requires_role_not_session() {
let cmd = REPL_COMMANDS
.iter()
.find(|c| c.name == ".edit role")
.unwrap();
assert!(cmd.is_valid(StateFlags::ROLE));
assert!(!cmd.is_valid(StateFlags::empty()));
assert!(!cmd.is_valid(StateFlags::ROLE | StateFlags::SESSION));
}
#[test]
fn repl_commands_exit_rag_requires_rag_not_agent() {
let cmd = REPL_COMMANDS
.iter()
.find(|c| c.name == ".exit rag")
.unwrap();
assert!(cmd.is_valid(StateFlags::RAG));
assert!(!cmd.is_valid(StateFlags::empty()));
assert!(!cmd.is_valid(StateFlags::RAG | StateFlags::AGENT));
}
#[test]
fn parse_command_plain_text_returns_none() {
assert!(parse_command("hello world").is_none());
}
#[test]
fn parse_command_empty_returns_none() {
assert!(parse_command("").is_none());
}
#[test]
fn parse_command_whitespace_only_returns_none() {
assert!(parse_command(" ").is_none());
}
#[test]
fn parse_command_dot_only() {
assert_eq!(parse_command("."), Some((".", None)));
}
#[test]
fn split_first_arg_none_input() {
assert!(split_first_arg(None).is_none());
}
#[test]
fn split_first_arg_single_word() {
assert_eq!(split_first_arg(Some("role")), Some(("role", None)));
}
#[test]
fn split_first_arg_two_words() {
assert_eq!(
split_first_arg(Some("role test-role")),
Some(("role", Some("test-role")))
);
}
#[test]
fn split_first_arg_with_extra_spaces() {
assert_eq!(
split_first_arg(Some("session my-session")),
Some(("session", Some("my-session")))
);
}
#[test]
fn repl_command_is_valid_pass_always_true() {
let cmd = ReplCommand::new(".test", "desc", AssertState::pass());
assert!(cmd.is_valid(StateFlags::empty()));
assert!(cmd.is_valid(StateFlags::all()));
}
#[test]
fn repl_command_is_valid_respects_true() {
let cmd = ReplCommand::new(".test", "desc", AssertState::True(StateFlags::ROLE));
assert!(cmd.is_valid(StateFlags::ROLE));
assert!(!cmd.is_valid(StateFlags::empty()));
}
#[test]
fn repl_command_is_valid_respects_false() {
let cmd = ReplCommand::new(".test", "desc", AssertState::False(StateFlags::AGENT));
assert!(cmd.is_valid(StateFlags::empty()));
assert!(!cmd.is_valid(StateFlags::AGENT));
}
#[test]
fn multiline_regex_captures_content_between_markers() {
let input = ":::\nhello world\n:::";
let captures = MULTILINE_RE.captures(input).unwrap().unwrap();
let content = captures.get(1).unwrap().as_str();
assert_eq!(content.trim(), "hello world");
}
#[test]
fn multiline_regex_does_not_match_single_marker() {
let input = ":::\nhello world";
let result = MULTILINE_RE.captures(input).unwrap();
assert!(result.is_none());
}
#[test]
fn multiline_regex_does_not_match_plain_text() {
let input = "hello world";
let result = MULTILINE_RE.captures(input).unwrap();
assert!(result.is_none());
}
}