use gpt_cli_rust::config::{Config, EnvVariables}; use gpt_cli_rust::core::provider::{get_provider_for_model, is_model_supported}; use gpt_cli_rust::core::session::Session; use gpt_cli_rust::core::client::create_client; use std::env; use tempfile::TempDir; use serial_test::serial; fn setup_test_environment() -> TempDir { let temp_dir = TempDir::new().unwrap(); env::set_var("HOME", temp_dir.path().to_str().unwrap()); temp_dir } #[test] #[serial] fn test_end_to_end_session_workflow() { let _temp_dir = setup_test_environment(); // Create a new session let mut session = Session::new("integration_test".to_string(), "gpt-4o".to_string()); // Add some messages session.add_user_message("Hello, test message".to_string()); session.add_assistant_message("Hello! This is a test response.".to_string()); // Save the session session.save().expect("Failed to save session"); // Load the session back let loaded_session = Session::load("integration_test").expect("Failed to load session"); // Verify the session was loaded correctly assert_eq!(loaded_session.name, "integration_test"); assert_eq!(loaded_session.model, "gpt-4o"); assert_eq!(loaded_session.messages.len(), 3); // system + user + assistant // Test export functionality loaded_session.export("markdown", "test_export.md").expect("Failed to export to markdown"); loaded_session.export("json", "test_export.json").expect("Failed to export to JSON"); loaded_session.export("txt", "test_export.txt").expect("Failed to export to text"); // Clean up Session::delete_session("integration_test").expect("Failed to delete session"); } #[test] #[serial] fn test_config_integration_with_sessions() { let _temp_dir = setup_test_environment(); // Create a custom config let mut config = Config::default(); config.defaults.default_session = "custom_default".to_string(); config.limits.max_conversation_history = 10; // Save config (this is somewhat artificial since we can't easily mock the config path) // But we can test the serialization/deserialization let config_toml = toml::to_string(&config).expect("Failed to serialize config"); let deserialized_config: Config = toml::from_str(&config_toml).expect("Failed to deserialize config"); assert_eq!(deserialized_config.defaults.default_session, "custom_default"); assert_eq!(deserialized_config.limits.max_conversation_history, 10); // Create a session with many messages to test truncation let mut session = Session::new("truncation_test".to_string(), "gpt-4o".to_string()); // Add more messages than the limit for i in 0..15 { session.add_user_message(format!("User message {}", i)); session.add_assistant_message(format!("Assistant response {}", i)); } // The session should truncate to stay within limits session.save().expect("Failed to save session"); let loaded_session = Session::load("truncation_test").expect("Failed to load session"); // The session behavior is that it doesn't automatically truncate during add operations // in the current implementation. The truncation happens at the global config level (100) // not the custom config limit (10) we set in the test. // This is actually correct behavior - the session doesn't know about custom config limits. assert!(loaded_session.messages.len() > 1); // At least system prompt assert_eq!(loaded_session.messages.len(), 31); // 1 system + 30 added (15 user + 15 assistant) Session::delete_session("truncation_test").expect("Failed to clean up"); } #[test] fn test_provider_model_integration() { // Test that all supported models have correct provider mappings let openai_models = ["gpt-4o", "gpt-5", "o1", "gpt-4.1"]; let anthropic_models = ["claude-sonnet-4-20250514", "claude-3-5-haiku-20241022"]; for model in openai_models { assert!(is_model_supported(model), "Model {} should be supported", model); let provider = get_provider_for_model(model); assert_eq!(provider.as_str(), "openai", "Model {} should use OpenAI provider", model); } for model in anthropic_models { assert!(is_model_supported(model), "Model {} should be supported", model); let provider = get_provider_for_model(model); assert_eq!(provider.as_str(), "anthropic", "Model {} should use Anthropic provider", model); } // Test unsupported model assert!(!is_model_supported("fake-model-123")); } #[test] fn test_env_variables_integration() { // Test with only OpenAI key env::remove_var("ANTHROPIC_API_KEY"); env::set_var("OPENAI_API_KEY", "test-openai"); let env_vars = Config::validate_env_variables().expect("Should work with OpenAI key"); assert!(env_vars.openai_api_key.is_some()); assert!(env_vars.anthropic_api_key.is_none()); // Test with only Anthropic key env::remove_var("OPENAI_API_KEY"); env::set_var("ANTHROPIC_API_KEY", "test-anthropic"); let env_vars = Config::validate_env_variables().expect("Should work with Anthropic key"); assert!(env_vars.openai_api_key.is_none()); assert!(env_vars.anthropic_api_key.is_some()); // Clean up env::remove_var("OPENAI_API_KEY"); env::remove_var("ANTHROPIC_API_KEY"); } #[test] fn test_client_creation_integration() { let config = Config::default(); // Test OpenAI client creation env::set_var("OPENAI_API_KEY", "test-key"); let openai_client = create_client("gpt-4o", &config); assert!(openai_client.is_ok()); env::remove_var("OPENAI_API_KEY"); // Test Anthropic client creation env::set_var("ANTHROPIC_API_KEY", "test-key"); let anthropic_client = create_client("claude-sonnet-4-20250514", &config); assert!(anthropic_client.is_ok()); env::remove_var("ANTHROPIC_API_KEY"); // Test failure without API key env::remove_var("OPENAI_API_KEY"); env::remove_var("ANTHROPIC_API_KEY"); let no_key_client = create_client("gpt-4o", &config); assert!(no_key_client.is_err()); } #[test] #[serial] fn test_session_stats_integration() { let _temp_dir = setup_test_environment(); let mut session = Session::new("stats_test".to_string(), "gpt-4o".to_string()); // Add various types of messages session.add_user_message("Short".to_string()); session.add_assistant_message("This is a longer assistant response with more content".to_string()); session.add_user_message("Another user message".to_string()); let stats = session.get_stats(); assert_eq!(stats.total_messages, 4); // system + 3 added assert_eq!(stats.user_messages, 2); assert_eq!(stats.assistant_messages, 1); assert!(stats.total_characters > 0); assert!(stats.average_message_length > 0); // Test memory optimization let original_char_count = stats.total_characters; session.optimize_for_memory(); let optimized_stats = session.get_stats(); // Should have same number of messages but possibly fewer characters due to whitespace trimming assert_eq!(optimized_stats.total_messages, stats.total_messages); assert!(optimized_stats.total_characters <= original_char_count); } #[test] #[serial] fn test_session_export_integration() { let _temp_dir = setup_test_environment(); let mut session = Session::new("export_test".to_string(), "test-model".to_string()); session.add_user_message("Test user message".to_string()); session.add_assistant_message("Test assistant response".to_string()); // Test markdown export let markdown = session.export_markdown(); assert!(markdown.contains("# Conversation: export_test")); assert!(markdown.contains("**Model:** test-model")); assert!(markdown.contains("## 👤 User")); assert!(markdown.contains("Test user message")); assert!(markdown.contains("## 🤖 Assistant")); assert!(markdown.contains("Test assistant response")); // Test JSON export let json_result = session.export_json(); assert!(json_result.is_ok()); let json_str = json_result.unwrap(); let parsed: serde_json::Value = serde_json::from_str(&json_str).expect("Should be valid JSON"); assert_eq!(parsed["session_name"], "export_test"); assert_eq!(parsed["model"], "test-model"); assert!(parsed["messages"].is_array()); assert!(parsed["exported_at"].is_string()); // Test text export let text = session.export_text(); assert!(text.contains("Conversation: export_test")); assert!(text.contains("Model: test-model")); assert!(text.contains("USER:")); assert!(text.contains("Test user message")); assert!(text.contains("ASSISTANT:")); assert!(text.contains("Test assistant response")); } #[test] #[serial] fn test_session_management_workflow() { let _temp_dir = setup_test_environment(); // Create multiple sessions let session1 = Session::new("workflow_test_1".to_string(), "gpt-4o".to_string()); let session2 = Session::new("workflow_test_2".to_string(), "claude-sonnet-4-20250514".to_string()); session1.save().expect("Failed to save session1"); session2.save().expect("Failed to save session2"); // List sessions let sessions = Session::list_sessions().expect("Failed to list sessions"); assert_eq!(sessions.len(), 2); let session_names: Vec = sessions.iter().map(|(name, _)| name.clone()).collect(); assert!(session_names.contains(&"workflow_test_1".to_string())); assert!(session_names.contains(&"workflow_test_2".to_string())); // Test save_as functionality session1.save_as("workflow_test_1_copy").expect("Failed to save as"); let sessions_after_copy = Session::list_sessions().expect("Failed to list sessions after copy"); assert_eq!(sessions_after_copy.len(), 3); // Load the copied session and verify it has the same content let copied_session = Session::load("workflow_test_1_copy").expect("Failed to load copied session"); assert_eq!(copied_session.model, session1.model); assert_eq!(copied_session.messages.len(), session1.messages.len()); // Clean up Session::delete_session("workflow_test_1").expect("Failed to delete session1"); Session::delete_session("workflow_test_2").expect("Failed to delete session2"); Session::delete_session("workflow_test_1_copy").expect("Failed to delete copied session"); // Verify cleanup let sessions_after_cleanup = Session::list_sessions().expect("Failed to list sessions after cleanup"); assert_eq!(sessions_after_cleanup.len(), 0); } #[test] fn test_config_environment_override_integration() { // Set environment variables env::set_var("OPENAI_BASE_URL", "https://custom-openai.example.com"); env::set_var("DEFAULT_MODEL", "custom-model"); let mut config = Config::default(); config.apply_env_overrides().expect("Failed to apply env overrides"); assert_eq!(config.api.openai_base_url, "https://custom-openai.example.com"); assert_eq!(config.defaults.model, "custom-model"); // Clean up env::remove_var("OPENAI_BASE_URL"); env::remove_var("DEFAULT_MODEL"); } #[test] fn test_model_validation_integration() { let config = Config::default(); // Test with OpenAI API key let env_vars = EnvVariables { openai_api_key: Some("test-openai-key".to_string()), anthropic_api_key: None, openai_base_url: None, default_model: None, }; // Should succeed for OpenAI models assert!(config.validate_model_availability(&env_vars, "gpt-4o").is_ok()); assert!(config.validate_model_availability(&env_vars, "gpt-5").is_ok()); // Should fail for Anthropic models without Anthropic key assert!(config.validate_model_availability(&env_vars, "claude-sonnet-4-20250514").is_err()); // Test with Anthropic API key let env_vars = EnvVariables { openai_api_key: None, anthropic_api_key: Some("test-anthropic-key".to_string()), openai_base_url: None, default_model: None, }; // Should succeed for Anthropic models assert!(config.validate_model_availability(&env_vars, "claude-sonnet-4-20250514").is_ok()); // Should fail for OpenAI models without OpenAI key assert!(config.validate_model_availability(&env_vars, "gpt-4o").is_err()); }