use super::*;
fn default_ccs() -> CcsConfig {
CcsConfig::default()
}
#[test]
fn test_parse_ccs_ref() {
assert_eq!(parse_ccs_ref("ccs"), Some(""));
assert_eq!(parse_ccs_ref("ccs/work"), Some("work"));
assert_eq!(parse_ccs_ref("ccs/personal"), Some("personal"));
assert_eq!(parse_ccs_ref("ccs/gemini"), Some("gemini"));
assert_eq!(
parse_ccs_ref("ccs/my-custom-alias"),
Some("my-custom-alias")
);
assert_eq!(parse_ccs_ref("claude"), None);
assert_eq!(parse_ccs_ref("codex"), None);
assert_eq!(parse_ccs_ref("ccs_work"), None);
assert_eq!(parse_ccs_ref("cccs/work"), None);
assert_eq!(parse_ccs_ref(""), None);
}
#[test]
fn test_is_ccs_ref() {
assert!(is_ccs_ref("ccs"));
assert!(is_ccs_ref("ccs/work"));
assert!(is_ccs_ref("ccs/gemini"));
assert!(!is_ccs_ref("claude"));
assert!(!is_ccs_ref("codex"));
}
#[test]
fn test_resolve_ccs_agent_default() {
let aliases = HashMap::new();
let config = resolve_ccs_agent("", &aliases, &default_ccs());
assert!(config.is_some());
let config = config.unwrap();
assert!(
config.cmd.ends_with("claude") || config.cmd == "ccs",
"cmd should be 'ccs' or a path ending with 'claude', got: {}",
config.cmd
);
assert!(config.can_commit);
assert_eq!(config.json_parser, JsonParserType::Claude);
}
#[test]
fn test_resolve_ccs_agent_with_alias() {
let mut aliases = HashMap::new();
aliases.insert(
"work".to_string(),
CcsAliasConfig {
cmd: "ccs work".to_string(),
..CcsAliasConfig::default()
},
);
aliases.insert(
"gemini".to_string(),
CcsAliasConfig {
cmd: "ccs gemini".to_string(),
..CcsAliasConfig::default()
},
);
let config = resolve_ccs_agent("work", &aliases, &default_ccs());
assert!(config.is_some());
let config = config.unwrap();
assert!(
config.cmd.ends_with("claude") || config.cmd == "ccs work",
"cmd should be 'ccs work' or a path ending with 'claude', got: {}",
config.cmd
);
let config = resolve_ccs_agent("gemini", &aliases, &default_ccs());
assert!(config.is_some());
let config = config.unwrap();
assert!(
config.cmd.ends_with("claude") || config.cmd == "ccs gemini",
"cmd should be 'ccs gemini' or a path ending with 'claude', got: {}",
config.cmd
);
let config = resolve_ccs_agent("unknown", &aliases, &default_ccs());
assert!(config.is_none());
}
#[test]
fn test_build_ccs_agent_config() {
let config = build_ccs_agent_config(
&CcsAliasConfig {
cmd: "ccs work".to_string(),
..CcsAliasConfig::default()
},
&default_ccs(),
"ccs-work".to_string(),
"work",
);
assert!(
config.cmd.ends_with("claude") || config.cmd == "ccs work",
"cmd should be 'ccs work' or a path ending with 'claude', got: {}",
config.cmd
);
assert_eq!(config.output_flag, "--output-format=stream-json");
assert_eq!(config.yolo_flag, "--dangerously-skip-permissions");
assert_eq!(config.verbose_flag, "--verbose");
assert!(config.can_commit);
assert_eq!(config.json_parser, JsonParserType::Claude);
assert!(config.model_flag.is_none());
assert_eq!(config.display_name, Some("ccs-work".to_string()));
}
#[test]
fn test_ccs_alias_resolver_empty() {
let resolver = CcsAliasResolver::empty();
assert!(resolver.try_resolve("ccs").is_some());
assert!(resolver.try_resolve("ccs/unknown").is_some());
}
#[test]
fn test_ccs_alias_resolver_with_aliases_resolves() {
let mut aliases = HashMap::new();
aliases.insert(
"work".to_string(),
CcsAliasConfig {
cmd: "ccs work".to_string(),
..CcsAliasConfig::default()
},
);
aliases.insert(
"personal".to_string(),
CcsAliasConfig {
cmd: "ccs personal".to_string(),
..CcsAliasConfig::default()
},
);
let resolver = CcsAliasResolver::new(aliases, default_ccs());
let config = resolver.try_resolve("ccs/work");
assert!(config.is_some());
let work_cmd = config.unwrap().cmd;
assert!(
work_cmd.ends_with("claude") || work_cmd == "ccs work",
"cmd should be 'ccs work' or a path ending with 'claude', got: {work_cmd}"
);
let config = resolver.try_resolve("ccs/personal");
assert!(config.is_some());
let personal_cmd = config.unwrap().cmd;
assert!(
personal_cmd.ends_with("claude") || personal_cmd == "ccs personal",
"cmd should be 'ccs personal' or a path ending with 'claude', got: {personal_cmd}"
);
let config = resolver.try_resolve("ccs");
assert!(config.is_some());
let default_cmd = config.unwrap().cmd;
assert!(
default_cmd.ends_with("claude") || default_cmd == "ccs",
"cmd should be 'ccs' or a path ending with 'claude', got: {default_cmd}"
);
let config = resolver.try_resolve("ccs/unknown");
assert!(config.is_some());
let unknown_cmd = config.unwrap().cmd;
assert!(
unknown_cmd.ends_with("claude") || unknown_cmd == "ccs unknown",
"cmd should be 'ccs unknown' or a path ending with 'claude', got: {unknown_cmd}"
);
let config = resolver.try_resolve("claude");
assert!(config.is_none());
}
#[test]
fn test_ccs_references_resolve() {
let resolver = CcsAliasResolver::empty();
assert!(resolver.try_resolve("ccs").is_some());
assert!(resolver.try_resolve("ccs/work").is_some());
assert!(resolver.try_resolve("ccs/unknown").is_some());
assert!(resolver.try_resolve("claude").is_none());
assert!(resolver.try_resolve("codex").is_none());
}
#[test]
fn test_ccs_alias_resolver_multiple_aliases_resolve_correctly() {
let mut aliases = HashMap::new();
aliases.insert(
"work".to_string(),
CcsAliasConfig {
cmd: "ccs work".to_string(),
..CcsAliasConfig::default()
},
);
aliases.insert(
"personal".to_string(),
CcsAliasConfig {
cmd: "ccs personal".to_string(),
..CcsAliasConfig::default()
},
);
let resolver = CcsAliasResolver::new(aliases, default_ccs());
let work_config = resolver.try_resolve("ccs/work").unwrap();
assert!(
work_config.cmd.contains("work") || work_config.cmd.ends_with("claude"),
"work alias should resolve with 'work' in command or end with claude"
);
let personal_config = resolver.try_resolve("ccs/personal").unwrap();
assert!(
personal_config.cmd.contains("personal") || personal_config.cmd.ends_with("claude"),
"personal alias should resolve with 'personal' in command or end with claude"
);
}
#[test]
fn test_ccs_command_variants() {
let mut aliases = HashMap::new();
aliases.insert(
"work".to_string(),
CcsAliasConfig {
cmd: "ccs work".to_string(),
..CcsAliasConfig::default()
},
);
aliases.insert(
"personal".to_string(),
CcsAliasConfig {
cmd: "ccs personal".to_string(),
..CcsAliasConfig::default()
},
);
aliases.insert(
"gemini".to_string(),
CcsAliasConfig {
cmd: "ccs gemini".to_string(),
..CcsAliasConfig::default()
},
);
aliases.insert(
"codex".to_string(),
CcsAliasConfig {
cmd: "ccs codex".to_string(),
..CcsAliasConfig::default()
},
);
aliases.insert(
"glm".to_string(),
CcsAliasConfig {
cmd: "ccs glm".to_string(),
..CcsAliasConfig::default()
},
);
aliases.insert(
"openrouter".to_string(),
CcsAliasConfig {
cmd: "ccs api openrouter".to_string(),
..CcsAliasConfig::default()
},
);
aliases.insert(
"custom-api".to_string(),
CcsAliasConfig {
cmd: "ccs api custom-profile".to_string(),
..CcsAliasConfig::default()
},
);
let resolver = CcsAliasResolver::new(aliases, default_ccs());
let config = resolver.try_resolve("ccs/work").unwrap();
assert!(
config.cmd.ends_with("claude") || config.cmd == "ccs work",
"cmd should be 'ccs work' or a path ending with 'claude', got: {}",
config.cmd
);
let config = resolver.try_resolve("ccs/gemini").unwrap();
assert!(
config.cmd.ends_with("claude") || config.cmd == "ccs gemini",
"cmd should be 'ccs gemini' or a path ending with 'claude', got: {}",
config.cmd
);
let config = resolver.try_resolve("ccs/codex").unwrap();
assert!(
config.cmd.ends_with("claude") || config.cmd == "ccs codex",
"cmd should be 'ccs codex' or a path ending with 'claude', got: {}",
config.cmd
);
let config = resolver.try_resolve("ccs/glm").unwrap();
assert!(
config.cmd.ends_with("claude") || config.cmd == "ccs glm",
"cmd should be 'ccs glm' or a path ending with 'claude', got: {}",
config.cmd
);
let config = resolver.try_resolve("ccs/openrouter").unwrap();
assert!(
config.cmd.ends_with("claude") || config.cmd == "ccs api openrouter",
"cmd should be 'ccs api openrouter' or a path ending with 'claude', got: {}",
config.cmd
);
let config = resolver.try_resolve("ccs/custom-api").unwrap();
assert!(
config.cmd.ends_with("claude") || config.cmd == "ccs api custom-profile",
"cmd should be 'ccs api custom-profile' or a path ending with 'claude', got: {}",
config.cmd
);
}
#[test]
fn test_ccs_config_has_correct_flags() {
let config = build_ccs_agent_config(
&CcsAliasConfig {
cmd: "ccs gemini".to_string(),
..CcsAliasConfig::default()
},
&default_ccs(),
"ccs-gemini".to_string(),
"gemini",
);
assert_eq!(config.output_flag, "--output-format=stream-json");
assert_eq!(config.yolo_flag, "--dangerously-skip-permissions");
assert_eq!(config.verbose_flag, "--verbose");
assert_eq!(config.print_flag, "--print");
assert_eq!(config.session_flag, "--resume {}");
assert!(config.can_commit);
assert_eq!(config.json_parser, JsonParserType::Claude);
assert_eq!(config.display_name, Some("ccs-gemini".to_string()));
}
#[test]
fn test_parse_ccs_ref_edge_cases() {
assert_eq!(parse_ccs_ref("ccs/"), Some("")); assert_eq!(parse_ccs_ref("ccs/a"), Some("a")); assert_eq!(
parse_ccs_ref("ccs/with-dashes-and_underscores"),
Some("with-dashes-and_underscores")
);
assert_eq!(parse_ccs_ref("ccs/with.dots"), Some("with.dots"));
assert_eq!(parse_ccs_ref("ccs/MixedCase"), Some("MixedCase"));
assert_eq!(parse_ccs_ref("ccs/123numeric"), Some("123numeric"));
assert_eq!(parse_ccs_ref("CCS"), None); assert_eq!(parse_ccs_ref("CCS/work"), None);
assert_eq!(parse_ccs_ref(" ccs"), None); assert_eq!(parse_ccs_ref("ccs "), None); }
#[test]
fn test_ccs_in_agent_chain_context() {
let mut aliases = HashMap::new();
aliases.insert(
"work".to_string(),
CcsAliasConfig {
cmd: "ccs work".to_string(),
..CcsAliasConfig::default()
},
);
aliases.insert(
"personal".to_string(),
CcsAliasConfig {
cmd: "ccs personal".to_string(),
..CcsAliasConfig::default()
},
);
let resolver = CcsAliasResolver::new(aliases, default_ccs());
assert!(resolver.try_resolve("ccs/work").is_some());
assert!(resolver.try_resolve("claude").is_none()); assert!(resolver.try_resolve("codex").is_none());
let config = resolver.try_resolve("ccs/work").unwrap();
assert!(config.can_commit);
assert!(!config.cmd.is_empty());
}
#[test]
fn test_ccs_display_names() {
let mut aliases = HashMap::new();
aliases.insert(
"glm".to_string(),
CcsAliasConfig {
cmd: "ccs glm".to_string(),
..CcsAliasConfig::default()
},
);
aliases.insert(
"gemini".to_string(),
CcsAliasConfig {
cmd: "ccs gemini".to_string(),
..CcsAliasConfig::default()
},
);
aliases.insert(
"work".to_string(),
CcsAliasConfig {
cmd: "ccs work".to_string(),
..CcsAliasConfig::default()
},
);
let resolver = CcsAliasResolver::new(aliases, default_ccs());
let glm_config = resolver.try_resolve("ccs/glm").unwrap();
assert_eq!(glm_config.display_name, Some("ccs-glm".to_string()));
let gemini_config = resolver.try_resolve("ccs/gemini").unwrap();
assert_eq!(gemini_config.display_name, Some("ccs-gemini".to_string()));
let work_config = resolver.try_resolve("ccs/work").unwrap();
assert_eq!(work_config.display_name, Some("ccs-work".to_string()));
let default_config = resolver.try_resolve("ccs").unwrap();
assert_eq!(default_config.display_name, Some("ccs".to_string()));
}
#[test]
fn test_ccs_glm_command_has_print_flag() {
let mut aliases = HashMap::new();
aliases.insert(
"glm".to_string(),
CcsAliasConfig {
cmd: "ccs glm".to_string(),
..CcsAliasConfig::default()
},
);
let resolver = CcsAliasResolver::new(aliases, default_ccs());
let glm_config = resolver.try_resolve("ccs/glm").unwrap();
assert_eq!(glm_config.print_flag, "--print");
let cmd = glm_config.build_cmd(true, true, true);
assert!(
cmd.contains(" --print"),
"GLM command must include --print flag"
);
let first_word = cmd.split_whitespace().next().unwrap_or("");
assert!(
first_word.ends_with("claude") || cmd.contains("ccs glm"),
"Command should start with a path ending in 'claude' or contain 'ccs glm', got: {cmd}"
);
}
#[test]
fn test_ccs_glm_flag_ordering() {
let mut aliases = HashMap::new();
aliases.insert(
"glm".to_string(),
CcsAliasConfig {
cmd: "ccs glm".to_string(),
..CcsAliasConfig::default()
},
);
let resolver = CcsAliasResolver::new(aliases, default_ccs());
let glm_config = resolver.try_resolve("ccs/glm").unwrap();
let cmd = glm_config.build_cmd(true, true, true);
let parts: Vec<&str> = cmd.split_whitespace().collect();
let first_part = parts[0];
assert!(
first_part.ends_with("claude") || first_part == "ccs",
"First part should end with 'claude' or be 'ccs', got: {first_part}"
);
let p_index = parts.iter().position(|&s| s == "--print");
assert!(p_index.is_some(), "--print flag must be present");
assert!(
p_index.unwrap() > 0,
"--print flag must come after command name"
);
}
#[test]
fn test_ccs_glm_with_empty_print_override() {
let mut aliases = HashMap::new();
aliases.insert(
"glm".to_string(),
CcsAliasConfig {
cmd: "ccs glm".to_string(),
print_flag: Some(String::new()), ..CcsAliasConfig::default()
},
);
let resolver = CcsAliasResolver::new(aliases, default_ccs());
let glm_config = resolver.try_resolve("ccs/glm").unwrap();
assert_eq!(glm_config.print_flag, "");
let cmd = glm_config.build_cmd(true, true, true);
assert!(
!cmd.contains(" --print"),
"Command should not include --print when explicitly disabled"
);
}
#[test]
fn test_glm_error_classification() {
use crate::agents::error::AgentErrorKind;
let error = AgentErrorKind::classify_with_agent(1, "", Some("ccs/glm"), None);
assert_eq!(error, AgentErrorKind::RetryableAgentQuirk);
let error = AgentErrorKind::classify_with_agent(1, "some error", Some("ccs/glm"), None);
assert_eq!(error, AgentErrorKind::RetryableAgentQuirk);
let error = AgentErrorKind::classify_with_agent(1, "glm failed", Some("ccs"), Some("glm"));
assert_eq!(error, AgentErrorKind::AgentSpecificQuirk);
let error = AgentErrorKind::classify_with_agent(1, "permission denied", Some("ccs/glm"), None);
assert_eq!(error, AgentErrorKind::ToolExecutionFailed);
let error = AgentErrorKind::classify_with_agent(1, "ccs glm failed", Some("ccs/glm"), None);
assert_eq!(error, AgentErrorKind::AgentSpecificQuirk);
}
#[test]
fn test_choose_best_profile_guess_exact_match() {
let suggestions = vec!["work".to_string(), "personal".to_string()];
let result = choose_best_profile_guess("work", &suggestions);
assert_eq!(result, Some("work"));
}
#[test]
fn test_choose_best_profile_guess_case_insensitive() {
let suggestions = vec!["Work".to_string(), "Personal".to_string()];
let result = choose_best_profile_guess("work", &suggestions);
assert_eq!(result, Some("Work"));
}
#[test]
fn test_choose_best_profile_guess_single_suggestion() {
let suggestions = vec!["only-option".to_string()];
let result = choose_best_profile_guess("typo", &suggestions);
assert_eq!(result, Some("only-option"));
}
#[test]
fn test_choose_best_profile_guess_prefix_match() {
let suggestions = vec!["work-main".to_string(), "personal".to_string()];
let result = choose_best_profile_guess("work", &suggestions);
assert_eq!(result, Some("work-main"));
}
#[test]
fn test_choose_best_profile_guess_no_match_returns_first() {
let suggestions = vec!["first".to_string(), "second".to_string()];
let result = choose_best_profile_guess("nomatch", &suggestions);
assert_eq!(result, Some("first"));
}
#[test]
fn test_choose_best_profile_guess_empty_suggestions() {
let suggestions: Vec<String> = vec![];
let result = choose_best_profile_guess("work", &suggestions);
assert_eq!(result, None);
}