#[cfg(test)]
mod integration_tests {
use crate::character::card::{CharacterCard, CharacterData};
use crate::character::import::{import_card, ImportError};
use crate::character::test_helpers::helpers::{
create_temp_card_file, create_temp_cards_dir, create_test_character,
};
use crate::core::app::conversation::ConversationController;
use crate::core::app::session::load_character_for_session;
use crate::core::config::data::Config;
use crate::utils::test_utils::{create_test_app, TestEnvVarGuard};
use std::fs;
use std::io::Write;
use tempfile::NamedTempFile;
#[test]
fn test_import_then_select_via_cli_workflow() {
let (_temp_dir, cards_dir) = create_temp_cards_dir();
let character = create_test_character("TestCLI", "Hello from CLI!");
let temp_card = create_temp_card_file(&character);
let dest_path = cards_dir.join("testcli.json");
fs::copy(temp_card.path(), &dest_path).unwrap();
let config = Config::default();
let mut service = crate::character::CharacterService::new();
let result = load_character_for_session(
Some(dest_path.to_str().unwrap()),
"openai",
"gpt-4",
&config,
&mut service,
);
assert!(result.is_ok());
let outcome = result.unwrap();
assert!(outcome.errors.is_empty());
let card = outcome.character.unwrap();
assert_eq!(card.data.name, "TestCLI");
assert_eq!(card.data.first_mes, "Hello from CLI!");
}
#[test]
fn test_import_then_select_via_picker_workflow() {
let (_temp_dir, cards_dir) = create_temp_cards_dir();
let char1 = create_test_character("PickerChar1", "Hello 1!");
let char2 = create_test_character("PickerChar2", "Hello 2!");
let card1_json = serde_json::to_string(&char1).unwrap();
let card2_json = serde_json::to_string(&char2).unwrap();
fs::write(cards_dir.join("picker1.json"), card1_json).unwrap();
fs::write(cards_dir.join("picker2.json"), card2_json).unwrap();
assert!(cards_dir.join("picker1.json").exists());
assert!(cards_dir.join("picker2.json").exists());
let config = Config::default();
let mut service = crate::character::CharacterService::new();
let result = load_character_for_session(
Some(cards_dir.join("picker1.json").to_str().unwrap()),
"openai",
"gpt-4",
&config,
&mut service,
);
assert!(result.is_ok());
let outcome = result.unwrap();
assert!(outcome.errors.is_empty());
assert_eq!(outcome.character.unwrap().data.name, "PickerChar1");
}
#[test]
fn test_import_set_default_start_session_workflow() {
let (_temp_dir, cards_dir) = create_temp_cards_dir();
let character = create_test_character("DefaultChar", "Hello by default!");
let card_json = serde_json::to_string(&character).unwrap();
let card_path = cards_dir.join("defaultchar.json");
fs::write(&card_path, card_json).unwrap();
let mut config = Config::default();
config.set_default_character(
"openai".to_string(),
"gpt-4".to_string(),
"defaultchar".to_string(),
);
assert_eq!(
config.get_default_character("openai", "gpt-4"),
Some(&"defaultchar".to_string())
);
let mut env_guard = TestEnvVarGuard::new();
env_guard.set_var("CHABEAU_CARDS_DIR", &cards_dir);
let mut service = crate::character::CharacterService::new();
let result = load_character_for_session(None, "openai", "gpt-4", &config, &mut service);
let outcome = result.expect("default load result");
assert!(outcome.errors.is_empty());
assert_eq!(outcome.character.unwrap().data.name, "DefaultChar");
}
#[test]
fn test_character_switching_during_session() {
let mut app = create_test_app();
let char_a = create_test_character("CharA", "Hello from A!");
app.session.set_character(char_a.clone());
assert_eq!(app.session.get_character().unwrap().data.name, "CharA");
assert!(!app.session.character_greeting_shown);
{
let mut conversation = ConversationController::new(
&mut app.session,
&mut app.ui,
&app.persona_manager,
&app.preset_manager,
);
conversation.show_character_greeting_if_needed();
}
assert_eq!(app.ui.messages.len(), 1);
assert_eq!(app.ui.messages.front().unwrap().content, "Hello from A!");
assert!(app.session.character_greeting_shown);
let char_b = create_test_character("CharB", "Hello from B!");
app.session.set_character(char_b.clone());
assert_eq!(app.session.get_character().unwrap().data.name, "CharB");
assert!(!app.session.character_greeting_shown);
{
let mut conversation = ConversationController::new(
&mut app.session,
&mut app.ui,
&app.persona_manager,
&app.preset_manager,
);
conversation.show_character_greeting_if_needed();
}
assert_eq!(app.ui.messages.len(), 2);
assert_eq!(app.ui.messages.back().unwrap().content, "Hello from B!");
assert!(app.session.character_greeting_shown);
}
#[test]
fn test_error_case_missing_card_file() {
let config = Config::default();
let mut service = crate::character::CharacterService::new();
let result = load_character_for_session(
Some("/nonexistent/path/to/card.json"),
"openai",
"gpt-4",
&config,
&mut service,
);
assert!(result.is_err());
let err = result.unwrap_err();
let err_str = err.to_string();
assert!(
err_str.contains("File not found")
|| err_str.contains("No such file")
|| err_str.contains("not found")
|| err_str.contains("Failed to load"),
"Unexpected error message: {}",
err_str
);
}
#[test]
fn test_error_case_invalid_card_format() {
let mut temp_file = NamedTempFile::with_suffix(".json").unwrap();
temp_file.write_all(b"{ invalid json }").unwrap();
temp_file.flush().unwrap();
let config = Config::default();
let mut service = crate::character::CharacterService::new();
let result = load_character_for_session(
Some(temp_file.path().to_str().unwrap()),
"openai",
"gpt-4",
&config,
&mut service,
);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.to_string().contains("Invalid JSON") || err.to_string().contains("expected"));
}
#[test]
fn test_error_case_missing_required_fields() {
let invalid_card = serde_json::json!({
"spec": "chara_card_v2",
"spec_version": "2.0",
"data": {
"name": "", "description": "Test",
"personality": "Test",
"scenario": "Test",
"first_mes": "Test",
"mes_example": "Test"
}
});
let mut temp_file = NamedTempFile::with_suffix(".json").unwrap();
temp_file
.write_all(invalid_card.to_string().as_bytes())
.unwrap();
temp_file.flush().unwrap();
let config = Config::default();
let mut service = crate::character::CharacterService::new();
let result = load_character_for_session(
Some(temp_file.path().to_str().unwrap()),
"openai",
"gpt-4",
&config,
&mut service,
);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.to_string().contains("name") || err.to_string().contains("validation"));
}
#[test]
fn test_error_case_wrong_spec_version() {
let wrong_spec = serde_json::json!({
"spec": "wrong_spec",
"spec_version": "1.0",
"data": {
"name": "Test",
"description": "Test",
"personality": "Test",
"scenario": "Test",
"first_mes": "Test",
"mes_example": "Test"
}
});
let mut temp_file = NamedTempFile::with_suffix(".json").unwrap();
temp_file
.write_all(wrong_spec.to_string().as_bytes())
.unwrap();
temp_file.flush().unwrap();
let config = Config::default();
let mut service = crate::character::CharacterService::new();
let result = load_character_for_session(
Some(temp_file.path().to_str().unwrap()),
"openai",
"gpt-4",
&config,
&mut service,
);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.to_string().contains("spec") || err.to_string().contains("validation"));
}
#[test]
fn test_full_conversation_flow_with_character() {
let mut app = create_test_app();
let character = create_test_character("ConvoBot", "Hello! How can I help?");
app.session.set_character(character);
{
let mut conversation = ConversationController::new(
&mut app.session,
&mut app.ui,
&app.persona_manager,
&app.preset_manager,
);
conversation.show_character_greeting_if_needed();
}
assert_eq!(app.ui.messages.len(), 1);
assert_eq!(
app.ui.messages.front().unwrap().content,
"Hello! How can I help?"
);
let api_messages = {
let mut conversation = ConversationController::new(
&mut app.session,
&mut app.ui,
&app.persona_manager,
&app.preset_manager,
);
conversation.add_user_message("What's the weather?".to_string())
};
assert!(api_messages.len() >= 3);
assert_eq!(api_messages[0].role, "system");
assert!(api_messages[0].content.contains("You are ConvoBot"));
assert_eq!(api_messages[1].role, "assistant");
assert_eq!(api_messages[1].content, "Hello! How can I help?");
assert_eq!(api_messages[2].role, "user");
assert_eq!(api_messages[2].content, "What's the weather?");
assert_eq!(api_messages.last().unwrap().role, "system");
assert_eq!(api_messages.last().unwrap().content, "Always be polite.");
}
#[test]
fn test_config_persistence_with_multiple_defaults() {
let (temp_dir, _cards_dir) = create_temp_cards_dir();
let config_path = temp_dir.path().join("test_config.toml");
let mut config = Config::default();
config.set_default_character(
"openai".to_string(),
"gpt-4".to_string(),
"alice".to_string(),
);
config.set_default_character(
"openai".to_string(),
"gpt-4o".to_string(),
"bob".to_string(),
);
config.set_default_character(
"anthropic".to_string(),
"claude-3-opus".to_string(),
"charlie".to_string(),
);
let toml_str = toml::to_string(&config).unwrap();
fs::write(&config_path, toml_str).unwrap();
let loaded_toml = fs::read_to_string(&config_path).unwrap();
let loaded_config: Config = toml::from_str(&loaded_toml).unwrap();
assert_eq!(
loaded_config.get_default_character("openai", "gpt-4"),
Some(&"alice".to_string())
);
assert_eq!(
loaded_config.get_default_character("openai", "gpt-4o"),
Some(&"bob".to_string())
);
assert_eq!(
loaded_config.get_default_character("anthropic", "claude-3-opus"),
Some(&"charlie".to_string())
);
}
#[test]
fn test_character_precedence_cli_over_default() {
let (_temp_dir, cards_dir) = create_temp_cards_dir();
let default_char = create_test_character("DefaultChar", "I'm the default");
let cli_char = create_test_character("CLIChar", "I'm from CLI");
let default_json = serde_json::to_string(&default_char).unwrap();
let cli_json = serde_json::to_string(&cli_char).unwrap();
let default_path = cards_dir.join("default.json");
let cli_path = cards_dir.join("cli.json");
fs::write(&default_path, default_json).unwrap();
fs::write(&cli_path, cli_json).unwrap();
let mut config = Config::default();
config.set_default_character(
"openai".to_string(),
"gpt-4".to_string(),
"default".to_string(),
);
let mut service = crate::character::CharacterService::new();
let result = load_character_for_session(
Some(cli_path.to_str().unwrap()),
"openai",
"gpt-4",
&config,
&mut service,
);
assert!(result.is_ok());
let outcome = result.unwrap();
assert!(outcome.errors.is_empty());
assert_eq!(outcome.character.unwrap().data.name, "CLIChar");
}
#[test]
fn test_character_with_empty_optional_fields() {
let mut app = create_test_app();
let minimal_char = CharacterCard {
spec: "chara_card_v2".to_string(),
spec_version: "2.0".to_string(),
data: CharacterData {
name: "MinimalChar".to_string(),
description: "Minimal".to_string(),
personality: "Simple".to_string(),
scenario: "Test".to_string(),
first_mes: "Hi".to_string(),
mes_example: "".to_string(),
creator_notes: None,
system_prompt: None,
post_history_instructions: None,
alternate_greetings: None,
tags: None,
creator: None,
character_version: None,
},
};
app.session.set_character(minimal_char);
let api_messages = {
let mut conversation = ConversationController::new(
&mut app.session,
&mut app.ui,
&app.persona_manager,
&app.preset_manager,
);
conversation.add_user_message("Test".to_string())
};
assert_eq!(api_messages.len(), 2);
assert_eq!(api_messages[0].role, "system");
assert_eq!(api_messages[1].role, "user");
}
#[test]
fn test_import_overwrite_protection() {
let (temp_dir, cards_dir) = create_temp_cards_dir();
let char1 = create_test_character("OverwriteTest", "Version 1");
let card1_json = serde_json::to_string(&char1).unwrap();
let card_path = cards_dir.join("overwrite.json");
fs::write(&card_path, card1_json).unwrap();
let char2 = create_test_character("OverwriteTest", "Version 2");
let temp_card_path = temp_dir.path().join("overwrite.json");
fs::write(&temp_card_path, serde_json::to_string(&char2).unwrap()).unwrap();
let result = {
let mut env_guard = TestEnvVarGuard::new();
env_guard.set_var("CHABEAU_CARDS_DIR", cards_dir.as_os_str());
let result = import_card(&temp_card_path, false);
drop(env_guard);
result
};
assert!(matches!(result, Err(ImportError::AlreadyExists(_))));
let content = fs::read_to_string(&card_path).unwrap();
assert!(content.contains("Version 1"));
}
}