#[cfg(test)]
mod integration_tests {
use crate::commands::{process_input, CommandResult};
use crate::core::app::conversation::ConversationController;
use crate::core::config::data::{Config, Persona};
use crate::core::persona::PersonaManager;
use crate::utils::test_utils::create_test_app;
use std::fs;
use tempfile::TempDir;
fn create_test_personas() -> Vec<Persona> {
vec![
Persona {
id: "alice-dev".to_string(),
display_name: "Alice".to_string(),
bio: Some("You are talking to Alice, a senior software developer with 10 years of experience in {{char}} development.".to_string()),
},
Persona {
id: "bob-student".to_string(),
display_name: "Bob".to_string(),
bio: Some("You are talking to {{user}}, a computer science student learning about AI.".to_string()),
},
Persona {
id: "charlie-no-bio".to_string(),
display_name: "Charlie".to_string(),
bio: None,
},
]
}
fn create_test_config_with_personas() -> Config {
Config {
personas: create_test_personas(),
..Default::default()
}
}
#[test]
fn test_cli_persona_command_updates_ui_and_status() {
let mut app = create_test_app();
let config = create_test_config_with_personas();
app.persona_manager =
PersonaManager::load_personas(&config).expect("Failed to load personas");
assert_eq!(app.ui.user_display_name, "You");
assert!(app.ui.status.is_none());
let result = process_input(&mut app, "/persona alice-dev");
assert!(matches!(result, CommandResult::Continue));
assert_eq!(app.ui.user_display_name, "Alice");
assert_eq!(app.ui.status.as_deref(), Some("Persona activated: Alice"));
assert_eq!(
app.persona_manager
.get_active_persona()
.expect("Persona should be active")
.id,
"alice-dev"
);
let result = process_input(&mut app, "/persona nonexistent-persona");
assert!(matches!(result, CommandResult::Continue));
assert_eq!(app.ui.user_display_name, "Alice");
assert_eq!(
app.persona_manager
.get_active_persona()
.expect("Persona should remain active")
.id,
"alice-dev"
);
assert!(app
.ui
.status
.as_deref()
.unwrap_or_default()
.starts_with("Persona error: Persona 'nonexistent-persona' not found"));
}
#[test]
fn test_interactive_persona_command_workflow() {
let mut app = create_test_app();
let config = create_test_config_with_personas();
app.persona_manager =
PersonaManager::load_personas(&config).expect("Failed to load personas");
let result = process_input(&mut app, "/persona");
assert!(
matches!(result, CommandResult::OpenPersonaPicker),
"Persona command should open picker"
);
let result = process_input(&mut app, "/persona alice-dev");
assert!(
matches!(result, CommandResult::Continue),
"Direct persona activation should continue"
);
let active_persona = app.persona_manager.get_active_persona();
assert!(
active_persona.is_some(),
"Persona should be active after direct command"
);
assert_eq!(active_persona.unwrap().id, "alice-dev");
let result = process_input(&mut app, "/persona nonexistent");
assert!(
matches!(result, CommandResult::Continue),
"Invalid persona should continue with error"
);
let active_persona = app.persona_manager.get_active_persona();
assert!(active_persona.is_some());
assert_eq!(active_persona.unwrap().id, "alice-dev");
}
#[test]
fn test_persona_picker_with_active_persona() {
let mut app = create_test_app();
let config = create_test_config_with_personas();
app.persona_manager =
PersonaManager::load_personas(&config).expect("Failed to load personas");
app.persona_manager
.set_active_persona("alice-dev")
.expect("Failed to activate persona");
let result = process_input(&mut app, "/persona");
assert!(matches!(result, CommandResult::OpenPersonaPicker));
let result = process_input(&mut app, "/persona bob-student");
assert!(matches!(result, CommandResult::Continue));
let active_persona = app.persona_manager.get_active_persona();
assert!(active_persona.is_some());
assert_eq!(active_persona.unwrap().id, "bob-student");
assert_eq!(active_persona.unwrap().display_name, "Bob");
}
#[test]
fn test_persona_picker_turn_off_updates_ui_state() {
let mut app = create_test_app();
let config = create_test_config_with_personas();
app.persona_manager =
PersonaManager::load_personas(&config).expect("Failed to load personas");
app.persona_manager
.set_active_persona("alice-dev")
.expect("Failed to activate persona");
let active_display_name = app.persona_manager.get_display_name();
app.ui.update_user_display_name(active_display_name.clone());
assert_eq!(app.ui.user_display_name, "Alice");
app.open_persona_picker();
{
let picker_state = app.picker_state().expect("Persona picker should be opened");
assert!(
picker_state
.items
.iter()
.any(|item| item.id == "[turn_off_persona]"),
"Turn off persona entry should be present in picker"
);
}
{
let picker_state = app
.picker_state_mut()
.expect("Persona picker state should be mutable");
let turn_off_index = picker_state
.items
.iter()
.position(|item| item.id == "[turn_off_persona]")
.expect("Turn off persona entry missing");
picker_state.selected = turn_off_index;
}
app.apply_selected_persona(false);
assert!(app.persona_manager.get_active_persona().is_none());
assert_eq!(app.ui.user_display_name, "You");
assert_eq!(app.ui.status.as_deref(), Some("Persona deactivated"));
assert!(app.picker_state().is_none());
}
#[test]
fn test_system_prompt_modification_with_active_persona() {
let config = create_test_config_with_personas();
let mut persona_manager =
PersonaManager::load_personas(&config).expect("Failed to load personas");
let base_prompt = "You are a helpful assistant.";
let prompt_no_persona = persona_manager.get_modified_system_prompt(base_prompt, None);
assert_eq!(
prompt_no_persona, base_prompt,
"Prompt should be unchanged without persona"
);
persona_manager
.set_active_persona("alice-dev")
.expect("Failed to activate persona");
let prompt_with_persona = persona_manager.get_modified_system_prompt(base_prompt, None);
assert!(prompt_with_persona.contains("Alice, a senior software developer"));
assert!(prompt_with_persona.contains(base_prompt));
assert!(
prompt_with_persona.len() > base_prompt.len(),
"Modified prompt should be longer"
);
persona_manager
.set_active_persona("charlie-no-bio")
.expect("Failed to activate persona");
let prompt_no_bio = persona_manager.get_modified_system_prompt(base_prompt, None);
assert_eq!(
prompt_no_bio, base_prompt,
"Prompt should be unchanged for persona without bio"
);
}
#[test]
fn test_persona_substitution_in_conversation() {
let config = create_test_config_with_personas();
let mut persona_manager =
PersonaManager::load_personas(&config).expect("Failed to load personas");
let text_with_placeholders = "Hello {{user}}, I am {{char}}!";
let result_no_persona =
persona_manager.apply_substitutions(text_with_placeholders, Some("TestBot"));
assert_eq!(result_no_persona, "Hello Anon, I am TestBot!");
persona_manager
.set_active_persona("alice-dev")
.expect("Failed to activate persona");
let result_with_persona =
persona_manager.apply_substitutions(text_with_placeholders, Some("TestBot"));
assert_eq!(result_with_persona, "Hello Alice, I am TestBot!");
let active_persona = persona_manager.get_active_persona().unwrap();
let bio_with_substitution = active_persona.bio.as_ref().unwrap();
let substituted_bio =
persona_manager.apply_substitutions(bio_with_substitution, Some("TestBot"));
assert!(substituted_bio.contains("Alice, a senior software developer"));
assert!(substituted_bio.contains("TestBot development"));
}
#[test]
fn test_default_persona_loading_from_config() {
let mut config = create_test_config_with_personas();
config.set_default_persona(
"openai".to_string(),
"gpt-4".to_string(),
"alice-dev".to_string(),
);
let persona_manager =
PersonaManager::load_personas(&config).expect("Failed to load personas");
let default = persona_manager.get_default_for_provider_model("openai", "gpt-4");
assert!(default.is_some());
assert_eq!(default.unwrap(), "alice-dev");
}
#[test]
fn test_cli_persona_overrides_default() {
let mut config = create_test_config_with_personas();
config.set_default_persona(
"openai".to_string(),
"gpt-4".to_string(),
"bob-student".to_string(),
);
let mut persona_manager =
PersonaManager::load_personas(&config).expect("Failed to load personas");
assert_eq!(
persona_manager
.get_default_for_provider_model("openai", "gpt-4")
.unwrap(),
"bob-student"
);
persona_manager
.set_active_persona("alice-dev")
.expect("Failed to set CLI persona");
let active = persona_manager.get_active_persona().unwrap();
assert_eq!(active.id, "alice-dev");
assert_ne!(active.id, "bob-student");
}
#[test]
fn test_persona_display_name_in_conversation() {
let mut app = create_test_app();
let config = create_test_config_with_personas();
app.persona_manager =
PersonaManager::load_personas(&config).expect("Failed to load personas");
assert_eq!(app.persona_manager.get_display_name(), "You");
app.persona_manager
.set_active_persona("alice-dev")
.expect("Failed to activate persona");
assert_eq!(app.persona_manager.get_display_name(), "Alice");
{
let mut conversation = ConversationController::new(
&mut app.session,
&mut app.ui,
&app.persona_manager,
&app.preset_manager,
);
conversation.add_user_message("Hello!".to_string());
}
assert!(!app.ui.messages.is_empty());
}
#[test]
fn test_persona_config_persistence() {
let temp_dir = TempDir::new().unwrap();
let config_path = temp_dir.path().join("test_personas.toml");
let mut config = create_test_config_with_personas();
config.set_default_persona(
"openai".to_string(),
"gpt-4".to_string(),
"alice-dev".to_string(),
);
config.set_default_persona(
"anthropic".to_string(),
"claude-3-opus".to_string(),
"bob-student".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.personas.len(), 3);
assert!(loaded_config.personas.iter().any(|p| p.id == "alice-dev"));
assert!(loaded_config.personas.iter().any(|p| p.id == "bob-student"));
assert!(loaded_config
.personas
.iter()
.any(|p| p.id == "charlie-no-bio"));
let persona_manager =
PersonaManager::load_personas(&loaded_config).expect("Failed to load personas");
assert_eq!(
persona_manager
.get_default_for_provider_model("openai", "gpt-4")
.unwrap(),
"alice-dev"
);
assert_eq!(
persona_manager
.get_default_for_provider_model("anthropic", "claude-3-opus")
.unwrap(),
"bob-student"
);
}
#[test]
fn test_end_to_end_persona_workflow() {
let mut app = create_test_app();
let config = create_test_config_with_personas();
app.persona_manager =
PersonaManager::load_personas(&config).expect("Failed to load personas");
app.persona_manager
.set_active_persona("alice-dev")
.expect("Failed to set CLI persona");
assert_eq!(app.persona_manager.get_display_name(), "Alice");
let initial_prompt = app
.persona_manager
.get_modified_system_prompt("You are helpful.", None);
assert!(initial_prompt.contains("Alice, a senior software developer"));
{
let mut conversation = ConversationController::new(
&mut app.session,
&mut app.ui,
&app.persona_manager,
&app.preset_manager,
);
conversation.add_user_message("What's your experience?".to_string());
}
app.persona_manager
.set_active_persona("bob-student")
.expect("Failed to switch persona");
assert_eq!(app.persona_manager.get_display_name(), "Bob");
let switched_prompt = app
.persona_manager
.get_modified_system_prompt("You are helpful.", None);
assert!(switched_prompt.contains("Bob, a computer science student"));
assert!(!switched_prompt.contains("Alice"));
{
let mut conversation = ConversationController::new(
&mut app.session,
&mut app.ui,
&app.persona_manager,
&app.preset_manager,
);
conversation.add_user_message("I'm learning AI.".to_string());
}
app.persona_manager.clear_active_persona();
assert_eq!(app.persona_manager.get_display_name(), "You");
let final_prompt = app
.persona_manager
.get_modified_system_prompt("You are helpful.", None);
assert_eq!(final_prompt, "You are helpful.");
}
#[test]
fn test_persona_error_recovery() {
let mut app = create_test_app();
let empty_config = Config::default();
app.persona_manager =
PersonaManager::load_personas(&empty_config).expect("Failed to load empty personas");
assert!(app.persona_manager.list_personas().is_empty());
assert!(app.persona_manager.get_active_persona().is_none());
assert_eq!(app.persona_manager.get_display_name(), "You");
let result = process_input(&mut app, "/persona");
assert!(matches!(result, CommandResult::OpenPersonaPicker));
let result = app.persona_manager.set_active_persona("nonexistent");
assert!(result.is_err());
assert!(app.persona_manager.get_active_persona().is_none());
let config = create_test_config_with_personas();
app.persona_manager =
PersonaManager::load_personas(&config).expect("Failed to load personas");
assert!(app.persona_manager.set_active_persona("alice-dev").is_ok());
assert!(app.persona_manager.get_active_persona().is_some());
}
#[test]
fn test_persona_command_variations() {
let mut app = create_test_app();
let config = create_test_config_with_personas();
app.persona_manager =
PersonaManager::load_personas(&config).expect("Failed to load personas");
let result = process_input(&mut app, "/persona");
assert!(matches!(result, CommandResult::OpenPersonaPicker));
let result = process_input(&mut app, "/persona alice-dev");
assert!(matches!(result, CommandResult::Continue));
assert_eq!(
app.persona_manager.get_active_persona().unwrap().id,
"alice-dev"
);
let result = process_input(&mut app, "/persona bob-student");
assert!(matches!(result, CommandResult::Continue));
assert_eq!(
app.persona_manager.get_active_persona().unwrap().id,
"bob-student"
);
let result = process_input(&mut app, "/persona charlie-no-bio");
assert!(matches!(result, CommandResult::Continue));
assert_eq!(
app.persona_manager.get_active_persona().unwrap().id,
"charlie-no-bio"
);
let result = process_input(&mut app, "/persona invalid-persona");
assert!(matches!(result, CommandResult::Continue));
assert_eq!(
app.persona_manager.get_active_persona().unwrap().id,
"charlie-no-bio"
);
}
}