use avocado_core::db::Database;
use avocado_core::index::VectorIndex;
use avocado_core::session::SessionManager;
use avocado_core::types::{CompilerConfig, MessageRole};
use std::path::PathBuf;
use tempfile::TempDir;
use std::sync::Arc;
use avocado_core::{span, Artifact};
use sha2::{Digest, Sha256};
use uuid::Uuid;
struct TestEnv {
_temp_dir: TempDir,
db_path: PathBuf,
db: Database,
session_manager: SessionManager,
index: Arc<VectorIndex>,
}
impl TestEnv {
fn new() -> Self {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let db_path = temp_dir.path().join("test.db");
let db = Database::new(&db_path).expect("Failed to create database");
let session_manager = SessionManager::new(db.clone());
let index = db.get_vector_index().expect("failed to get vector index");
Self {
_temp_dir: temp_dir,
db_path,
db,
session_manager,
index,
}
}
fn default_config() -> CompilerConfig {
CompilerConfig {
token_budget: 8000,
..Default::default()
}
}
}
async fn ingest_text(db: &Database, path: &str, content: &str) {
let artifact_id = Uuid::new_v4().to_string();
let content_hash = format!("{:x}", Sha256::digest(content.as_bytes()));
let artifact = Artifact {
id: artifact_id.clone(),
path: path.to_string(),
content: content.to_string(),
content_hash,
metadata: None,
created_at: chrono::Utc::now(),
};
db.insert_artifact(&artifact).expect("insert_artifact failed");
let mut spans = span::extract_spans(content, &artifact_id).expect("extract_spans failed");
let texts: Vec<&str> = spans.iter().map(|s| s.text.as_str()).collect();
let embeddings = avocado_core::embedding::embed_batch(texts, None, None)
.await
.expect("embed_batch failed");
for (s, emb) in spans.iter_mut().zip(embeddings.iter()) {
s.embedding = Some(emb.clone());
s.embedding_model = Some("minilm".to_string());
}
db.insert_spans(&spans).expect("insert_spans failed");
}
#[tokio::test]
async fn test_full_session_workflow() {
let env = TestEnv::new();
let session = env
.session_manager
.start_session(Some("alice"))
.expect("Failed to create session");
assert_eq!(session.user_id, Some("alice".to_string()));
let test_content = "Rust is a systems programming language focused on safety and performance.";
ingest_text(&env.db, "rust_intro.md", test_content).await;
let (msg1, ws1) = env
.session_manager
.add_user_message(
&session.id,
"What is Rust?",
TestEnv::default_config(),
env.index.as_ref(),
None,
)
.await
.expect("Failed to add user message");
assert_eq!(msg1.role, MessageRole::User);
assert_eq!(msg1.content, "What is Rust?");
assert_eq!(msg1.sequence_number, 0);
let msg2 = env
.session_manager
.add_assistant_message(
&session.id,
"Rust is a systems programming language...",
None,
)
.expect("Failed to add assistant message");
assert_eq!(msg2.role, MessageRole::Assistant);
assert_eq!(msg2.sequence_number, 1);
let (msg3, ws2) = env
.session_manager
.add_user_message(
&session.id,
"Tell me about ownership",
TestEnv::default_config(),
&env.index,
None,
)
.await
.expect("Failed to add second user message");
assert_eq!(msg3.sequence_number, 2);
let history = env
.session_manager
.get_conversation_history(&session.id, None)
.expect("Failed to get history");
assert!(history.contains("What is Rust?"));
assert!(history.contains("Tell me about ownership"));
let replay = env
.session_manager
.replay_session(&session.id)
.expect("Failed to replay session");
assert_eq!(replay.turns.len(), 2); assert!(replay.turns[0].working_set.is_some());
assert!(replay.turns[0].assistant_message.is_some());
assert!(replay.turns[1].working_set.is_some());
}
#[tokio::test]
async fn test_multiple_sessions_isolation() {
let env = TestEnv::new();
let session1 = env
.session_manager
.start_session(Some("alice"))
.expect("Failed to create session 1");
let session2 = env
.session_manager
.start_session(Some("bob"))
.expect("Failed to create session 2");
ingest_text(&env.db, "test.md", "Test content").await;
env.session_manager
.add_user_message(
&session1.id,
"Alice's question",
TestEnv::default_config(),
env.index.as_ref(),
None,
)
.await
.expect("Failed to add to session 1");
env.session_manager
.add_user_message(
&session2.id,
"Bob's question",
TestEnv::default_config(),
env.index.as_ref(),
None,
)
.await
.expect("Failed to add to session 2");
let history1 = env
.session_manager
.get_conversation_history(&session1.id, None)
.expect("Failed to get history 1");
let history2 = env
.session_manager
.get_conversation_history(&session2.id, None)
.expect("Failed to get history 2");
assert!(history1.contains("Alice's question"));
assert!(!history1.contains("Bob's question"));
assert!(history2.contains("Bob's question"));
assert!(!history2.contains("Alice's question"));
}
#[tokio::test]
async fn test_long_conversation_history() {
let env = TestEnv::new();
let session = env
.session_manager
.start_session(Some("test_user"))
.expect("Failed to create session");
ingest_text(&env.db, "test.md", "Test content for context compilation").await;
for i in 0..50 {
env.session_manager
.add_user_message(
&session.id,
&format!("User message {}", i),
TestEnv::default_config(),
env.index.as_ref(),
None,
)
.await
.expect("Failed to add user message");
env.session_manager
.add_assistant_message(
&session.id,
&format!("Assistant response {}", i),
None,
)
.expect("Failed to add assistant message");
}
let messages = env
.db
.get_messages(&session.id, None)
.expect("Failed to get messages");
assert_eq!(messages.len(), 100);
for (idx, msg) in messages.iter().enumerate() {
assert_eq!(msg.sequence_number, idx as usize);
}
let history = env
.session_manager
.get_conversation_history(&session.id, None)
.expect("Failed to get history");
assert!(history.contains("User message 0"));
assert!(history.contains("User message 49"));
assert!(history.contains("Assistant response 0"));
assert!(history.contains("Assistant response 49"));
}
#[tokio::test]
async fn test_token_limited_history() {
let env = TestEnv::new();
let session = env
.session_manager
.start_session(Some("test_user"))
.expect("Failed to create session");
ingest_text(&env.db, "test.md", "Test content").await;
let long_message = "A".repeat(1000);
for i in 0..10 {
env.session_manager
.add_user_message(
&session.id,
&format!("Message {}: {}", i, long_message),
TestEnv::default_config(),
env.index.as_ref(),
None,
)
.await
.expect("Failed to add message");
}
let history = env
.session_manager
.get_conversation_history(&session.id, Some(1000))
.expect("Failed to get limited history");
assert!(history.contains("Message 9"));
assert!(!history.contains("Message 0"));
assert!(!history.contains("Message 1"));
}
#[tokio::test]
async fn test_session_replay_structure() {
let env = TestEnv::new();
let session = env
.session_manager
.start_session(Some("test_user"))
.expect("Failed to create session");
ingest_text(&env.db, "test.md", "Test content").await;
env.session_manager
.add_user_message(
&session.id,
"First question",
TestEnv::default_config(),
env.index.as_ref(),
None,
)
.await
.expect("Failed to add user message");
env.session_manager
.add_assistant_message(&session.id, "First answer", None)
.expect("Failed to add assistant message");
env.session_manager
.add_user_message(
&session.id,
"Second question",
TestEnv::default_config(),
&env.index,
None,
)
.await
.expect("Failed to add user message");
let replay = env
.session_manager
.replay_session(&session.id)
.expect("Failed to replay");
assert_eq!(replay.turns.len(), 2);
assert_eq!(replay.turns[0].user_message.content, "First question");
assert!(replay.turns[0].working_set.is_some());
assert!(replay.turns[0].assistant_message.is_some());
assert_eq!(
replay.turns[0].assistant_message.as_ref().unwrap().content,
"First answer"
);
assert_eq!(replay.turns[1].user_message.content, "Second question");
assert!(replay.turns[1].working_set.is_some());
assert!(replay.turns[1].assistant_message.is_none());
}
#[tokio::test]
async fn test_session_deletion_cascade() {
let env = TestEnv::new();
let session = env
.session_manager
.start_session(Some("test_user"))
.expect("Failed to create session");
ingest_text(&env.db, "test.md", "Test content").await;
env.session_manager
.add_user_message(
&session.id,
"Test question",
TestEnv::default_config(),
env.index.as_ref(),
None,
)
.await
.expect("Failed to add message");
env.session_manager
.add_assistant_message(&session.id, "Test answer", None)
.expect("Failed to add assistant message");
let messages_before = env
.db
.get_messages(&session.id, None)
.expect("Failed to get messages");
assert!(!messages_before.is_empty());
env.db
.delete_session(&session.id)
.expect("Failed to delete session");
let session_data = env.db.get_session_full(&session.id).expect("DB error");
assert!(session_data.is_none());
let messages_after = env.db.get_messages(&session.id, None);
assert!(
messages_after.is_err() || messages_after.unwrap().is_empty(),
"Messages should be deleted with session"
);
}
#[test]
fn test_session_list_filtering() {
let env = TestEnv::new();
env.session_manager
.start_session(Some("alice"))
.expect("Failed to create session");
env.session_manager
.start_session(Some("alice"))
.expect("Failed to create session");
env.session_manager
.start_session(Some("bob"))
.expect("Failed to create session");
let all_sessions = env.db.list_sessions(None, None).expect("Failed to list sessions");
assert_eq!(all_sessions.len(), 3);
let alice_sessions = env.db.list_sessions(Some("alice"), None).expect("Failed to list Alice's sessions");
assert_eq!(alice_sessions.len(), 2);
let limited_sessions = env.db.list_sessions(None, Some(2)).expect("Failed to list limited sessions");
assert_eq!(limited_sessions.len(), 2);
}
#[test]
fn test_session_not_found_error() {
let env = TestEnv::new();
let result = env.session_manager.replay_session("nonexistent-id");
assert!(result.is_err());
let history = env
.session_manager
.get_conversation_history("nonexistent-id", None)
.expect("history should return Ok for non-existent session");
assert_eq!(history, "");
}
#[tokio::test]
async fn test_concurrent_message_additions() {
let env = TestEnv::new();
let session = env
.session_manager
.start_session(Some("test_user"))
.expect("Failed to create session");
ingest_text(&env.db, "test.md", "Test content").await;
let mut handles = vec![];
let shared_index = env.db.get_vector_index().expect("get_vector_index failed");
for i in 0..10 {
let session_id = session.id.clone();
let sm = SessionManager::new(env.db.clone());
let index = shared_index.clone();
let handle = tokio::spawn(async move {
sm.add_user_message(
&session_id,
&format!("Concurrent message {}", i),
TestEnv::default_config(),
index.as_ref(),
None,
)
.await
});
handles.push(handle);
}
for handle in handles {
handle.await.expect("Task panicked").expect("Failed to add message");
}
let messages = env
.db
.get_messages(&session.id, None)
.expect("Failed to get messages");
assert_eq!(messages.len(), 10);
let mut sequence_numbers: Vec<usize> = messages.iter().map(|m| m.sequence_number).collect();
sequence_numbers.sort();
sequence_numbers.dedup();
assert_eq!(sequence_numbers.len(), 10, "Sequence numbers should be unique");
}
#[tokio::test]
async fn test_empty_session_operations() {
let env = TestEnv::new();
let session = env
.session_manager
.start_session(Some("test_user"))
.expect("Failed to create session");
let history = env
.session_manager
.get_conversation_history(&session.id, None)
.expect("Failed to get history");
assert_eq!(history, "");
let replay = env
.session_manager
.replay_session(&session.id)
.expect("Failed to replay");
assert_eq!(replay.turns.len(), 0);
}