use metis_core::application::services::workspace::WorkspaceInitializationService;
use metis_core::application::services::{DatabaseService, SyncService};
use metis_core::dal::Database;
use std::fs;
use tempfile::tempdir;
#[tokio::test]
async fn test_short_code_collision_resolution() {
let temp_dir = tempdir().unwrap();
let base_path = temp_dir.path();
let result = WorkspaceInitializationService::initialize_workspace_with_prefix(
base_path,
"Test Project",
Some("TEST"),
)
.await
.expect("Failed to initialize workspace");
let metis_dir = result.metis_dir;
let init_a_dir = metis_dir
.join("strategies/NULL/initiatives/I-0001/tasks");
fs::create_dir_all(&init_a_dir).expect("Failed to create initiative A dir");
let task_a = init_a_dir.join("T-0001.md");
let task_a_content = "---\n\
id: dev-a-task\n\
level: task\n\
title: \"Developer A Task\"\n\
short_code: \"TEST-T-0001\"\n\
created_at: 2025-01-01T10:00:00Z\n\
updated_at: 2025-01-01T10:00:00Z\n\
parent: I-0001\n\
blocked_by: []\n\
archived: false\n\
tags:\n\
- \"#task\"\n\
- \"#phase/todo\"\n\
exit_criteria_met: false\n\
---\n\
\n\
# Developer A Task\n\
\n\
This task was created by developer A.";
fs::write(&task_a, task_a_content).expect("Failed to write task A");
let init_b_dir = metis_dir
.join("strategies/NULL/initiatives/I-0002/tasks");
fs::create_dir_all(&init_b_dir).expect("Failed to create initiative B dir");
let task_b = init_b_dir.join("T-0001.md");
let task_b_content = "---\n\
id: dev-b-task\n\
level: task\n\
title: \"Developer B Task\"\n\
short_code: \"TEST-T-0001\"\n\
created_at: 2025-01-01T11:00:00Z\n\
updated_at: 2025-01-01T11:00:00Z\n\
parent: I-0002\n\
blocked_by: []\n\
archived: false\n\
tags:\n\
- \"#task\"\n\
- \"#phase/todo\"\n\
exit_criteria_met: false\n\
---\n\
\n\
# Developer B Task\n\
\n\
This task was created by developer B.";
fs::write(&task_b, task_b_content).expect("Failed to write task B");
let db_path = metis_dir.join("metis.db");
let db = Database::new(&db_path.to_string_lossy()).expect("Failed to open database");
let mut db_service = DatabaseService::new(db.into_repository());
let mut sync_service = SyncService::new(&mut db_service)
.with_workspace_dir(&metis_dir);
let sync_results = sync_service
.sync_directory(&metis_dir)
.await
.expect("Sync should succeed");
println!("Sync results:");
for result in &sync_results {
println!(" {:?}", result);
}
let renumbered_results: Vec<_> = sync_results
.iter()
.filter(|r| matches!(r, metis_core::application::services::synchronization::SyncResult::Renumbered { .. }))
.collect();
assert_eq!(
renumbered_results.len(),
1,
"Should have exactly 1 renumbered document"
);
println!("Files in init_a_dir:");
for entry in fs::read_dir(&init_a_dir).unwrap() {
let entry = entry.unwrap();
println!(" - {}", entry.file_name().to_string_lossy());
}
println!("Files in init_b_dir:");
for entry in fs::read_dir(&init_b_dir).unwrap() {
let entry = entry.unwrap();
println!(" - {}", entry.file_name().to_string_lossy());
}
let task_a_new_content = fs::read_to_string(&task_a)
.or_else(|_| {
let renamed_path = init_a_dir.join("TEST-T-0002.md");
fs::read_to_string(renamed_path)
})
.expect("Should be able to read task A");
let task_b_files: Vec<_> = fs::read_dir(&init_b_dir)
.unwrap()
.map(|e| e.unwrap().path())
.filter(|p| p.extension().and_then(|e| e.to_str()) == Some("md"))
.collect();
assert_eq!(task_b_files.len(), 1, "Should have exactly one file in init_b_dir");
let task_b_new_content = fs::read_to_string(&task_b_files[0])
.expect("Should be able to read task B");
assert!(
task_a_new_content.contains("TEST-T-") && task_b_new_content.contains("TEST-T-"),
"Both tasks should have TEST-T- short codes"
);
let has_0001 = task_a_new_content.contains("TEST-T-0001")
|| task_b_new_content.contains("TEST-T-0001");
let has_0002 = task_a_new_content.contains("TEST-T-0002")
|| task_b_new_content.contains("TEST-T-0002");
assert!(has_0001, "One task should have TEST-T-0001");
assert!(has_0002, "One task should have TEST-T-0002 (renumbered task)");
let repo = db_service.find_by_type(metis_core::domain::documents::types::DocumentType::Task)
.expect("Should be able to query tasks");
let task_short_codes: Vec<String> = repo.iter().map(|t| t.short_code.clone()).collect();
assert!(
task_short_codes.contains(&"TEST-T-0001".to_string())
|| task_short_codes.contains(&"TEST-T-0002".to_string()),
"Database should contain renumbered tasks"
);
}
#[tokio::test]
async fn test_sibling_cross_reference_update() {
let temp_dir = tempdir().unwrap();
let base_path = temp_dir.path();
let result = WorkspaceInitializationService::initialize_workspace(base_path, "Test Project")
.await
.expect("Failed to initialize workspace");
let metis_dir = result.metis_dir;
let tasks_dir = metis_dir
.join("strategies/NULL/initiatives/I-0001/tasks");
fs::create_dir_all(&tasks_dir).expect("Failed to create tasks dir");
let task1 = tasks_dir.join("T-0001.md");
let task1_content = "---\n\
id: task-1\n\
level: task\n\
title: \"Task 1\"\n\
short_code: \"TEST-T-0001\"\n\
created_at: 2025-01-01T10:00:00Z\n\
updated_at: 2025-01-01T10:00:00Z\n\
parent: I-0001\n\
blocked_by: []\n\
archived: false\n\
tags:\n\
- \"#task\"\n\
exit_criteria_met: false\n\
---\n\
\n\
# Task 1";
fs::write(&task1, task1_content).expect("Failed to write task 1");
let task2 = tasks_dir.join("T-0002.md");
let task2_content = "---\n\
id: task-2\n\
level: task\n\
title: \"Task 2\"\n\
short_code: \"TEST-T-0001\"\n\
created_at: 2025-01-01T11:00:00Z\n\
updated_at: 2025-01-01T11:00:00Z\n\
parent: I-0001\n\
blocked_by:\n\
- TEST-T-0001\n\
archived: false\n\
tags:\n\
- \"#task\"\n\
exit_criteria_met: false\n\
---\n\
\n\
# Task 2\n\
\n\
This task depends on TEST-T-0001 being completed.";
fs::write(&task2, task2_content).expect("Failed to write task 2");
let db_path = metis_dir.join("metis.db");
let db = Database::new(&db_path.to_string_lossy()).expect("Failed to open database");
let mut db_service = DatabaseService::new(db.into_repository());
let mut sync_service = SyncService::new(&mut db_service)
.with_workspace_dir(&metis_dir);
sync_service
.sync_directory(&metis_dir)
.await
.expect("Sync should succeed");
let task2_updated = fs::read_to_string(&task2)
.or_else(|_| {
let renamed = tasks_dir.join("T-0003.md");
fs::read_to_string(renamed)
})
.expect("Should be able to read updated task 2");
let has_reference = task2_updated.contains("TEST-T-");
assert!(
has_reference,
"Task 2 should still have a reference to a task"
);
}
#[tokio::test]
async fn test_collision_resolution_depth_ordering() {
let temp_dir = tempdir().unwrap();
let base_path = temp_dir.path();
let result = WorkspaceInitializationService::initialize_workspace(base_path, "Test Project")
.await
.expect("Failed to initialize workspace");
let metis_dir = result.metis_dir;
let shallow_dir = metis_dir.join("strategies/NULL/initiatives/I-0001/tasks");
fs::create_dir_all(&shallow_dir).expect("Failed to create shallow dir");
let shallow_task = shallow_dir.join("T-0001.md");
let shallow_content = "---\n\
id: shallow-task\n\
level: task\n\
title: \"Shallow Task\"\n\
short_code: \"TEST-T-0001\"\n\
created_at: 2025-01-01T10:00:00Z\n\
updated_at: 2025-01-01T10:00:00Z\n\
parent: \n\
blocked_by: []\n\
archived: false\n\
tags: [\"#task\"]\n\
exit_criteria_met: false\n\
---\n\
# Shallow Task";
fs::write(&shallow_task, shallow_content).expect("Failed to write shallow task");
let deep_dir = metis_dir.join("strategies/S-0001/initiatives/I-0002/tasks");
fs::create_dir_all(&deep_dir).expect("Failed to create deep dir");
let deep_task = deep_dir.join("T-0001.md");
let deep_content = "---\n\
id: deep-task\n\
level: task\n\
title: \"Deep Task\"\n\
short_code: \"TEST-T-0001\"\n\
created_at: 2025-01-01T11:00:00Z\n\
updated_at: 2025-01-01T11:00:00Z\n\
parent: \n\
blocked_by: []\n\
archived: false\n\
tags: [\"#task\"]\n\
exit_criteria_met: false\n\
---\n\
# Deep Task";
fs::write(&deep_task, deep_content).expect("Failed to write deep task");
let db_path = metis_dir.join("metis.db");
let db = Database::new(&db_path.to_string_lossy()).expect("Failed to open database");
let mut db_service = DatabaseService::new(db.into_repository());
let mut sync_service = SyncService::new(&mut db_service)
.with_workspace_dir(&metis_dir);
sync_service
.sync_directory(&metis_dir)
.await
.expect("Sync should succeed");
let shallow_updated = fs::read_to_string(&shallow_task)
.expect("Shallow task should exist at original path");
assert!(
shallow_updated.contains("TEST-T-0001"),
"Shallow task should keep original short code"
);
let deep_files: Vec<_> = fs::read_dir(&deep_dir)
.unwrap()
.map(|e| e.unwrap().path())
.filter(|p| p.extension().and_then(|e| e.to_str()) == Some("md"))
.collect();
assert_eq!(deep_files.len(), 1, "Should have exactly one file in deep_dir");
let deep_updated = fs::read_to_string(&deep_files[0])
.expect("Should be able to read deep task");
let deep_filename = deep_files[0].file_name().unwrap().to_string_lossy();
assert!(
deep_filename.contains("T-0002.md") || deep_updated.contains("TEST-T-0002"),
"Deep task should be renumbered (file: {}, contains TEST-T-0002: {})",
deep_filename,
deep_updated.contains("TEST-T-0002")
);
}