//! Integration tests for time tracking commands. //! //! Tests start/stop/discard timer and session listing through the repository //! layer, since time tracking commands are thin wrappers that require Tauri `State<'_>`. use chrono::{Duration, Utc}; use goingson_core::{NewTask, Priority}; use crate::test_utils::setup_test_state; // ============ Start Timer ============ #[tokio::test] async fn test_start_timer_success() { let (state, user_id) = setup_test_state().await; let new_task = NewTask::builder("Timed task") .priority(Priority::Medium) .build(); let task = state.tasks.create(user_id, new_task).await.unwrap(); let session = state.tasks.start_timer(task.id, user_id).await.unwrap(); assert_eq!(session.task_id, task.id); assert!(session.is_active()); assert!(session.ended_at.is_none()); assert!(session.duration_minutes.is_none()); } #[tokio::test] async fn test_start_timer_conflict() { let (state, user_id) = setup_test_state().await; let task1 = NewTask::builder("First task") .priority(Priority::Medium) .build(); let task1 = state.tasks.create(user_id, task1).await.unwrap(); let task2 = NewTask::builder("Second task") .priority(Priority::Medium) .build(); let task2 = state.tasks.create(user_id, task2).await.unwrap(); // Start timer on first task state.tasks.start_timer(task1.id, user_id).await.unwrap(); // Starting a timer on second task should fail (only one active timer allowed) let result = state.tasks.start_timer(task2.id, user_id).await; assert!(result.is_err()); } // ============ Stop Timer ============ #[tokio::test] async fn test_stop_timer_success() { let (state, user_id) = setup_test_state().await; let new_task = NewTask::builder("Task to time") .priority(Priority::High) .build(); let task = state.tasks.create(user_id, new_task).await.unwrap(); // Start and then stop the timer state.tasks.start_timer(task.id, user_id).await.unwrap(); let stopped = state.tasks.stop_timer(task.id, user_id).await.unwrap(); assert!(stopped.is_some()); let session = stopped.unwrap(); assert_eq!(session.task_id, task.id); assert!(session.ended_at.is_some()); assert!(!session.is_active()); } #[tokio::test] async fn test_stop_timer_no_active_session() { let (state, user_id) = setup_test_state().await; let new_task = NewTask::builder("Never timed") .priority(Priority::Low) .build(); let task = state.tasks.create(user_id, new_task).await.unwrap(); // Stopping a timer that was never started returns None let stopped = state.tasks.stop_timer(task.id, user_id).await.unwrap(); assert!(stopped.is_none()); } // ============ Get Active Timer ============ #[tokio::test] async fn test_get_active_timer_none() { let (state, user_id) = setup_test_state().await; // No tasks, no timers let active = state.tasks.get_active_timer(user_id).await.unwrap(); assert!(active.is_none()); } #[tokio::test] async fn test_get_active_timer_some() { let (state, user_id) = setup_test_state().await; let new_task = NewTask::builder("Active timer task") .priority(Priority::Medium) .build(); let task = state.tasks.create(user_id, new_task).await.unwrap(); state.tasks.start_timer(task.id, user_id).await.unwrap(); let active = state.tasks.get_active_timer(user_id).await.unwrap(); assert!(active.is_some()); let (session, description) = active.unwrap(); assert_eq!(session.task_id, task.id); assert!(session.is_active()); assert_eq!(description, "Active timer task"); } #[tokio::test] async fn test_get_active_timer_after_stop() { let (state, user_id) = setup_test_state().await; let new_task = NewTask::builder("Start then stop") .priority(Priority::Medium) .build(); let task = state.tasks.create(user_id, new_task).await.unwrap(); state.tasks.start_timer(task.id, user_id).await.unwrap(); state.tasks.stop_timer(task.id, user_id).await.unwrap(); // After stopping, no active timer let active = state.tasks.get_active_timer(user_id).await.unwrap(); assert!(active.is_none()); } // ============ Discard Timer ============ #[tokio::test] async fn test_discard_timer_success() { let (state, user_id) = setup_test_state().await; let new_task = NewTask::builder("Discard me") .priority(Priority::Low) .build(); let task = state.tasks.create(user_id, new_task).await.unwrap(); state.tasks.start_timer(task.id, user_id).await.unwrap(); let discarded = state.tasks.discard_timer(task.id, user_id).await.unwrap(); assert!(discarded); // No active timer after discard let active = state.tasks.get_active_timer(user_id).await.unwrap(); assert!(active.is_none()); } #[tokio::test] async fn test_discard_timer_no_active() { let (state, user_id) = setup_test_state().await; let new_task = NewTask::builder("Not timed") .priority(Priority::Medium) .build(); let task = state.tasks.create(user_id, new_task).await.unwrap(); // Discarding when no timer is active returns false let discarded = state.tasks.discard_timer(task.id, user_id).await.unwrap(); assert!(!discarded); } // ============ List Time Sessions ============ #[tokio::test] async fn test_list_time_sessions_empty() { let (state, user_id) = setup_test_state().await; let new_task = NewTask::builder("No sessions") .priority(Priority::Medium) .build(); let task = state.tasks.create(user_id, new_task).await.unwrap(); let sessions = state .tasks .list_time_sessions(task.id, user_id) .await .unwrap(); assert!(sessions.is_empty()); } #[tokio::test] async fn test_list_time_sessions_after_start_stop() { let (state, user_id) = setup_test_state().await; let new_task = NewTask::builder("Tracked task") .priority(Priority::Medium) .build(); let task = state.tasks.create(user_id, new_task).await.unwrap(); // Complete one session state.tasks.start_timer(task.id, user_id).await.unwrap(); state.tasks.stop_timer(task.id, user_id).await.unwrap(); let sessions = state .tasks .list_time_sessions(task.id, user_id) .await .unwrap(); assert_eq!(sessions.len(), 1); assert!(sessions[0].ended_at.is_some()); } // ============ Time Summary ============ #[tokio::test] async fn test_get_time_summary_empty() { let (state, user_id) = setup_test_state().await; let start = Utc::now() - Duration::days(7); let end = Utc::now(); let summaries = state .tasks .get_time_summary(user_id, start, end) .await .unwrap(); assert!(summaries.is_empty()); }