mod common;
#[test]
fn test_search_query_required()
{
let output = common::clg_cmd()
.args( [ ".search" ] )
.current_dir( env!( "CARGO_MANIFEST_DIR" ) )
.output()
.expect( "Failed to execute command" );
let stderr = String::from_utf8_lossy( &output.stderr );
let stdout = String::from_utf8_lossy( &output.stdout );
let combined = format!( "{stderr}{stdout}" );
assert!(
!output.status.success(),
"Should fail when query missing. Got: {combined}"
);
assert!(
combined.to_lowercase().contains( "query" ) &&
combined.to_lowercase().contains( "required" ),
"Error should mention query is required. Got: {combined}"
);
}
#[test]
fn test_search_query_empty()
{
let output = common::clg_cmd()
.args( [ ".search", "query::" ] )
.current_dir( env!( "CARGO_MANIFEST_DIR" ) )
.output()
.expect( "Failed to execute command" );
let stderr = String::from_utf8_lossy( &output.stderr );
let stdout = String::from_utf8_lossy( &output.stdout );
let combined = format!( "{stderr}{stdout}" );
assert!(
!output.status.success(),
"Should fail when query empty. Got: {combined}"
);
assert!(
combined.to_lowercase().contains( "query" ) &&
( combined.to_lowercase().contains( "empty" ) ||
combined.to_lowercase().contains( "expected value" ) ),
"Error should mention query validation. Got: {combined}"
);
}
#[test]
fn test_search_case_sensitive_invalid()
{
let output = common::clg_cmd()
.args( [ ".search", "query::test", "case_sensitive::2" ] )
.current_dir( env!( "CARGO_MANIFEST_DIR" ) )
.output()
.expect( "Failed to execute command" );
let stderr = String::from_utf8_lossy( &output.stderr );
let stdout = String::from_utf8_lossy( &output.stdout );
let combined = format!( "{stderr}{stdout}" );
assert!(
!output.status.success(),
"Should fail with invalid case_sensitive value. Got: {combined}"
);
assert!(
combined.to_lowercase().contains( "case" ) ||
combined.to_lowercase().contains( "invalid" ),
"Error should mention case_sensitive or invalid. Got: {combined}"
);
}
#[test]
fn test_search_entry_type_invalid()
{
let output = common::clg_cmd()
.args( [ ".search", "query::test", "entry_type::invalid" ] )
.current_dir( env!( "CARGO_MANIFEST_DIR" ) )
.output()
.expect( "Failed to execute command" );
let stderr = String::from_utf8_lossy( &output.stderr );
let stdout = String::from_utf8_lossy( &output.stdout );
let combined = format!( "{stderr}{stdout}" );
assert!(
!output.status.success(),
"Should fail with invalid entry_type. Got: {combined}"
);
assert!(
combined.to_lowercase().contains( "entry" ) ||
combined.to_lowercase().contains( "type" ) ||
combined.to_lowercase().contains( "invalid" ),
"Error should mention entry_type or invalid. Got: {combined}"
);
}
#[test]
fn test_search_entry_type_valid()
{
use tempfile::TempDir;
let storage = TempDir::new().unwrap();
common::write_test_session( storage.path(), "search-proj-et", "sess-et-001", 4 );
for entry_type in [ "user", "assistant", "all" ]
{
let output = common::clg_cmd()
.args( [
".search",
"query::entry",
&format!( "entry_type::{entry_type}" ),
"project::search-proj-et",
] )
.env( "CLAUDE_STORAGE_ROOT", storage.path() )
.output()
.expect( "Failed to execute .search" );
let stderr = String::from_utf8_lossy( &output.stderr );
assert!(
output.status.success(),
"Search with entry_type::{entry_type} should succeed. stderr: {stderr}"
);
let has_validation_error =
stderr.to_lowercase().contains( "entry" ) &&
stderr.to_lowercase().contains( "type" ) &&
stderr.to_lowercase().contains( "invalid" );
assert!(
!has_validation_error,
"Should not fail on entry_type validation for '{entry_type}'. stderr: {stderr}"
);
}
}
#[test]
fn test_search_entry_type_all_is_valid_bug_reproducer_issue_021()
{
use tempfile::TempDir;
let storage = TempDir::new().unwrap();
common::write_test_session( storage.path(), "search-proj-all", "sess-all-001", 2 );
let output = common::clg_cmd()
.args( [ ".search", "query::entry", "entry_type::all", "project::search-proj-all" ] )
.env( "CLAUDE_STORAGE_ROOT", storage.path() )
.output()
.expect( "Failed to execute .search" );
let stderr = String::from_utf8_lossy( &output.stderr );
let stdout = String::from_utf8_lossy( &output.stdout );
let combined = format!( "{stderr}{stdout}" );
assert!(
output.status.success(),
"entry_type::all must be accepted as valid per YAML spec. Got: {combined}"
);
assert!(
!combined.contains( "Invalid entry_type" ),
"entry_type::all must not produce invalid-value error. Got: {combined}"
);
}
#[test]
fn test_search_verbosity_invalid()
{
let output = common::clg_cmd()
.args( [ ".search", "query::test", "verbosity::-1" ] )
.current_dir( env!( "CARGO_MANIFEST_DIR" ) )
.output()
.expect( "Failed to execute command" );
let stderr = String::from_utf8_lossy( &output.stderr );
let stdout = String::from_utf8_lossy( &output.stdout );
let combined = format!( "{stderr}{stdout}" );
assert!(
!output.status.success(),
"Should fail with verbosity::-1. Got: {combined}"
);
let output = common::clg_cmd()
.args( [ ".search", "query::test", "verbosity::10" ] )
.current_dir( env!( "CARGO_MANIFEST_DIR" ) )
.output()
.expect( "Failed to execute command" );
let stderr = String::from_utf8_lossy( &output.stderr );
let stdout = String::from_utf8_lossy( &output.stdout );
let combined = format!( "{stderr}{stdout}" );
assert!(
!output.status.success(),
"Should fail with verbosity::10. Got: {combined}"
);
}
#[test]
fn test_search_project_nonexistent()
{
use tempfile::TempDir;
let storage = TempDir::new().unwrap();
let output = common::clg_cmd()
.args( [ ".search", "query::test", "project::nonexistent-uuid-12345" ] )
.env( "CLAUDE_STORAGE_ROOT", storage.path() )
.output()
.expect( "Failed to execute .search" );
let stderr = String::from_utf8_lossy( &output.stderr );
let stdout = String::from_utf8_lossy( &output.stdout );
let combined = format!( "{stderr}{stdout}" );
assert!(
!output.status.success(),
"Should fail with nonexistent project. Got: {combined}"
);
assert!(
combined.to_lowercase().contains( "project" ) &&
( combined.to_lowercase().contains( "not found" ) ||
combined.to_lowercase().contains( "does not exist" ) ||
combined.to_lowercase().contains( "no project" ) ),
"Error should mention project not found. Got: {combined}"
);
}
#[test]
fn test_search_session_nonexistent()
{
use tempfile::TempDir;
let storage = TempDir::new().unwrap();
common::write_test_session( storage.path(), "search-proj-sne", "real-session-9999", 2 );
let output = common::clg_cmd()
.args( [
".search",
"query::test",
"session::nonexistent-session-id-xyz",
"project::search-proj-sne",
] )
.env( "CLAUDE_STORAGE_ROOT", storage.path() )
.output()
.expect( "Failed to execute .search" );
let stderr = String::from_utf8_lossy( &output.stderr );
let stdout = String::from_utf8_lossy( &output.stdout );
let combined = format!( "{stderr}{stdout}" );
assert!(
!output.status.success(),
"Should fail with nonexistent session. Got: {combined}"
);
assert!(
combined.to_lowercase().contains( "session" ) &&
( combined.to_lowercase().contains( "not found" ) ||
combined.to_lowercase().contains( "does not exist" ) ||
combined.to_lowercase().contains( "no session" ) ),
"Error should mention session not found. Got: {combined}"
);
}
#[test]
fn test_search_singular_noun_one_match()
{
use tempfile::TempDir;
let storage = TempDir::new().unwrap();
common::write_test_session( storage.path(), "search-proj-sing", "sess-sing-001", 2 );
let output = common::clg_cmd()
.args( [ ".search", "query::entry 0", "project::search-proj-sing", "verbosity::1" ] )
.env( "CLAUDE_STORAGE_ROOT", storage.path() )
.output()
.expect( "Failed to execute .search" );
let stdout = String::from_utf8_lossy( &output.stdout );
let stderr = String::from_utf8_lossy( &output.stderr );
assert!(
output.status.success(),
".search must succeed; stderr: {stderr}"
);
assert!(
stdout.contains( "Found 1 match:" ),
"with 1 match, header must use singular 'match' (not 'matches'); got:\n{stdout}"
);
assert!(
!stdout.contains( "Found 1 matches:" ),
"with 1 match, header must NOT use plural 'matches'; got:\n{stdout}"
);
}
#[test]
fn test_search_plural_noun_multiple_matches()
{
use tempfile::TempDir;
let storage = TempDir::new().unwrap();
common::write_test_session( storage.path(), "search-proj-plur", "sess-plur-001", 4 );
let output = common::clg_cmd()
.args( [ ".search", "query::entry", "project::search-proj-plur", "verbosity::1" ] )
.env( "CLAUDE_STORAGE_ROOT", storage.path() )
.output()
.expect( "Failed to execute .search" );
let stdout = String::from_utf8_lossy( &output.stdout );
let stderr = String::from_utf8_lossy( &output.stderr );
assert!(
output.status.success(),
".search must succeed; stderr: {stderr}"
);
assert!(
!stdout.contains( "Found 1 match:" ),
"with multiple matches, must not show singular 'match'; got:\n{stdout}"
);
if let Some( line ) = stdout.lines().find( | l | l.starts_with( "Found" ) )
{
assert!(
line.contains( "matches:" ),
"multi-match header must use plural 'matches'; got: {line}"
);
}
}