//! Integration tests for milestone repository operations. use chrono::NaiveDate; use goingson_core::{MilestoneStatus, NewMilestone, NewTask, Priority, TaskStatus}; use crate::test_utils::{create_test_project, setup_test_state}; #[tokio::test] async fn create_and_list_milestone() { let (state, user_id) = setup_test_state().await; let project_id = create_test_project(&state, user_id).await; let target = NaiveDate::from_ymd_opt(2026, 8, 15).unwrap(); let created = state .milestones .create( user_id, NewMilestone { project_id, name: "Alpha Release".to_string(), description: "Ship it".to_string(), position: 0, target_date: Some(target), }, ) .await .unwrap(); assert_eq!(created.name, "Alpha Release"); assert_eq!(created.description, "Ship it"); assert_eq!(created.target_date, Some(target)); assert_eq!(created.status, MilestoneStatus::Open); let milestones = state .milestones .list_by_project(project_id, user_id) .await .unwrap(); assert_eq!(milestones.len(), 1); assert_eq!(milestones[0].id, created.id); } #[tokio::test] async fn update_milestone() { let (state, user_id) = setup_test_state().await; let project_id = create_test_project(&state, user_id).await; let created = state .milestones .create( user_id, NewMilestone { project_id, name: "Original".to_string(), description: "old".to_string(), position: 0, target_date: None, }, ) .await .unwrap(); let new_date = NaiveDate::from_ymd_opt(2026, 12, 31).unwrap(); let updated = state .milestones .update( created.id, user_id, "Renamed", "new desc", Some(new_date), &MilestoneStatus::Completed, ) .await .unwrap() .unwrap(); assert_eq!(updated.name, "Renamed"); assert_eq!(updated.description, "new desc"); assert_eq!(updated.target_date, Some(new_date)); } #[tokio::test] async fn update_nonexistent_milestone_returns_none() { let (state, user_id) = setup_test_state().await; let fake_id = goingson_core::MilestoneId::from(uuid::Uuid::new_v4()); let result = state .milestones .update(fake_id, user_id, "Nope", "", None, &MilestoneStatus::Open) .await .unwrap(); assert!(result.is_none()); } #[tokio::test] async fn delete_milestone() { let (state, user_id) = setup_test_state().await; let project_id = create_test_project(&state, user_id).await; let created = state .milestones .create( user_id, NewMilestone { project_id, name: "To Delete".to_string(), description: String::new(), position: 0, target_date: None, }, ) .await .unwrap(); assert!(state.milestones.delete(created.id, user_id).await.unwrap()); assert!( state .milestones .get_by_id(created.id, user_id) .await .unwrap() .is_none() ); // Deleting an already-gone milestone reports no rows affected. assert!(!state.milestones.delete(created.id, user_id).await.unwrap()); } /// Mirrors the progress fields the `list_milestones`/`update_milestone` commands /// pre-compute: task_count, completed_count, and rounded percentage from the tasks /// linked to a milestone. #[tokio::test] async fn milestone_progress_reflects_linked_tasks() { let (state, user_id) = setup_test_state().await; let project_id = create_test_project(&state, user_id).await; let milestone = state .milestones .create( user_id, NewMilestone { project_id, name: "Progress".to_string(), description: String::new(), position: 0, target_date: None, }, ) .await .unwrap(); // 4 tasks on the milestone, 3 complete; plus 1 unrelated task on the project. for i in 0..4 { let t = NewTask::builder(format!("Milestone task {i}")) .project_id(project_id) .milestone_id(milestone.id) .priority(Priority::Medium) .build(); let task = state.tasks.create(user_id, t).await.unwrap(); if i < 3 { state.tasks.complete(task.id, user_id).await.unwrap(); } } let unrelated = NewTask::builder("Not on milestone") .project_id(project_id) .priority(Priority::Low) .build(); state.tasks.create(user_id, unrelated).await.unwrap(); let all_tasks = state .tasks .list_by_project(user_id, project_id) .await .unwrap(); let milestone_tasks: Vec<_> = all_tasks .iter() .filter(|t| t.milestone_id == Some(milestone.id)) .collect(); let task_count = milestone_tasks.len(); let completed_count = milestone_tasks .iter() .filter(|t| t.status == TaskStatus::Completed) .count(); let progress = ((completed_count as f64 / task_count as f64) * 100.0).round() as u8; assert_eq!(task_count, 4); assert_eq!(completed_count, 3); assert_eq!(progress, 75); }