mod common;
use tempfile::TempDir;
fn stdout( out : &std::process::Output ) -> String
{
String::from_utf8_lossy( &out.stdout ).into_owned()
}
fn stderr( out : &std::process::Output ) -> String
{
String::from_utf8_lossy( &out.stderr ).into_owned()
}
fn assert_exit( out : &std::process::Output, code : i32 )
{
assert_eq!(
out.status.code().unwrap_or( -1 ),
code,
"expected exit {code}, got {:?}; stderr: {}",
out.status.code(),
stderr( out )
);
}
#[ test ]
fn ec_1_path_absolute_accepted_in_status()
{
let root = TempDir::new().unwrap();
common::write_test_session( root.path(), "proj-path", "sess", 2 );
let out = common::clg_cmd()
.env( "CLAUDE_STORAGE_ROOT", root.path() )
.arg( ".status" )
.arg( format!( "path::{}", root.path().display() ) )
.output()
.unwrap();
assert_exit( &out, 0 );
}
#[ test ]
fn ec_2_path_tilde_expanded_in_status()
{
let home = TempDir::new().unwrap();
std::fs::create_dir_all( home.path().join( ".claude" ) ).unwrap();
let out = common::clg_cmd()
.env( "HOME", home.path() )
.arg( ".status" )
.arg( "path::~/.claude/" )
.output()
.unwrap();
assert_exit( &out, 0 );
}
#[ test ]
fn ec_3_path_relative_accepted_in_exists()
{
let root = TempDir::new().unwrap();
common::write_test_session( root.path(), "proj-path3", "sess", 2 );
let out = common::clg_cmd()
.env( "CLAUDE_STORAGE_ROOT", root.path() )
.arg( ".project.exists" )
.arg( "path::subdir/project" )
.output()
.unwrap();
let code = out.status.code().unwrap_or( -1 );
assert!(
code == 0 || code == 1,
"EC-3: relative path must not cause a format-validation error (exit 2+); got exit {code}"
);
}
#[ test ]
fn ec_4_path_empty_rejected()
{
let out = common::clg_cmd()
.arg( ".status" )
.arg( "path::" )
.output()
.unwrap();
assert_exit( &out, 1 );
let combined = format!( "{}{}", stderr( &out ), stdout( &out ) );
assert!(
combined.contains( "path" ),
"EC-4: error must mention 'path'; got: {combined}"
);
}
#[ test ]
fn ec_5_path_list_substring_case_insensitive()
{
let root = TempDir::new().unwrap();
common::write_path_project_session(
root.path(),
&std::path::PathBuf::from( "/home/user/myproject" ),
"sess",
2,
);
let out_upper = common::clg_cmd()
.env( "CLAUDE_STORAGE_ROOT", root.path() )
.arg( ".list" )
.arg( "path::MYPROJECT" )
.output()
.unwrap();
let out_lower = common::clg_cmd()
.env( "CLAUDE_STORAGE_ROOT", root.path() )
.arg( ".list" )
.arg( "path::myproject" )
.output()
.unwrap();
assert_exit( &out_upper, 0 );
assert_exit( &out_lower, 0 );
assert_eq!(
stdout( &out_upper ),
stdout( &out_lower ),
"EC-5: uppercase and lowercase path filter must produce identical results"
);
}
#[ test ]
fn ec_6_path_list_no_match_returns_empty()
{
let root = TempDir::new().unwrap();
common::write_test_session( root.path(), "proj-path6", "sess", 2 );
let out = common::clg_cmd()
.env( "CLAUDE_STORAGE_ROOT", root.path() )
.arg( ".list" )
.arg( "path::zzznomatch999" )
.output()
.unwrap();
assert_exit( &out, 0 );
let output = stdout( &out );
assert!(
!output.contains( "proj-path6" ),
"EC-6: non-matching path filter must return empty list; got: {output}"
);
}
#[ test ]
fn ec_7_path_default_resolves_to_cwd()
{
let home = TempDir::new().unwrap();
let project_dir = TempDir::new().unwrap();
let storage_root = home.path().join( ".claude" );
common::write_path_project_session(
&storage_root,
project_dir.path(),
"sess",
2,
);
let out = common::clg_cmd()
.env( "HOME", home.path() )
.current_dir( project_dir.path() )
.arg( ".project.exists" )
.output()
.unwrap();
assert_exit( &out, 0 );
let output = stdout( &out );
assert!(
output.contains( "sessions exist" ),
"EC-7: cwd with history must report 'sessions exist'; got: {output}"
);
}
#[ test ]
fn ec_8_path_nonexistent_exists_exits_1()
{
let root = TempDir::new().unwrap();
let out = common::clg_cmd()
.env( "CLAUDE_STORAGE_ROOT", root.path() )
.arg( ".project.exists" )
.arg( "path::/tmp/nonexistent-dir-xyzabc-clg-test" )
.output()
.unwrap();
assert_exit( &out, 1 );
let err = stderr( &out );
assert!(
!err.contains( "panic" ) && !err.contains( "thread" ),
"EC-8: nonexistent path must produce graceful not-found, not a panic; got: {err}"
);
}