use intent_engine::db::models::SearchResult;
use intent_engine::db::{create_pool, run_migrations};
use intent_engine::events::EventManager;
use intent_engine::report::ReportManager;
use intent_engine::search::SearchManager;
use intent_engine::tasks::TaskManager;
use rand::Rng;
use std::time::Instant;
use tempfile::TempDir;
const TOTAL_TASKS_100K: usize = 100_000;
const TOTAL_TASKS_10K: usize = 10_000;
#[tokio::test]
#[ignore] async fn test_large_dataset_report_and_search() {
run_dataset_test(TOTAL_TASKS_100K).await;
}
#[tokio::test]
#[ignore] async fn test_medium_dataset_report_and_search() {
run_dataset_test(TOTAL_TASKS_10K).await;
}
async fn run_dataset_test(total_tasks: usize) {
println!(
"\n🚀 Starting dataset performance test with {} tasks",
total_tasks
);
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("large_test.db");
let pool = create_pool(&db_path).await.unwrap();
run_migrations(&pool).await.unwrap();
let task_mgr = TaskManager::new(&pool);
let search_mgr = SearchManager::new(&pool);
let event_mgr = EventManager::new(&pool);
let report_mgr = ReportManager::new(&pool);
println!("\n📝 Phase 1: Inserting {} tasks...", total_tasks);
let start = Instant::now();
let keywords = vec![
"authentication",
"database",
"frontend",
"backend",
"api",
"security",
"optimization",
"refactor",
"feature",
"bugfix",
"documentation",
"testing",
"deployment",
"migration",
"performance",
];
let mut task_ids = Vec::with_capacity(total_tasks);
for i in 0..total_tasks {
let keyword_idx = i % keywords.len();
let keyword = keywords[keyword_idx];
let name = format!("{} task #{}", keyword, i);
let spec = format!("Implement {} functionality for module {}", keyword, i / 100);
let task = task_mgr
.add_task(
name.to_string(),
Some(spec.to_string()),
None,
None,
None,
None,
)
.await
.unwrap();
task_ids.push(task.id);
if (i + 1) % 10000 == 0 {
println!(
" ✓ Inserted {} tasks ({:.1}%)",
i + 1,
(i + 1) as f64 / total_tasks as f64 * 100.0
);
}
}
let insert_duration = start.elapsed();
println!(
"✅ Inserted {} tasks in {:.2}s ({:.0} tasks/sec)",
total_tasks,
insert_duration.as_secs_f64(),
total_tasks as f64 / insert_duration.as_secs_f64()
);
println!("\n🎲 Phase 2: Randomly starting and completing tasks...");
let start = Instant::now();
#[allow(deprecated)]
let mut rng = rand::thread_rng();
let mut started_count = 0;
let mut completed_count = 0;
let tasks_to_start = total_tasks * 30 / 100;
for _ in 0..tasks_to_start {
#[allow(deprecated)]
let random_idx = rng.gen_range(0..task_ids.len());
let task_id = task_ids[random_idx];
if task_mgr.start_task(task_id, false).await.is_ok() {
started_count += 1;
}
}
println!(" ✓ Started {} tasks", started_count);
let tasks_to_complete = started_count / 2;
for _ in 0..tasks_to_complete {
#[allow(deprecated)]
let random_idx = rng.gen_range(0..task_ids.len());
let task_id = task_ids[random_idx];
if sqlx::query(
r#"
INSERT INTO sessions (session_id, current_task_id, created_at, last_active_at)
VALUES ('-1', ?, datetime('now'), datetime('now'))
ON CONFLICT(session_id) DO UPDATE SET
current_task_id = excluded.current_task_id,
last_active_at = datetime('now')
"#,
)
.bind(task_id)
.execute(&pool)
.await
.is_ok()
{
let status: Option<String> =
sqlx::query_scalar("SELECT status FROM tasks WHERE id = ?")
.bind(task_id)
.fetch_optional(&pool)
.await
.unwrap();
if status == Some("doing".to_string()) {
let uncompleted_children: i64 = sqlx::query_scalar(
"SELECT COUNT(*) FROM tasks WHERE parent_id = ? AND status != 'done'",
)
.bind(task_id)
.fetch_one(&pool)
.await
.unwrap();
if uncompleted_children == 0 && task_mgr.done_task(false).await.is_ok() {
completed_count += 1;
}
}
}
}
println!(" ✓ Completed {} tasks", completed_count);
let state_change_duration = start.elapsed();
println!(
"✅ State changes completed in {:.2}s",
state_change_duration.as_secs_f64()
);
println!("\n📋 Adding events to sample tasks...");
let start = Instant::now();
let events_count = 1000;
for _ in 0..events_count {
#[allow(deprecated)]
let random_idx = rng.gen_range(0..task_ids.len());
let task_id = task_ids[random_idx];
let _ = event_mgr
.add_event(
task_id,
"progress".to_string(),
"Made significant progress on this task".to_string(),
)
.await;
}
let events_duration = start.elapsed();
println!(
"✅ Added {} events in {:.2}s",
events_count,
events_duration.as_secs_f64()
);
println!("\n📊 Phase 3: Testing Report Performance...");
let start = Instant::now();
let report = report_mgr
.generate_report(None, None, None, None, true)
.await
.unwrap();
let summary_duration = start.elapsed();
println!(" Report Summary:");
println!(" Total tasks: {}", report.summary.total_tasks);
println!(" Todo: {}", report.summary.tasks_by_status.todo);
println!(" Doing: {}", report.summary.tasks_by_status.doing);
println!(" Done: {}", report.summary.tasks_by_status.done);
println!(" Total events: {}", report.summary.total_events);
println!(
" ⏱️ Summary report generated in {:.3}s",
summary_duration.as_secs_f64()
);
assert_eq!(report.summary.total_tasks, total_tasks as i64);
let total_status = report.summary.tasks_by_status.todo
+ report.summary.tasks_by_status.doing
+ report.summary.tasks_by_status.done;
assert_eq!(total_status, total_tasks as i64);
let start = Instant::now();
let done_report = report_mgr
.generate_report(None, Some("done".to_string()), None, None, true)
.await
.unwrap();
let status_filter_duration = start.elapsed();
println!(" Status Filter Report (done only):");
println!(" Total tasks: {}", done_report.summary.total_tasks);
println!(
" ⏱️ Status filter report generated in {:.3}s",
status_filter_duration.as_secs_f64()
);
assert_eq!(
done_report.summary.total_tasks,
report.summary.tasks_by_status.done
);
let start = Instant::now();
let time_report = report_mgr
.generate_report(Some("24h".to_string()), None, None, None, true)
.await
.unwrap();
let time_filter_duration = start.elapsed();
println!(" Time Filter Report (last 24h):");
println!(" Total tasks: {}", time_report.summary.total_tasks);
println!(
" ⏱️ Time filter report generated in {:.3}s",
time_filter_duration.as_secs_f64()
);
println!("\n🔍 Phase 4: Testing Search Performance and Accuracy...");
let mut total_search_duration = std::time::Duration::ZERO;
for keyword in &keywords[0..5] {
let start = Instant::now();
let results = search_mgr
.search(keyword, true, false, None, None, false)
.await
.unwrap()
.results;
let search_duration = start.elapsed();
total_search_duration += search_duration;
let expected_min = total_tasks / keywords.len() - 100; let expected_max = total_tasks / keywords.len() + 100;
println!(
" Search '{}': {} results in {:.3}s",
keyword,
results.len(),
search_duration.as_secs_f64()
);
let mut accurate = 0;
for result in results.iter().take(100) {
if let SearchResult::Task { task, .. } = result {
if task.name.contains(keyword)
|| task.spec.as_ref().is_some_and(|s| s.contains(keyword))
{
accurate += 1;
}
}
}
let accuracy = accurate as f64 / results.len().min(100) as f64 * 100.0;
println!(
" Accuracy: {:.1}% (checked {} results)",
accuracy,
results.len().min(100)
);
assert!(
results.len() >= expected_min,
"Search for '{}' returned too few results: {} < {}",
keyword,
results.len(),
expected_min
);
assert!(
results.len() <= expected_max,
"Search for '{}' returned too many results: {} > {}",
keyword,
results.len(),
expected_max
);
assert!(
accuracy >= 95.0,
"Search accuracy too low: {:.1}% < 95%",
accuracy
);
}
let start = Instant::now();
let complex_results = search_mgr
.search("authentication AND module", true, false, None, None, false)
.await
.unwrap()
.results;
let complex_duration = start.elapsed();
println!(
" Complex search 'authentication AND module': {} results in {:.3}s",
complex_results.len(),
complex_duration.as_secs_f64()
);
let mut accurate = 0;
for result in complex_results.iter().take(100) {
if let SearchResult::Task { task, .. } = result {
let text = format!(
"{} {}",
task.name,
task.spec.as_ref().unwrap_or(&String::new())
);
if text.contains("authentication") && text.contains("module") {
accurate += 1;
}
}
}
if !complex_results.is_empty() {
let accuracy = accurate as f64 / complex_results.len().min(100) as f64 * 100.0;
println!(" Accuracy: {:.1}%", accuracy);
assert!(accuracy >= 95.0);
}
println!("\n🔎 Phase 5: Testing find_tasks Performance...");
let start = Instant::now();
let result = task_mgr
.find_tasks(Some("todo".to_string()), None, None, None, None)
.await
.unwrap();
let find_duration = start.elapsed();
println!(
" Find todo tasks: {} results in {:.3}s",
result.tasks.len(),
find_duration.as_secs_f64()
);
assert_eq!(
result.tasks.len() as i64,
report.summary.tasks_by_status.todo
);
let avg_search_duration = total_search_duration.as_secs_f64() / 5.0;
println!("\n{}", "=".repeat(60));
println!("📈 PERFORMANCE SUMMARY");
println!("{}", "=".repeat(60));
println!("Dataset: {} tasks, {} events", total_tasks, events_count);
println!(
"Insert: {:.2}s ({:.0} tasks/sec)",
insert_duration.as_secs_f64(),
total_tasks as f64 / insert_duration.as_secs_f64()
);
println!("Report: {:.3}s (summary)", summary_duration.as_secs_f64());
println!(
"Search: ~{:.3}s (average per keyword)",
avg_search_duration
);
println!(
"Find: {:.3}s (status filter)",
find_duration.as_secs_f64()
);
println!("{}", "=".repeat(60));
assert!(
summary_duration.as_secs_f64() < 5.0,
"Summary report too slow: {:.2}s > 5s",
summary_duration.as_secs_f64()
);
assert!(
avg_search_duration < 1.0,
"Search too slow: {:.2}s > 1s",
avg_search_duration
);
assert!(
find_duration.as_secs_f64() < 2.0,
"Find tasks too slow: {:.2}s > 2s",
find_duration.as_secs_f64()
);
println!("✅ All performance tests passed!");
}
#[tokio::test]
#[ignore] async fn test_search_accuracy_detailed() {
println!("\n🎯 Testing search accuracy with controlled dataset...");
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("accuracy_test.db");
let pool = create_pool(&db_path).await.unwrap();
run_migrations(&pool).await.unwrap();
let task_mgr = TaskManager::new(&pool);
let search_mgr = SearchManager::new(&pool);
let test_cases = vec![
("JWT Authentication", "Implement JWT token authentication"),
("User Authentication", "Add user authentication with OAuth2"),
(
"Database Optimization",
"Optimize database query performance",
),
("API Endpoint", "Create new API endpoint for authentication"),
("Frontend Component", "Build authentication form component"),
("Backend Service", "Authentication service implementation"),
("Security Audit", "Perform security audit on authentication"),
("Performance Testing", "Load test authentication endpoints"),
("Documentation", "Document authentication flow"),
("Migration Script", "Database migration for auth tables"),
];
println!("Creating {} test tasks...", test_cases.len() * 100);
for i in 0..100 {
for (name_template, spec_template) in &test_cases {
let name = format!("{} #{}", name_template, i);
let spec = format!("{} - iteration {}", spec_template, i);
task_mgr
.add_task(
name.to_string(),
Some(spec.to_string()),
None,
None,
None,
None,
)
.await
.unwrap();
}
}
println!("Testing search accuracy...\n");
let results = search_mgr
.search("authentication", true, false, None, None, false)
.await
.unwrap()
.results;
println!("Search 'authentication': {} results", results.len());
let expected = test_cases
.iter()
.filter(|(n, s)| {
n.to_lowercase().contains("authentication")
|| s.to_lowercase().contains("authentication")
})
.count()
* 100;
println!(" Expected: ~{} tasks", expected);
println!(" Actual: {} tasks", results.len());
println!(
" Accuracy: {:.1}%",
results.len() as f64 / expected as f64 * 100.0
);
assert!(
results.len() >= expected - 10 && results.len() <= expected + 10,
"Search returned {} tasks, expected ~{}",
results.len(),
expected
);
let results = search_mgr
.search("authentication AND JWT", true, false, None, None, false)
.await
.unwrap()
.results;
println!(
"\nSearch 'authentication AND JWT': {} results",
results.len()
);
let expected = 100; println!(" Expected: ~{} tasks", expected);
println!(" Actual: {} tasks", results.len());
assert!(results.len() >= expected - 10 && results.len() <= expected + 10);
let results = search_mgr
.search("JWT OR OAuth2", true, false, None, None, false)
.await
.unwrap()
.results;
println!("\nSearch 'JWT OR OAuth2': {} results", results.len());
let expected = 200; println!(" Expected: ~{} tasks", expected);
println!(" Actual: {} tasks", results.len());
assert!(results.len() >= expected - 20 && results.len() <= expected + 20);
let results = search_mgr
.search("authentication NOT JWT", true, false, None, None, false)
.await
.unwrap()
.results;
println!(
"\nSearch 'authentication NOT JWT': {} results",
results.len()
);
let expected = 600; println!(" Expected: ~{} tasks", expected);
println!(" Actual: {} tasks", results.len());
println!("\n✅ Search accuracy tests completed!");
}
#[tokio::test]
#[ignore]
async fn test_concurrent_search_performance() {
println!("\n⚡ Testing concurrent search performance...");
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("concurrent_test.db");
let pool = create_pool(&db_path).await.unwrap();
run_migrations(&pool).await.unwrap();
let task_mgr = TaskManager::new(&pool);
let _search_mgr = SearchManager::new(&pool);
println!("Creating 10,000 tasks...");
for i in 0..10000 {
let keyword = match i % 5 {
0 => "authentication",
1 => "database",
2 => "frontend",
3 => "backend",
_ => "api",
};
task_mgr
.add_task(
format!("{} task #{}", keyword, i),
Some(format!("Implementation for {}", keyword)),
None,
None,
None,
None,
)
.await
.unwrap();
}
println!("Running concurrent searches...");
let start = Instant::now();
let mut handles = vec![];
for keyword in &["authentication", "database", "frontend", "backend", "api"] {
for _ in 0..2 {
let keyword = keyword.to_string();
let temp_dir_path = temp_dir.path().to_path_buf();
let handle = tokio::spawn(async move {
let db_path = temp_dir_path.join("concurrent_test.db");
let pool = create_pool(&db_path).await.unwrap();
let _task_mgr = TaskManager::new(&pool);
let search_mgr = SearchManager::new(&pool);
let start = Instant::now();
let results = search_mgr
.search(&keyword, true, false, None, None, false)
.await
.unwrap()
.results;
let duration = start.elapsed();
(keyword, results.len(), duration)
});
handles.push(handle);
}
}
let mut total_results = 0;
for handle in handles {
let (keyword, count, duration) = handle.await.unwrap();
println!(
" '{}': {} results in {:.3}s",
keyword,
count,
duration.as_secs_f64()
);
total_results += count;
}
let total_duration = start.elapsed();
println!("\n✅ Concurrent searches completed:");
println!(" Total time: {:.3}s", total_duration.as_secs_f64());
println!(" Total results: {}", total_results);
println!(
" Average time per search: {:.3}s",
total_duration.as_secs_f64() / 10.0
);
assert!(
total_duration.as_secs_f64() < 10.0,
"Concurrent searches too slow: {:.2}s > 10s",
total_duration.as_secs_f64()
);
}