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_output_required_missing_exits_1()
{
let root = TempDir::new().unwrap();
common::write_test_session( root.path(), "proj-out", "-default_topic", 2 );
let out = common::clg_cmd()
.env( "CLAUDE_STORAGE_ROOT", root.path() )
.arg( ".export" )
.arg( "session_id::-default_topic" )
.arg( "project::proj-out" )
.output()
.unwrap();
assert_exit( &out, 1 );
let combined = format!( "{}{}", stderr( &out ), stdout( &out ) );
assert!(
combined.contains( "output" ),
"EC-1: error must mention 'output'; got: {combined}"
);
}
#[ test ]
fn ec_2_output_absolute_path_accepted()
{
let root = TempDir::new().unwrap();
let out_dir = TempDir::new().unwrap();
common::write_test_session( root.path(), "proj-out2", "-default_topic", 2 );
let out_path = out_dir.path().join( "absolute.md" );
let out = common::clg_cmd()
.env( "CLAUDE_STORAGE_ROOT", root.path() )
.arg( ".export" )
.arg( "session_id::-default_topic" )
.arg( "project::proj-out2" )
.arg( format!( "output::{}", out_path.display() ) )
.output()
.unwrap();
assert_exit( &out, 0 );
assert!(
out_path.exists(),
"EC-2: file must exist at absolute path; stderr: {}",
stderr( &out )
);
}
#[ test ]
fn ec_3_output_tilde_path_accepted()
{
let root = TempDir::new().unwrap();
let out_dir = TempDir::new().unwrap();
common::write_test_session( root.path(), "proj-out3", "-default_topic", 2 );
let out_path = out_dir.path().join( "clg-test-output.md" );
let out = common::clg_cmd()
.env( "CLAUDE_STORAGE_ROOT", root.path() )
.arg( ".export" )
.arg( "session_id::-default_topic" )
.arg( "project::proj-out3" )
.arg( format!( "output::{}", out_path.display() ) )
.output()
.unwrap();
assert_exit( &out, 0 );
assert!(
out_path.exists(),
"EC-3: file must exist at output path; stderr: {}",
stderr( &out )
);
}
#[ test ]
fn ec_4_output_relative_path_accepted()
{
let root = TempDir::new().unwrap();
let cwd = TempDir::new().unwrap();
common::write_test_session( root.path(), "proj-out4", "-default_topic", 2 );
let out = common::clg_cmd()
.env( "CLAUDE_STORAGE_ROOT", root.path() )
.current_dir( cwd.path() )
.arg( ".export" )
.arg( "session_id::-default_topic" )
.arg( "project::proj-out4" )
.arg( "output::session-output.md" )
.output()
.unwrap();
assert_exit( &out, 0 );
assert!(
cwd.path().join( "session-output.md" ).exists(),
"EC-4: file must exist at relative path; stderr: {}",
stderr( &out )
);
}
#[ test ]
fn ec_5_output_empty_rejected()
{
let out = common::clg_cmd()
.arg( ".export" )
.arg( "session_id::-default_topic" )
.arg( "output::" )
.output()
.unwrap();
assert_exit( &out, 1 );
let combined = format!( "{}{}", stderr( &out ), stdout( &out ) );
assert!(
combined.contains( "output" ) || combined.contains( "path" ) || combined.contains( "empty" ),
"EC-5: error must mention output or empty path; got: {combined}"
);
}
#[ test ]
fn ec_6_output_nonexistent_parent_exits_2()
{
let root = TempDir::new().unwrap();
common::write_test_session( root.path(), "proj-out6", "-default_topic", 2 );
let out = common::clg_cmd()
.env( "CLAUDE_STORAGE_ROOT", root.path() )
.arg( ".export" )
.arg( "session_id::-default_topic" )
.arg( "project::proj-out6" )
.arg( "output::/nonexistent-clg-test-dir/subdir/file.md" )
.output()
.unwrap();
assert_exit( &out, 1 );
}
#[ test ]
fn ec_7_output_overwrites_existing_file()
{
let root = TempDir::new().unwrap();
let out_dir = TempDir::new().unwrap();
common::write_test_session( root.path(), "proj-out7", "-default_topic", 2 );
let out_path = out_dir.path().join( "existing.md" );
std::fs::write( &out_path, b"ORIGINAL SENTINEL CONTENT" ).unwrap();
let out = common::clg_cmd()
.env( "CLAUDE_STORAGE_ROOT", root.path() )
.arg( ".export" )
.arg( "session_id::-default_topic" )
.arg( "project::proj-out7" )
.arg( format!( "output::{}", out_path.display() ) )
.output()
.unwrap();
assert_exit( &out, 0 );
let content = std::fs::read_to_string( &out_path ).unwrap();
assert!(
!content.contains( "ORIGINAL SENTINEL CONTENT" ),
"EC-7: overwrite must replace original content; got: {content}"
);
}
#[ test ]
fn ec_8_output_whitespace_only_rejected()
{
let out = common::clg_cmd()
.arg( ".export" )
.arg( "session_id::-default_topic" )
.arg( "output:: " )
.output()
.unwrap();
assert_exit( &out, 1 );
let _ = stdout( &out );
}