//! Integration tests for the `PUT /api/v1/sources/preferred` endpoint. //! //! Tests: //! - Setting preferred sources //! - Clearing all preferred sources //! - Authentication requirement //! //! Requires a running Postgres instance. Set `TEST_DATABASE_URL` to run. mod common; use axum::http::StatusCode; fn require_test_db() -> bool { std::env::var("TEST_DATABASE_URL").is_ok() } #[tokio::test] async fn update_preferred_sets_sources() { if !require_test_db() { eprintln!("SKIPPED: TEST_DATABASE_URL not set"); return; } let app = common::TestApp::new().await; let (_user_id, session) = app .create_authenticated_user("pref-set@example.com") .await; // Create a theme first let theme_body = serde_json::json!({ "name": "Pref Theme", "theme": "Test", "categories": ["Cat"] }); let (theme_status, theme_resp) = app .post_with_session("/api/v1/themes", &theme_body, &session) .await; assert_eq!(theme_status, StatusCode::CREATED); let theme_id = theme_resp["id"].as_str().unwrap(); // Create 3 sources let mut source_ids = Vec::new(); for i in 1..=3 { let body = serde_json::json!({ "title": format!("Source {}", i), "url": format!("https://source{}.example.com", i), "theme_id": theme_id }); let (status, resp) = app .post_with_session("/api/v1/sources", &body, &session) .await; assert_eq!(status, StatusCode::CREATED); source_ids.push(resp["id"].as_str().unwrap().to_string()); } // PUT /sources/preferred with [id1, id3] let pref_body = serde_json::json!({ "source_ids": [source_ids[0], source_ids[2]], "theme_id": theme_id }); let (pref_status, _) = app .put_with_session("/api/v1/sources/preferred", &pref_body, &session) .await; assert_eq!(pref_status, StatusCode::OK); // GET /sources → verify preferred flags let (list_status, list_body) = app .get_with_session( &format!("/api/v1/sources?theme_id={}", theme_id), &session, ) .await; assert_eq!(list_status, StatusCode::OK); let sources = list_body.as_array().expect("Should be an array"); assert_eq!(sources.len(), 3); for source in sources { let id = source["id"].as_str().unwrap(); let is_preferred = source["is_preferred"].as_bool().unwrap(); if id == source_ids[0] || id == source_ids[2] { assert!(is_preferred, "Source {} should be preferred", id); } else { assert!(!is_preferred, "Source {} should NOT be preferred", id); } } } #[tokio::test] async fn update_preferred_clears_all_when_empty() { if !require_test_db() { eprintln!("SKIPPED: TEST_DATABASE_URL not set"); return; } let app = common::TestApp::new().await; let (_user_id, session) = app .create_authenticated_user("pref-clear@example.com") .await; // Create a theme let theme_body = serde_json::json!({ "name": "Pref Clear Theme", "theme": "Test", "categories": ["Cat"] }); let (_, theme_resp) = app .post_with_session("/api/v1/themes", &theme_body, &session) .await; let theme_id = theme_resp["id"].as_str().unwrap(); // Create 2 sources let mut source_ids = Vec::new(); for i in 1..=2 { let body = serde_json::json!({ "title": format!("Source {}", i), "url": format!("https://clear-source{}.example.com", i), "theme_id": theme_id }); let (_, resp) = app .post_with_session("/api/v1/sources", &body, &session) .await; source_ids.push(resp["id"].as_str().unwrap().to_string()); } // Set some as preferred let pref_body = serde_json::json!({ "source_ids": [source_ids[0]], "theme_id": theme_id }); let (pref_status, _) = app .put_with_session("/api/v1/sources/preferred", &pref_body, &session) .await; assert_eq!(pref_status, StatusCode::OK); // Clear all preferred let clear_body = serde_json::json!({ "source_ids": [], "theme_id": theme_id }); let (clear_status, _) = app .put_with_session("/api/v1/sources/preferred", &clear_body, &session) .await; assert_eq!(clear_status, StatusCode::OK); // GET /sources → all is_preferred=false let (list_status, list_body) = app .get_with_session( &format!("/api/v1/sources?theme_id={}", theme_id), &session, ) .await; assert_eq!(list_status, StatusCode::OK); let sources = list_body.as_array().expect("Should be an array"); for source in sources { assert_eq!( source["is_preferred"].as_bool().unwrap(), false, "All sources should be non-preferred after clearing" ); } } #[tokio::test] async fn update_preferred_without_auth_returns_401() { if !require_test_db() { eprintln!("SKIPPED: TEST_DATABASE_URL not set"); return; } let app = common::TestApp::new().await; let body = serde_json::json!({ "source_ids": [], "theme_id": "00000000-0000-0000-0000-000000000000" }); let (status, resp) = app .put_with_session("/api/v1/sources/preferred", &body, "invalid-session-token") .await; assert_eq!( status, StatusCode::UNAUTHORIZED, "PUT /sources/preferred without auth should return 401" ); assert_eq!(resp["error"], "unauthorized"); }