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.
142 lines
5.5 KiB
Rust
142 lines
5.5 KiB
Rust
//! Integration tests for the LLM logs endpoint (GAP-2).
|
|
//!
|
|
//! Tests:
|
|
//! - GET /api/v1/llm-logs/:job_id
|
|
//!
|
|
//! The handler first checks that a synthesis with the given job_id exists and
|
|
//! belongs to the authenticated user, then returns the associated log entries.
|
|
//! A random/unknown job_id therefore returns 404 (not an empty array).
|
|
//!
|
|
//! 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()
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
// Auth (1 test)
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
#[tokio::test]
|
|
async fn get_llm_logs_without_auth_returns_401() {
|
|
if !require_test_db() {
|
|
eprintln!("SKIPPED: TEST_DATABASE_URL not set");
|
|
return;
|
|
}
|
|
|
|
let app = common::TestApp::new().await;
|
|
let fake_job_id = uuid::Uuid::new_v4();
|
|
|
|
let (status, body) = app
|
|
.get_with_session(
|
|
&format!("/api/v1/llm-logs/{}", fake_job_id),
|
|
"invalid-session-token",
|
|
)
|
|
.await;
|
|
|
|
assert_eq!(
|
|
status,
|
|
StatusCode::UNAUTHORIZED,
|
|
"GET /llm-logs/:job_id without auth should return 401"
|
|
);
|
|
assert_eq!(body["error"], "unauthorized");
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
// Not found (1 test)
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
/// The handler first verifies the job_id maps to a synthesis owned by the
|
|
/// authenticated user. A random UUID that has no matching synthesis in the DB
|
|
/// returns 404. This is intentional — it prevents enumeration of job IDs.
|
|
#[tokio::test]
|
|
async fn get_llm_logs_returns_404_for_unknown_job() {
|
|
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("llm-logs-404@example.com")
|
|
.await;
|
|
|
|
let fake_job_id = uuid::Uuid::new_v4();
|
|
let (status, body) = app
|
|
.get_with_session(&format!("/api/v1/llm-logs/{}", fake_job_id), &session)
|
|
.await;
|
|
|
|
assert_eq!(
|
|
status,
|
|
StatusCode::NOT_FOUND,
|
|
"GET /llm-logs/:job_id for an unknown job_id should return 404"
|
|
);
|
|
assert_eq!(body["error"], "not_found");
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
// Happy path (1 test)
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
/// Verify that when a synthesis exists for the given job_id, the endpoint
|
|
/// returns 200 with a JSON array (the log entries, which may be empty if no
|
|
/// LLM calls were recorded for the synthesis created directly via helper).
|
|
#[tokio::test]
|
|
async fn get_llm_logs_returns_array_for_known_job() {
|
|
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("llm-logs-array@example.com")
|
|
.await;
|
|
|
|
// Insert a test synthesis with a known job_id directly into the database.
|
|
// The `insert_test_synthesis` helper uses a random job_id internally; we
|
|
// need to insert our own to control the job_id used for the log lookup.
|
|
let job_id = uuid::Uuid::new_v4();
|
|
let sections = serde_json::json!([{
|
|
"title": "AI News",
|
|
"items": [{"title": "Article 1", "url": "https://example.com/1", "summary": "Summary"}]
|
|
}]);
|
|
let synthesis_id: (uuid::Uuid,) = sqlx::query_as(
|
|
"INSERT INTO syntheses (user_id, week, sections, status, job_id)
|
|
VALUES ($1, $2, $3, 'completed', $4)
|
|
RETURNING id",
|
|
)
|
|
.bind(user_id)
|
|
.bind("2026-W13")
|
|
.bind(§ions)
|
|
.bind(job_id)
|
|
.fetch_one(&app.pool)
|
|
.await
|
|
.expect("Failed to insert test synthesis");
|
|
let _ = synthesis_id; // verify it was inserted; we only need the job_id for the request
|
|
|
|
let (status, body) = app
|
|
.get_with_session(&format!("/api/v1/llm-logs/{}", job_id), &session)
|
|
.await;
|
|
|
|
assert_eq!(
|
|
status,
|
|
StatusCode::OK,
|
|
"GET /llm-logs/:job_id for a known synthesis should return 200"
|
|
);
|
|
assert!(
|
|
body.as_array().is_some(),
|
|
"Response should be a JSON array, got: {}", body
|
|
);
|
|
// No LLM calls were made for this synthesis (inserted directly), so the
|
|
// array is empty — but the important thing is it's a valid array.
|
|
assert!(
|
|
body.as_array().unwrap().is_empty(),
|
|
"Log array should be empty for a synthesis with no recorded LLM calls"
|
|
);
|
|
}
|