//! Integration tests for the article history and provenance endpoints. //! //! Tests: //! - GET /api/v1/article-history — list article history //! - DELETE /api/v1/article-history — clear article history //! - GET /api/v1/syntheses/:id/provenance — get provenance for a synthesis //! //! Covers authentication, empty-state responses, and nonexistent lookups. //! //! 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() } // ═══════════════════════════════════════════════════════════════════════════ // Authentication // ═══════════════════════════════════════════════════════════════════════════ #[tokio::test] async fn list_article_history_without_auth_returns_401() { if !require_test_db() { eprintln!("SKIPPED: TEST_DATABASE_URL not set"); return; } let app = common::TestApp::new().await; let (status, body) = app .get_with_session("/api/v1/article-history", "invalid-token") .await; assert_eq!( status, StatusCode::UNAUTHORIZED, "GET /article-history without auth should return 401" ); assert_eq!(body["error"], "unauthorized"); } // ═══════════════════════════════════════════════════════════════════════════ // List — empty state // ═══════════════════════════════════════════════════════════════════════════ #[tokio::test] async fn list_article_history_returns_empty_initially() { 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("history-empty@example.com") .await; let (status, resp) = app .get_with_session("/api/v1/article-history", &session) .await; assert_eq!( status, StatusCode::OK, "GET /article-history should return 200" ); let items = resp["items"] .as_array() .expect("Response should contain an items array"); assert!(items.is_empty(), "New user should have empty article history"); assert_eq!(resp["total"], 0); } // ═══════════════════════════════════════════════════════════════════════════ // Clear history // ═══════════════════════════════════════════════════════════════════════════ #[tokio::test] async fn clear_article_history_returns_200() { 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("history-clear@example.com") .await; let (status, resp) = app .delete_with_session("/api/v1/article-history", &session) .await; assert_eq!( status, StatusCode::OK, "DELETE /article-history should return 200" ); assert_eq!(resp["deleted"], 0, "No entries to delete for a new user"); } // ═══════════════════════════════════════════════════════════════════════════ // Provenance — nonexistent synthesis // ═══════════════════════════════════════════════════════════════════════════ #[tokio::test] async fn get_provenance_for_nonexistent_synthesis_returns_404() { 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("provenance-404@example.com") .await; let random_id = uuid::Uuid::new_v4(); let (status, _resp) = app .get_with_session( &format!("/api/v1/syntheses/{}/provenance", random_id), &session, ) .await; assert_eq!( status, StatusCode::NOT_FOUND, "GET /syntheses/:id/provenance for nonexistent synthesis should return 404" ); }