use sqlmodel_console::{OutputMode, SqlModelConsole};
use std::collections::HashMap;
#[derive(Default, Clone)]
struct MockEnv {
vars: HashMap<&'static str, &'static str>,
}
impl MockEnv {
fn new() -> Self {
Self::default()
}
fn with(key: &'static str, value: &'static str) -> Self {
let mut env = Self::new();
env.set(key, value);
env
}
fn set(&mut self, key: &'static str, value: &'static str) -> &mut Self {
self.vars.insert(key, value);
self
}
fn lookup(&self, key: &str) -> Option<String> {
self.vars.get(key).map(|v| (*v).to_string())
}
fn detect(&self) -> OutputMode {
OutputMode::detect_with_env(|k| self.lookup(k), true)
}
fn is_agent(&self) -> bool {
OutputMode::is_agent_environment_with(|k| self.lookup(k))
}
}
#[test]
fn test_detects_claude_code() {
let env = MockEnv::with("CLAUDE_CODE", "1");
assert!(env.is_agent());
assert_eq!(env.detect(), OutputMode::Plain);
}
#[test]
fn test_detects_codex_cli() {
let env = MockEnv::with("CODEX_CLI", "1");
assert!(env.is_agent());
assert_eq!(env.detect(), OutputMode::Plain);
}
#[test]
fn test_detects_codex_session() {
let env = MockEnv::with("CODEX_SESSION", "session-123");
assert!(env.is_agent());
assert_eq!(env.detect(), OutputMode::Plain);
}
#[test]
fn test_detects_cursor_session() {
let env = MockEnv::with("CURSOR_SESSION", "abc123");
assert!(env.is_agent());
assert_eq!(env.detect(), OutputMode::Plain);
}
#[test]
fn test_detects_cursor_editor() {
let env = MockEnv::with("CURSOR_EDITOR", "1");
assert!(env.is_agent());
assert_eq!(env.detect(), OutputMode::Plain);
}
#[test]
fn test_detects_aider_model() {
let env = MockEnv::with("AIDER_MODEL", "gpt-4");
assert!(env.is_agent());
assert_eq!(env.detect(), OutputMode::Plain);
}
#[test]
fn test_detects_aider_repo() {
let env = MockEnv::with("AIDER_REPO", "/path/to/repo");
assert!(env.is_agent());
assert_eq!(env.detect(), OutputMode::Plain);
}
#[test]
fn test_detects_agent_mode() {
let env = MockEnv::with("AGENT_MODE", "1");
assert!(env.is_agent());
assert_eq!(env.detect(), OutputMode::Plain);
}
#[test]
fn test_detects_ai_agent() {
let env = MockEnv::with("AI_AGENT", "1");
assert!(env.is_agent());
assert_eq!(env.detect(), OutputMode::Plain);
}
#[test]
fn test_detects_github_copilot() {
let env = MockEnv::with("GITHUB_COPILOT", "1");
assert!(env.is_agent());
assert_eq!(env.detect(), OutputMode::Plain);
}
#[test]
fn test_detects_copilot_session() {
let env = MockEnv::with("COPILOT_SESSION", "sess-456");
assert!(env.is_agent());
assert_eq!(env.detect(), OutputMode::Plain);
}
#[test]
fn test_detects_continue_session() {
let env = MockEnv::with("CONTINUE_SESSION", "cont-789");
assert!(env.is_agent());
assert_eq!(env.detect(), OutputMode::Plain);
}
#[test]
fn test_detects_cody_agent() {
let env = MockEnv::with("CODY_AGENT", "1");
assert!(env.is_agent());
assert_eq!(env.detect(), OutputMode::Plain);
}
#[test]
fn test_detects_cody_session() {
let env = MockEnv::with("CODY_SESSION", "cody-abc");
assert!(env.is_agent());
assert_eq!(env.detect(), OutputMode::Plain);
}
#[test]
fn test_detects_windsurf_session() {
let env = MockEnv::with("WINDSURF_SESSION", "ws-123");
assert!(env.is_agent());
assert_eq!(env.detect(), OutputMode::Plain);
}
#[test]
fn test_detects_codeium_agent() {
let env = MockEnv::with("CODEIUM_AGENT", "1");
assert!(env.is_agent());
assert_eq!(env.detect(), OutputMode::Plain);
}
#[test]
fn test_detects_gemini_cli() {
let env = MockEnv::with("GEMINI_CLI", "1");
assert!(env.is_agent());
assert_eq!(env.detect(), OutputMode::Plain);
}
#[test]
fn test_detects_gemini_session() {
let env = MockEnv::with("GEMINI_SESSION", "gem-xyz");
assert!(env.is_agent());
assert_eq!(env.detect(), OutputMode::Plain);
}
#[test]
fn test_detects_codewhisperer() {
let env = MockEnv::with("CODEWHISPERER_SESSION", "cw-123");
assert!(env.is_agent());
assert_eq!(env.detect(), OutputMode::Plain);
}
#[test]
fn test_detects_amazon_q() {
let env = MockEnv::with("AMAZON_Q_SESSION", "q-456");
assert!(env.is_agent());
assert_eq!(env.detect(), OutputMode::Plain);
}
#[test]
fn test_no_agent_when_clean() {
let env = MockEnv::new();
assert!(!env.is_agent());
}
#[test]
fn test_force_rich_in_agent_environment() {
let mut env = MockEnv::new();
env.set("CLAUDE_CODE", "1");
env.set("SQLMODEL_RICH", "1");
assert_eq!(env.detect(), OutputMode::Rich);
}
#[test]
fn test_plain_override_with_agent() {
let mut env = MockEnv::new();
env.set("CLAUDE_CODE", "1");
env.set("SQLMODEL_PLAIN", "1");
assert_eq!(env.detect(), OutputMode::Plain);
}
#[test]
fn test_plain_beats_rich_override() {
let mut env = MockEnv::new();
env.set("SQLMODEL_PLAIN", "1");
env.set("SQLMODEL_RICH", "1");
assert_eq!(env.detect(), OutputMode::Plain);
}
#[test]
fn test_plain_beats_json_override() {
let mut env = MockEnv::new();
env.set("SQLMODEL_PLAIN", "1");
env.set("SQLMODEL_JSON", "1");
assert_eq!(env.detect(), OutputMode::Plain);
}
#[test]
fn test_json_beats_rich_override() {
let mut env = MockEnv::new();
env.set("SQLMODEL_JSON", "1");
env.set("SQLMODEL_RICH", "1");
assert_eq!(env.detect(), OutputMode::Json);
}
#[test]
fn test_no_color_causes_plain() {
let mut env = MockEnv::new();
env.set("NO_COLOR", "");
assert_eq!(env.detect(), OutputMode::Plain);
}
#[test]
fn test_ci_causes_plain() {
let mut env = MockEnv::new();
env.set("CI", "true");
assert_eq!(env.detect(), OutputMode::Plain);
}
#[test]
fn test_dumb_terminal_causes_plain() {
let mut env = MockEnv::new();
env.set("TERM", "dumb");
assert_eq!(env.detect(), OutputMode::Plain);
}
#[test]
fn test_multiple_agents_detected() {
let mut env = MockEnv::new();
env.set("CLAUDE_CODE", "1");
env.set("CODEX_CLI", "1");
env.set("CURSOR_SESSION", "test");
assert!(env.is_agent());
assert_eq!(env.detect(), OutputMode::Plain);
}
#[test]
fn test_plain_mode_console_no_ansi() {
let console = SqlModelConsole::with_mode(OutputMode::Plain);
assert!(console.is_plain());
assert!(!console.mode().supports_ansi());
}
#[test]
fn test_json_mode_no_ansi() {
let console = SqlModelConsole::with_mode(OutputMode::Json);
assert!(console.is_json());
assert!(!console.mode().supports_ansi());
}
#[test]
fn test_only_rich_supports_ansi() {
assert!(!OutputMode::Plain.supports_ansi());
assert!(OutputMode::Rich.supports_ansi());
assert!(!OutputMode::Json.supports_ansi());
}
#[test]
fn test_strip_markup_removes_style_tags() {
use sqlmodel_console::console::strip_markup;
assert_eq!(strip_markup("[bold]text[/]"), "text");
assert_eq!(strip_markup("[red]error[/]"), "error");
assert_eq!(strip_markup("[green]success[/]"), "success");
assert_eq!(strip_markup("[bold red]warning[/]"), "warning");
assert_eq!(strip_markup("[red on white]highlighted[/]"), "highlighted");
assert_eq!(strip_markup("[bold][italic]nested[/][/]"), "nested");
assert_eq!(strip_markup("[red]a[/] [blue]b[/]"), "a b");
}
#[test]
fn test_strip_markup_preserves_array_indices() {
use sqlmodel_console::console::strip_markup;
assert_eq!(strip_markup("array[0]"), "array[0]");
assert_eq!(strip_markup("array[123]"), "array[123]");
assert_eq!(strip_markup("items[i]"), "items[i]");
assert_eq!(strip_markup("matrix[n]"), "matrix[n]");
assert_eq!(strip_markup("data[x1]"), "data[x1]");
assert_eq!(strip_markup("arr[i2]"), "arr[i2]");
assert_eq!(strip_markup("get_item(arr[0])"), "get_item(arr[0])");
}
#[test]
fn test_plain_output_no_escape_sequences() {
let ansi_patterns = [
"\x1b[", "\x1b]", "\x1bP", "\x1b\\", "\u{009b}", ];
let console = SqlModelConsole::with_mode(OutputMode::Plain);
assert!(console.is_plain());
assert!(!console.mode().supports_ansi());
for pattern in ansi_patterns {
let mode_str = OutputMode::Plain.as_str();
assert!(
!mode_str.contains(pattern),
"Mode string should not contain ANSI: {mode_str}"
);
}
}
#[test]
fn test_plain_mode_strings_parseable() {
let console = SqlModelConsole::with_mode(OutputMode::Plain);
assert_eq!(console.mode().as_str(), "plain");
assert!(console.mode().as_str().is_ascii());
}
#[test]
fn test_json_mode_produces_valid_json() {
#[derive(serde::Serialize)]
struct TestData {
name: String,
count: i32,
active: bool,
}
let data = TestData {
name: "test".to_string(),
count: 42,
active: true,
};
let json = serde_json::to_string(&data).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
assert_eq!(parsed["name"], "test");
assert_eq!(parsed["count"], 42);
assert_eq!(parsed["active"], true);
}
#[test]
fn test_json_mode_string() {
assert_eq!(OutputMode::Json.as_str(), "json");
assert!(OutputMode::Json.is_structured());
}
#[test]
fn test_mode_display() {
assert_eq!(format!("{}", OutputMode::Plain), "plain");
assert_eq!(format!("{}", OutputMode::Rich), "rich");
assert_eq!(format!("{}", OutputMode::Json), "json");
}
#[test]
fn test_console_auto_detection() {
let mut env = MockEnv::new();
env.set("CLAUDE_CODE", "1");
let console = SqlModelConsole::with_env(|k| env.lookup(k), true);
assert!(console.is_plain());
}
#[test]
fn test_console_explicit_mode() {
let console = SqlModelConsole::with_mode(OutputMode::Rich);
assert!(console.is_rich());
let console = SqlModelConsole::with_mode(OutputMode::Plain);
assert!(console.is_plain());
let console = SqlModelConsole::with_mode(OutputMode::Json);
assert!(console.is_json());
}
#[test]
fn test_console_set_mode() {
let mut console = SqlModelConsole::with_mode(OutputMode::Rich);
assert!(console.is_rich());
console.set_mode(OutputMode::Plain);
assert!(console.is_plain());
console.set_mode(OutputMode::Json);
assert!(console.is_json());
}
#[test]
fn test_truthy_values() {
for truthy in ["1", "true", "TRUE", "True", "yes", "YES", "on", "ON"] {
let env = MockEnv::with("SQLMODEL_PLAIN", truthy);
assert_eq!(
env.detect(),
OutputMode::Plain,
"Failed for truthy value: {truthy}"
);
}
}
#[test]
fn test_falsy_values() {
for falsy in ["0", "false", "FALSE", "no", "NO", "off", "OFF", ""] {
let mut env = MockEnv::new();
env.set("SQLMODEL_PLAIN", falsy);
env.set("SQLMODEL_RICH", "1");
let mode = env.detect();
assert_eq!(
mode,
OutputMode::Rich,
"SQLMODEL_PLAIN={falsy} should not trigger plain mode"
);
}
}
#[test]
fn test_agent_marker_presence_not_value() {
let env = MockEnv::with("CLAUDE_CODE", "");
assert!(env.is_agent());
}
#[test]
fn test_output_mode_default() {
assert_eq!(OutputMode::default(), OutputMode::Rich);
}
#[test]
fn test_mode_predicates() {
assert!(OutputMode::Plain.is_plain());
assert!(!OutputMode::Rich.is_plain());
assert!(!OutputMode::Json.is_plain());
assert!(!OutputMode::Plain.is_rich());
assert!(OutputMode::Rich.is_rich());
assert!(!OutputMode::Json.is_rich());
assert!(!OutputMode::Plain.is_structured());
assert!(!OutputMode::Rich.is_structured());
assert!(OutputMode::Json.is_structured());
}
#[test]
fn test_console_default_equals_new() {
let c1 = SqlModelConsole::default();
let c2 = SqlModelConsole::new();
assert_eq!(c1.mode(), c2.mode());
assert_eq!(c1.get_plain_width(), c2.get_plain_width());
}
#[test]
fn test_documented_agent_support() {
struct AgentInfo {
name: &'static str,
env_var: &'static str,
example_value: &'static str,
}
let agents = [
AgentInfo {
name: "Claude Code",
env_var: "CLAUDE_CODE",
example_value: "1",
},
AgentInfo {
name: "OpenAI Codex CLI",
env_var: "CODEX_CLI",
example_value: "1",
},
AgentInfo {
name: "Cursor IDE",
env_var: "CURSOR_SESSION",
example_value: "session-id",
},
AgentInfo {
name: "Aider",
env_var: "AIDER_MODEL",
example_value: "gpt-4",
},
AgentInfo {
name: "GitHub Copilot",
env_var: "GITHUB_COPILOT",
example_value: "1",
},
AgentInfo {
name: "Continue.dev",
env_var: "CONTINUE_SESSION",
example_value: "sess-123",
},
AgentInfo {
name: "Sourcegraph Cody",
env_var: "CODY_AGENT",
example_value: "1",
},
AgentInfo {
name: "Windsurf/Codeium",
env_var: "WINDSURF_SESSION",
example_value: "ws-123",
},
AgentInfo {
name: "Google Gemini CLI",
env_var: "GEMINI_CLI",
example_value: "1",
},
AgentInfo {
name: "Amazon CodeWhisperer",
env_var: "CODEWHISPERER_SESSION",
example_value: "cw-123",
},
AgentInfo {
name: "Amazon Q",
env_var: "AMAZON_Q_SESSION",
example_value: "q-456",
},
];
for agent in agents {
let env = MockEnv::with(agent.env_var, agent.example_value);
assert!(
env.is_agent(),
"{} should be detected via {} env var",
agent.name,
agent.env_var
);
assert_eq!(
env.detect(),
OutputMode::Plain,
"{} should trigger plain mode",
agent.name
);
}
}