#![ cfg( unix ) ]
use claude_storage_core::{ Storage, ExportFormat, export_session };
use std::io::Cursor;
#[test]
fn export_markdown_basic()
{
let storage = Storage::with_root( "/tmp/claude_tests_empty" );
let projects = storage.list_projects().expect( "Failed to list projects" );
if projects.is_empty()
{
println!( "Skipping test: no projects found" );
return;
}
let project = projects.into_iter().next().unwrap();
let mut sessions = project.sessions().expect( "Failed to get sessions" );
if sessions.is_empty()
{
println!( "Skipping test: no sessions found" );
return;
}
let mut found_valid = false;
for session in &mut sessions
{
let mut output = Cursor::new( Vec::new() );
if export_session( session, ExportFormat::Markdown, &mut output ).is_err()
{
continue; }
let result = String::from_utf8( output.into_inner() ).expect( "Invalid UTF-8" );
if result.contains( "# Session:" ) && result.contains( "## Entry" )
{
found_valid = true;
assert!( result.contains( "**Path**:" ), "Missing path" );
assert!( result.contains( "**Entries**:" ), "Missing entry count" );
assert!( result.contains( "---" ), "Missing separator" );
break;
}
}
if !found_valid
{
println!( "Skipping test: no valid sessions found for markdown export" );
}
}
#[test]
fn export_json_basic()
{
let storage = Storage::with_root( "/tmp/claude_tests_empty" );
let projects = storage.list_projects().expect( "Failed to list projects" );
if projects.is_empty()
{
println!( "Skipping test: no projects found" );
return;
}
let project = projects.into_iter().next().unwrap();
let mut sessions = project.sessions().expect( "Failed to get sessions" );
if sessions.is_empty()
{
println!( "Skipping test: no sessions found" );
return;
}
let session = sessions.first_mut().unwrap();
let mut output = Cursor::new( Vec::new() );
export_session( session, ExportFormat::Json, &mut output )
.expect( "Failed to export session" );
let result = String::from_utf8( output.into_inner() ).expect( "Invalid UTF-8" );
assert!( result.contains( "\"session_id\":" ), "Missing session_id" );
assert!( result.contains( "\"storage_path\":" ), "Missing storage_path" );
assert!( result.contains( "\"entries\":" ), "Missing entries array" );
assert!( result.starts_with( '{' ), "Should start with open brace" );
assert!( result.trim().ends_with( '}' ), "Should end with close brace" );
}
#[test]
fn export_text_basic()
{
let storage = Storage::with_root( "/tmp/claude_tests_empty" );
let projects = storage.list_projects().expect( "Failed to list projects" );
if projects.is_empty()
{
println!( "Skipping test: no projects found" );
return;
}
let project = projects.into_iter().next().unwrap();
let mut sessions = project.sessions().expect( "Failed to get sessions" );
if sessions.is_empty()
{
println!( "Skipping test: no sessions found" );
return;
}
let mut found_valid = false;
for session in &mut sessions
{
let mut output = Cursor::new( Vec::new() );
if export_session( session, ExportFormat::Text, &mut output ).is_err()
{
continue; }
let result = String::from_utf8( output.into_inner() ).expect( "Invalid UTF-8" );
if result.contains( "Session:" ) && ( result.contains( "[User]" ) || result.contains( "[Assistant]" ) )
{
found_valid = true;
assert!( result.contains( "Path:" ), "Missing path" );
assert!( result.contains( "Entries:" ), "Missing entry count" );
assert!( result.contains( "---" ), "Missing separator" );
break;
}
}
if !found_valid
{
println!( "Skipping test: no valid sessions found for text export" );
}
}
#[test]
fn export_format_from_str()
{
assert_eq!
(
ExportFormat::from_str( "markdown" ).unwrap(),
ExportFormat::Markdown
);
assert_eq!
(
ExportFormat::from_str( "md" ).unwrap(),
ExportFormat::Markdown
);
assert_eq!
(
ExportFormat::from_str( "json" ).unwrap(),
ExportFormat::Json
);
assert_eq!
(
ExportFormat::from_str( "text" ).unwrap(),
ExportFormat::Text
);
assert_eq!
(
ExportFormat::from_str( "txt" ).unwrap(),
ExportFormat::Text
);
assert_eq!
(
ExportFormat::from_str( "MARKDOWN" ).unwrap(),
ExportFormat::Markdown
);
assert!( ExportFormat::from_str( "invalid" ).is_err() );
}
#[test]
fn export_format_extension()
{
assert_eq!( ExportFormat::Markdown.extension(), "md" );
assert_eq!( ExportFormat::Json.extension(), "json" );
assert_eq!( ExportFormat::Text.extension(), "txt" );
}
#[test]
fn export_to_file()
{
let storage = Storage::with_root( "/tmp/claude_tests_empty" );
let projects = storage.list_projects().expect( "Failed to list projects" );
if projects.is_empty()
{
println!( "Skipping test: no projects found" );
return;
}
let project = projects.into_iter().next().unwrap();
let mut sessions = project.sessions().expect( "Failed to get sessions" );
if sessions.is_empty()
{
println!( "Skipping test: no sessions found" );
return;
}
let session = sessions.first_mut().unwrap();
let output_path = std::path::PathBuf::from( "/tmp/test_export.md" );
claude_storage_core::export_session_to_file
(
session,
ExportFormat::Markdown,
&output_path
).expect( "Failed to export to file" );
assert!( output_path.exists(), "Output file not created" );
let content = std::fs::read_to_string( &output_path )
.expect( "Failed to read output file" );
assert!( !content.is_empty(), "Output file is empty" );
assert!( content.contains( "# Session:" ), "Missing session header" );
}
#[test]
fn export_markdown_with_thinking()
{
let storage = Storage::with_root( "/tmp/claude_tests_empty" );
let projects = storage.list_projects().expect( "Failed to list projects" );
if projects.is_empty()
{
println!( "Skipping test: no projects found" );
return;
}
let project = projects.into_iter().next().unwrap();
let mut sessions = project.sessions().expect( "Failed to get sessions" );
if sessions.is_empty()
{
println!( "Skipping test: no sessions found" );
return;
}
let mut found_thinking = false;
for session in &mut sessions
{
let mut output = Cursor::new( Vec::new() );
if export_session( session, ExportFormat::Markdown, &mut output ).is_err()
{
continue;
}
let result = String::from_utf8( output.into_inner() ).expect( "Invalid UTF-8" );
if result.contains( "<details>" ) && result.contains( "Thinking" )
{
found_thinking = true;
assert!( result.contains( "<summary>Thinking" ), "Missing thinking summary" );
assert!( result.contains( "</details>" ), "Missing details close tag" );
break;
}
}
if !found_thinking
{
println!( "No sessions with thinking blocks found (this is ok)" );
}
}
#[test]
fn export_with_metadata_entries()
{
use std::io::Cursor;
let storage = Storage::with_root( "/tmp/claude_tests_empty" );
let projects = storage.list_projects().expect( "Failed to list projects" );
if projects.is_empty()
{
println!( "Skipping test: no projects found" );
return;
}
let mut found_session_with_metadata = false;
for project in projects
{
let Ok( mut sessions ) = project.sessions() else { continue };
for session in &mut sessions
{
let path = session.storage_path();
let Ok( content ) = std::fs::read_to_string( path ) else { continue };
let has_metadata = content.lines().any( | line |
{
line.contains( r#""type":"queue-operation"# )
|| line.contains( r#""type":"summary"# )
|| line.contains( r#""type":"file-history-snapshot"# )
});
if !has_metadata
{
continue;
}
found_session_with_metadata = true;
let mut output = Cursor::new( Vec::new() );
export_session( session, ExportFormat::Markdown, &mut output )
.expect( "Export failed for session with metadata entries" );
let result = String::from_utf8( output.into_inner() ).expect( "Invalid UTF-8" );
assert!( result.contains( "# Session:" ), "Missing session header" );
assert!( result.contains( "**Path**:" ), "Missing path" );
let sid = session.id();
println!( "Successfully exported session with metadata entries: {sid}" );
break;
}
if found_session_with_metadata
{
break;
}
}
if !found_session_with_metadata
{
println!( "Skipping test: no sessions with metadata entries found" );
println!( "This is expected if testing on a minimal storage instance" );
}
}
#[ test ]
fn export_format_invalid_string_returns_clear_error()
{
let result = ExportFormat::from_str( "xml" );
assert!( result.is_err(), "Unknown format 'xml' must return Err" );
let err_msg = result.unwrap_err().to_string();
assert!(
!err_msg.contains( "I/O error" ),
"Error for invalid format should not say 'I/O error'. Got: {err_msg}"
);
assert!(
err_msg.contains( "xml" ),
"Error must reference the invalid format value. Got: {err_msg}"
);
assert!( ExportFormat::from_str( "markdown" ).is_ok() );
assert!( ExportFormat::from_str( "md" ).is_ok() );
assert!( ExportFormat::from_str( "json" ).is_ok() );
assert!( ExportFormat::from_str( "text" ).is_ok() );
assert!( ExportFormat::from_str( "txt" ).is_ok() );
assert!( ExportFormat::from_str( "Markdown" ).is_ok() );
assert!( ExportFormat::from_str( "JSON" ).is_ok() );
}