//! Integration tests for saved view repository operations. use goingson_core::{ NewSavedView, Priority, ProjectId, SortDirection, SortField, TaskStatus, ViewFilters, ViewType, }; use crate::test_utils::setup_test_state; fn view(name: &str, view_type: ViewType, pinned: bool) -> NewSavedView { NewSavedView { name: name.to_string(), view_type, filters: ViewFilters::default(), sort_by: None, sort_order: Some(SortDirection::Asc), is_pinned: Some(pinned), } } #[tokio::test] async fn create_and_list_saved_view() { let (state, user_id) = setup_test_state().await; let created = state .saved_views .create(user_id, view("My Tasks", ViewType::Tasks, false)) .await .unwrap(); assert_eq!(created.name, "My Tasks"); assert_eq!(created.view_type, ViewType::Tasks); assert!(!created.is_pinned); let all = state.saved_views.list_all(user_id).await.unwrap(); assert_eq!(all.len(), 1); assert_eq!(all[0].id, created.id); } #[tokio::test] async fn list_pinned_filters_to_pinned_only() { let (state, user_id) = setup_test_state().await; state .saved_views .create(user_id, view("Pinned", ViewType::Tasks, true)) .await .unwrap(); state .saved_views .create(user_id, view("Unpinned", ViewType::Emails, false)) .await .unwrap(); let pinned = state.saved_views.list_pinned(user_id).await.unwrap(); assert_eq!(pinned.len(), 1); assert_eq!(pinned[0].name, "Pinned"); assert_eq!(state.saved_views.list_all(user_id).await.unwrap().len(), 2); } #[tokio::test] async fn delete_saved_view() { let (state, user_id) = setup_test_state().await; let created = state .saved_views .create(user_id, view("Temp", ViewType::Events, false)) .await .unwrap(); assert!(state.saved_views.delete(created.id, user_id).await.unwrap()); assert!( state .saved_views .get_by_id(created.id, user_id) .await .unwrap() .is_none() ); // Second delete finds nothing to remove. assert!(!state.saved_views.delete(created.id, user_id).await.unwrap()); } #[tokio::test] async fn get_nonexistent_view_returns_none() { let (state, user_id) = setup_test_state().await; let fake_id = goingson_core::SavedViewId::from(uuid::Uuid::new_v4()); assert!( state .saved_views .get_by_id(fake_id, user_id) .await .unwrap() .is_none() ); } /// The view's filter payload is stored as JSON and the sort config as text columns. /// Verify the full query payload survives a create -> fetch round-trip. #[tokio::test] async fn filters_and_sort_round_trip() { let (state, user_id) = setup_test_state().await; let project_id = ProjectId::new(); let filters = ViewFilters { status: Some(vec![TaskStatus::Pending, TaskStatus::Started]), priority: Some(vec![Priority::High]), project_id: Some(project_id), is_snoozed: Some(true), is_waiting: Some(false), ..Default::default() }; let created = state .saved_views .create( user_id, NewSavedView { name: "Complex".to_string(), view_type: ViewType::Tasks, filters: filters.clone(), sort_by: Some(SortField::Urgency), sort_order: Some(SortDirection::Desc), is_pinned: Some(true), }, ) .await .unwrap(); let fetched = state .saved_views .get_by_id(created.id, user_id) .await .unwrap() .unwrap(); assert_eq!(fetched.filters.status, filters.status); assert_eq!(fetched.filters.priority, filters.priority); assert_eq!(fetched.filters.project_id, Some(project_id)); assert_eq!(fetched.filters.is_snoozed, Some(true)); assert_eq!(fetched.filters.is_waiting, Some(false)); assert_eq!(fetched.sort_by, Some(SortField::Urgency)); assert_eq!(fetched.sort_order, SortDirection::Desc); assert!(fetched.is_pinned); } #[tokio::test] async fn toggle_pinned_flips_state() { let (state, user_id) = setup_test_state().await; let created = state .saved_views .create(user_id, view("Toggle", ViewType::Tasks, false)) .await .unwrap(); assert!(!created.is_pinned); let on = state .saved_views .toggle_pinned(created.id, user_id) .await .unwrap() .unwrap(); assert!(on.is_pinned); let off = state .saved_views .toggle_pinned(created.id, user_id) .await .unwrap() .unwrap(); assert!(!off.is_pinned); }