You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ai_synth/backend/tests/api_article_history_test.rs

134 lines
5.0 KiB
Rust

//! 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"
);
}