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_topic_simple_name_accepted()
{
let base = TempDir::new().unwrap();
let out = common::clg_cmd()
.arg( ".session.dir" )
.arg( format!( "path::{}", base.path().display() ) )
.arg( "topic::work" )
.output()
.unwrap();
assert_exit( &out, 0 );
let output = stdout( &out ).trim().to_owned();
assert!(
output.ends_with( "/-work" ),
"EC-1: topic::work must produce path ending in '/-work'; got: {output}"
);
}
#[ test ]
fn ec_2_topic_empty_rejected()
{
let base = TempDir::new().unwrap();
let out = common::clg_cmd()
.arg( ".session.dir" )
.arg( format!( "path::{}", base.path().display() ) )
.arg( "topic::" )
.output()
.unwrap();
assert_exit( &out, 1 );
let combined = format!( "{}{}", stderr( &out ), stdout( &out ) );
assert!(
combined.contains( "topic" ) || combined.contains( "empty" ),
"EC-2: error must mention topic or empty; got: {combined}"
);
}
#[ test ]
fn ec_3_topic_slash_rejected()
{
let base = TempDir::new().unwrap();
let out = common::clg_cmd()
.arg( ".session.dir" )
.arg( format!( "path::{}", base.path().display() ) )
.arg( "topic::sub/dir" )
.output()
.unwrap();
assert_exit( &out, 1 );
let combined = format!( "{}{}", stderr( &out ), stdout( &out ) );
assert!(
combined.contains( "topic" ) || combined.contains( '/' ) || combined.contains( "slash" ) || combined.contains( "separator" ),
"EC-3: error must mention topic or slash; got: {combined}"
);
}
#[ test ]
fn ec_4_topic_backslash_accepted_on_unix()
{
let base = TempDir::new().unwrap();
let out = common::clg_cmd()
.arg( ".session.dir" )
.arg( format!( "path::{}", base.path().display() ) )
.arg( "topic::sub\\dir" )
.output()
.unwrap();
let code = out.status.code().unwrap_or( -1 );
assert!(
code == 0 || code == 1,
"EC-4: backslash in topic must not cause a crash (exit > 1); got exit {code}"
);
}
#[ test ]
fn ec_5_topic_default_in_session_dir()
{
let base = TempDir::new().unwrap();
let out = common::clg_cmd()
.arg( ".session.dir" )
.arg( format!( "path::{}", base.path().display() ) )
.output()
.unwrap();
assert_exit( &out, 0 );
let output = stdout( &out ).trim().to_owned();
assert!(
output.ends_with( "/-default_topic" ),
"EC-5: absent topic must default to '/-default_topic'; got: {output}"
);
}
#[ test ]
fn ec_6_topic_default_in_session_ensure()
{
let home = TempDir::new().unwrap();
let base = TempDir::new().unwrap();
let out = common::clg_cmd()
.env( "HOME", home.path() )
.arg( ".session.ensure" )
.arg( format!( "path::{}", base.path().display() ) )
.output()
.unwrap();
assert_exit( &out, 0 );
let output = stdout( &out );
let first_line = output.lines().next().unwrap_or( "" );
assert!(
first_line.ends_with( "/-default_topic" ),
"EC-6: absent topic in .session.ensure must default to '/-default_topic'; got: {first_line}"
);
}
#[ test ]
fn ec_7_topic_absent_in_project_path_no_suffix()
{
let base = TempDir::new().unwrap();
let out = common::clg_cmd()
.arg( ".project.path" )
.arg( format!( "path::{}", base.path().display() ) )
.output()
.unwrap();
assert_exit( &out, 0 );
let output = stdout( &out ).trim().to_owned();
assert!(
!output.ends_with( "/-default_topic" ),
"EC-7: absent topic in .project.path must produce no topic suffix; got: {output}"
);
}
#[ test ]
fn ec_8_topic_absent_in_exists_checks_base()
{
let home = TempDir::new().unwrap();
let base = TempDir::new().unwrap();
let storage_root = home.path().join( ".claude" );
common::write_path_project_session( &storage_root, base.path(), "sess", 2 );
let out = common::clg_cmd()
.env( "HOME", home.path() )
.arg( ".project.exists" )
.arg( format!( "path::{}", base.path().display() ) )
.output()
.unwrap();
assert_exit( &out, 0 );
let output = stdout( &out );
assert!(
output.contains( "sessions exist" ),
"EC-8: base path with storage must report 'sessions exist'; got: {output}"
);
}
#[ test ]
fn ec_9_topic_with_hyphen_accepted()
{
let base = TempDir::new().unwrap();
let out = common::clg_cmd()
.arg( ".session.dir" )
.arg( format!( "path::{}", base.path().display() ) )
.arg( "topic::my-topic" )
.output()
.unwrap();
assert_exit( &out, 0 );
let output = stdout( &out ).trim().to_owned();
assert!(
output.ends_with( "/-my-topic" ),
"EC-9: topic::my-topic must produce path ending in '/-my-topic'; got: {output}"
);
}
#[ test ]
fn ec_10_topic_with_underscore_accepted()
{
let base = TempDir::new().unwrap();
let out = common::clg_cmd()
.arg( ".session.dir" )
.arg( format!( "path::{}", base.path().display() ) )
.arg( "topic::default_topic" )
.output()
.unwrap();
assert_exit( &out, 0 );
let output = stdout( &out ).trim().to_owned();
assert!(
output.ends_with( "/-default_topic" ),
"EC-10: topic::default_topic must produce path ending in '/-default_topic'; got: {output}"
);
}