claude-utils 0.2.0

Cross-platform companion toolkit for Anthropic's Claude Code CLI
Documentation
use claude_utils::{ClaudeUtilsError, Result};
use std::io;

#[test]
fn test_error_conversions() {
    // Test IO error conversion
    let io_error = io::Error::new(io::ErrorKind::NotFound, "File not found");
    let utils_error: ClaudeUtilsError = io_error.into();
    assert!(matches!(utils_error, ClaudeUtilsError::FileOperation(_)));
    
    // Test image error conversion
    let img_error = image::ImageError::Unsupported(
        image::error::UnsupportedError::from_format_and_kind(
            image::error::ImageFormatHint::Unknown,
            image::error::UnsupportedErrorKind::GenericFeature("test".to_string()),
        )
    );
    let utils_error: ClaudeUtilsError = img_error.into();
    assert!(matches!(utils_error, ClaudeUtilsError::ImageProcessing(_)));
    
    // Test serialization error conversion
    let json_error = serde_json::from_str::<String>("invalid json").unwrap_err();
    let utils_error: ClaudeUtilsError = json_error.into();
    assert!(matches!(utils_error, ClaudeUtilsError::Serialization(_)));
}

#[test]
fn test_custom_errors() {
    let clipboard_error = ClaudeUtilsError::Clipboard("Test clipboard error".to_string());
    assert_eq!(clipboard_error.to_string(), "Clipboard error: Test clipboard error");
    
    let auth_error = ClaudeUtilsError::Authentication("Invalid token".to_string());
    assert_eq!(auth_error.to_string(), "Authentication error: Invalid token");
    
    let mcp_error = ClaudeUtilsError::McpProtocol("Protocol violation".to_string());
    assert_eq!(mcp_error.to_string(), "MCP protocol error: Protocol violation");
    
    let server_error = ClaudeUtilsError::Server("Port in use".to_string());
    assert_eq!(server_error.to_string(), "Server error: Port in use");
    
    let turbo_error = ClaudeUtilsError::Turbo("Rollback failed".to_string());
    assert_eq!(turbo_error.to_string(), "Turbo mode error: Rollback failed");
}

#[test]
fn test_result_type() {
    fn returns_result() -> Result<String> {
        Ok("Success".to_string())
    }
    
    fn returns_error() -> Result<String> {
        Err(ClaudeUtilsError::Clipboard("Failed".to_string()))
    }
    
    assert!(returns_result().is_ok());
    assert!(returns_error().is_err());
}

#[tokio::test]
async fn test_file_operation_errors() {
    let result: Result<()> = async {
        // Try to read non-existent file
        tokio::fs::read("/non/existent/path/file.txt").await?;
        Ok(())
    }.await;
    
    assert!(result.is_err());
    match result.unwrap_err() {
        ClaudeUtilsError::FileOperation(e) => {
            assert_eq!(e.kind(), io::ErrorKind::NotFound);
        }
        _ => panic!("Expected FileOperation error"),
    }
}

#[test]
fn test_error_chain() {
    // Create a chain of errors
    let io_error = io::Error::new(io::ErrorKind::PermissionDenied, "Access denied");
    let utils_error: ClaudeUtilsError = io_error.into();
    
    // Check that error message is preserved
    assert!(utils_error.to_string().contains("Access denied") || 
            utils_error.to_string().contains("permission denied"));
}

#[test]
fn test_authentication_error_scenarios() {
    let scenarios = vec![
        ("", "Empty token"),
        ("invalid-token", "Invalid format"),
        ("expired-token", "Token expired"),
    ];
    
    for (token, reason) in scenarios {
        let error = ClaudeUtilsError::Authentication(format!("{reason}: {token}"));
        assert!(error.to_string().contains(reason));
    }
}

#[test]
fn test_mcp_protocol_errors() {
    let errors = vec![
        ClaudeUtilsError::McpProtocol("Invalid JSON-RPC version".to_string()),
        ClaudeUtilsError::McpProtocol("Method not found".to_string()),
        ClaudeUtilsError::McpProtocol("Invalid parameters".to_string()),
    ];
    
    for error in errors {
        assert!(error.to_string().starts_with("MCP protocol error:"));
    }
}

#[tokio::test]
async fn test_concurrent_error_handling() {
    use std::sync::Arc;
    use tokio::sync::Mutex;
    
    let error_count = Arc::new(Mutex::new(0));
    let handles: Vec<_> = (0..10)
        .map(|i| {
            let count = error_count.clone();
            tokio::spawn(async move {
                let result: Result<()> = if i % 2 == 0 {
                    Ok(())
                } else {
                    Err(ClaudeUtilsError::Server(format!("Error {i}")))
                };
                
                if result.is_err() {
                    let mut c = count.lock().await;
                    *c += 1;
                }
            })
        })
        .collect();
    
    for handle in handles {
        handle.await.unwrap();
    }
    
    assert_eq!(*error_count.lock().await, 5);
}

#[test] 
fn test_error_display_formatting() {
    let error = ClaudeUtilsError::Turbo("Operation failed: unable to create checkpoint".to_string());
    let formatted = format!("{error}");
    assert_eq!(formatted, "Turbo mode error: Operation failed: unable to create checkpoint");
    
    // Test Debug formatting
    let debug_formatted = format!("{error:?}");
    assert!(debug_formatted.contains("Turbo"));
    assert!(debug_formatted.contains("Operation failed"));
}