a / api /tests /free_api_key.rs
PNRxA
feat: reflect server defaults in the free-key "Try it out" form
7e7d412
Raw History Blame Contribute Delete
14.7 kB
mod common;
use axum::body::Body;
use axum::http::{Request, StatusCode};
use common::TestAppOptions;
use http_body_util::BodyExt;
use tower::ServiceExt;
fn json_body(json: serde_json::Value) -> Body {
Body::from(json.to_string())
}
/// Helper: enable or disable the free API key setting via admin API.
async fn set_free_api_key_enabled(app: &axum::Router, token: &str, enabled: bool) {
let req = Request::builder()
.method("PUT")
.uri("/api/admin/settings")
.header("content-type", "application/json")
.header("authorization", format!("Bearer {token}"))
.body(json_body(serde_json::json!({
"image_source": "t",
"free_api_key_enabled": enabled
})))
.unwrap();
let res = app.clone().oneshot(req).await.unwrap();
assert_eq!(res.status(), StatusCode::OK);
}
// --- GET /api/admin/settings includes free_api_key_enabled ---
#[tokio::test]
async fn settings_defaults_free_api_key_disabled() {
let (app, _state) = common::setup_test_app().await;
let token = common::setup_admin(&app).await;
let req = Request::builder()
.uri("/api/admin/settings")
.header("authorization", format!("Bearer {token}"))
.body(Body::empty())
.unwrap();
let res = app.oneshot(req).await.unwrap();
assert_eq!(res.status(), StatusCode::OK);
let body = res.into_body().collect().await.unwrap().to_bytes();
let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
assert_eq!(json["free_api_key_enabled"], false);
}
#[tokio::test]
async fn settings_enable_free_api_key_and_read_back() {
let (app, _state) = common::setup_test_app().await;
let token = common::setup_admin(&app).await;
set_free_api_key_enabled(&app, &token, true).await;
let req = Request::builder()
.uri("/api/admin/settings")
.header("authorization", format!("Bearer {token}"))
.body(Body::empty())
.unwrap();
let res = app.oneshot(req).await.unwrap();
assert_eq!(res.status(), StatusCode::OK);
let body = res.into_body().collect().await.unwrap().to_bytes();
let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
assert_eq!(json["free_api_key_enabled"], true);
}
#[tokio::test]
async fn settings_disable_free_api_key_and_read_back() {
let (app, _state) = common::setup_test_app().await;
let token = common::setup_admin(&app).await;
// Enable then disable
set_free_api_key_enabled(&app, &token, true).await;
set_free_api_key_enabled(&app, &token, false).await;
let req = Request::builder()
.uri("/api/admin/settings")
.header("authorization", format!("Bearer {token}"))
.body(Body::empty())
.unwrap();
let res = app.oneshot(req).await.unwrap();
assert_eq!(res.status(), StatusCode::OK);
let body = res.into_body().collect().await.unwrap().to_bytes();
let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
assert_eq!(json["free_api_key_enabled"], false);
}
// --- GET /api/auth/status includes free_api_key_enabled ---
#[tokio::test]
async fn auth_status_includes_free_api_key_enabled_default_false() {
let (app, _state) = common::setup_test_app().await;
let req = Request::builder()
.uri("/api/auth/status")
.body(Body::empty())
.unwrap();
let res = app.oneshot(req).await.unwrap();
assert_eq!(res.status(), StatusCode::OK);
let body = res.into_body().collect().await.unwrap().to_bytes();
let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
assert_eq!(json["free_api_key_enabled"], false);
}
#[tokio::test]
async fn auth_status_reflects_enabled_free_api_key() {
let (app, _state) = common::setup_test_app().await;
let token = common::setup_admin(&app).await;
set_free_api_key_enabled(&app, &token, true).await;
let req = Request::builder()
.uri("/api/auth/status")
.body(Body::empty())
.unwrap();
let res = app.oneshot(req).await.unwrap();
assert_eq!(res.status(), StatusCode::OK);
let body = res.into_body().collect().await.unwrap().to_bytes();
let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
assert_eq!(json["free_api_key_enabled"], true);
}
// --- Poster route with free API key ---
#[tokio::test]
async fn free_key_rejected_when_disabled() {
let (app, _state) = common::setup_test_app().await;
let req = Request::builder()
.uri("/t0-free-rpdb/imdb/poster-default/tt0111161.jpg")
.body(Body::empty())
.unwrap();
let res = app.oneshot(req).await.unwrap();
assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
}
#[tokio::test]
async fn free_key_accepted_when_enabled() {
let (app, _state) = common::setup_test_app().await;
let token = common::setup_admin(&app).await;
set_free_api_key_enabled(&app, &token, true).await;
// The TMDB call will fail since we use a fake API key, but the
// response should NOT be 401 — the free key itself is valid.
let req = Request::builder()
.uri("/t0-free-rpdb/imdb/poster-default/tt0111161.jpg")
.body(Body::empty())
.unwrap();
let res = app.oneshot(req).await.unwrap();
assert_ne!(
res.status(),
StatusCode::UNAUTHORIZED,
"free key should not return 401 when enabled"
);
}
#[tokio::test]
async fn free_key_rejected_after_disabling() {
let (app, _state) = common::setup_test_app().await;
let token = common::setup_admin(&app).await;
// Enable
set_free_api_key_enabled(&app, &token, true).await;
// Verify it works
let req = Request::builder()
.uri("/t0-free-rpdb/imdb/poster-default/tt0111161.jpg")
.body(Body::empty())
.unwrap();
let res = app.clone().oneshot(req).await.unwrap();
assert_ne!(res.status(), StatusCode::UNAUTHORIZED);
// Disable
set_free_api_key_enabled(&app, &token, false).await;
// Should be rejected again
let req = Request::builder()
.uri("/t0-free-rpdb/imdb/poster-default/tt0111161.jpg")
.body(Body::empty())
.unwrap();
let res = app.oneshot(req).await.unwrap();
assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
}
// --- key-login with free API key always fails ---
#[tokio::test]
async fn key_login_rejects_free_api_key() {
let (app, _state) = common::setup_test_app().await;
let token = common::setup_admin(&app).await;
// Enable the free key for poster serving
set_free_api_key_enabled(&app, &token, true).await;
// Attempt to login with the free key — should fail since it has no DB row
let req = Request::builder()
.method("POST")
.uri("/api/auth/key-login")
.header("content-type", "application/json")
.body(json_body(serde_json::json!({"api_key": "t0-free-rpdb"})))
.unwrap();
let res = app.oneshot(req).await.unwrap();
assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
}
// --- Free key does not track last_used ---
#[tokio::test]
async fn free_key_does_not_track_last_used() {
let (app, state) = common::setup_test_app().await;
let token = common::setup_admin(&app).await;
set_free_api_key_enabled(&app, &token, true).await;
let req = Request::builder()
.uri("/t0-free-rpdb/imdb/poster-default/tt0111161.jpg")
.body(Body::empty())
.unwrap();
let _res = app.oneshot(req).await.unwrap();
// pending_last_used should be empty — free key doesn't insert into it
assert!(
state.pending_last_used.is_empty(),
"free API key should not track last_used"
);
}
// --- FREE_KEY_ENABLED env var override ---
#[tokio::test]
async fn env_var_true_forces_free_key_on() {
let (app, _state) = common::setup_test_app_with_options(TestAppOptions {
free_key_enabled: Some(true),
..Default::default()
})
.await;
// Free key should work without any DB toggle
let req = Request::builder()
.uri("/t0-free-rpdb/imdb/poster-default/tt0111161.jpg")
.body(Body::empty())
.unwrap();
let res = app.oneshot(req).await.unwrap();
assert_ne!(
res.status(),
StatusCode::UNAUTHORIZED,
"free key should be accepted when FREE_KEY_ENABLED=true"
);
}
#[tokio::test]
async fn env_var_false_forces_free_key_off() {
let (app, _state) = common::setup_test_app_with_options(TestAppOptions {
free_key_enabled: Some(false),
..Default::default()
})
.await;
let token = common::setup_admin(&app).await;
// Try to enable via DB — should be silently ignored
set_free_api_key_enabled(&app, &token, true).await;
// Free key should still be rejected
let req = Request::builder()
.uri("/t0-free-rpdb/imdb/poster-default/tt0111161.jpg")
.body(Body::empty())
.unwrap();
let res = app.oneshot(req).await.unwrap();
assert_eq!(
res.status(),
StatusCode::UNAUTHORIZED,
"free key should be rejected when FREE_KEY_ENABLED=false"
);
}
#[tokio::test]
async fn env_var_sets_locked_in_settings_response() {
let (app, _state) = common::setup_test_app_with_options(TestAppOptions {
free_key_enabled: Some(true),
..Default::default()
})
.await;
let token = common::setup_admin(&app).await;
let req = Request::builder()
.uri("/api/admin/settings")
.header("authorization", format!("Bearer {token}"))
.body(Body::empty())
.unwrap();
let res = app.oneshot(req).await.unwrap();
assert_eq!(res.status(), StatusCode::OK);
let body = res.into_body().collect().await.unwrap().to_bytes();
let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
assert_eq!(json["free_api_key_enabled"], true);
assert_eq!(json["free_api_key_locked"], true);
}
#[tokio::test]
async fn no_env_var_not_locked_in_settings_response() {
let (app, _state) = common::setup_test_app().await;
let token = common::setup_admin(&app).await;
let req = Request::builder()
.uri("/api/admin/settings")
.header("authorization", format!("Bearer {token}"))
.body(Body::empty())
.unwrap();
let res = app.oneshot(req).await.unwrap();
assert_eq!(res.status(), StatusCode::OK);
let body = res.into_body().collect().await.unwrap().to_bytes();
let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
assert_eq!(json["free_api_key_locked"], false);
}
// --- GET /api/free-key/settings (public, reflects global defaults) ---
#[tokio::test]
async fn free_key_settings_rejected_when_disabled() {
let (app, _state) = common::setup_test_app().await;
let req = Request::builder()
.uri("/api/free-key/settings")
.body(Body::empty())
.unwrap();
let res = app.oneshot(req).await.unwrap();
assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
}
#[tokio::test]
async fn free_key_settings_returns_defaults_when_enabled() {
let (app, _state) = common::setup_test_app().await;
let token = common::setup_admin(&app).await;
set_free_api_key_enabled(&app, &token, true).await;
let req = Request::builder()
.uri("/api/free-key/settings")
.body(Body::empty())
.unwrap();
let res = app.oneshot(req).await.unwrap();
assert_eq!(res.status(), StatusCode::OK);
let body = res.into_body().collect().await.unwrap().to_bytes();
let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
// Default render settings are present and enum-coded the same as the image API.
assert_eq!(json["ratings_order"], "mal,imdb,lb,rt,mc,rta,tmdb,trakt,mdblist,ebert");
assert_eq!(json["ratings_exclude"], "");
assert_eq!(json["image_source"], "t");
assert_eq!(json["lang"], "en");
// Operational flags from the admin response must NOT leak onto the public endpoint.
assert!(json.get("free_api_key_locked").is_none());
assert!(json.get("fanart_available").is_none());
}
#[tokio::test]
async fn free_key_settings_reflects_admin_changes() {
let (app, _state) = common::setup_test_app().await;
let token = common::setup_admin(&app).await;
// Enable the free key and set a custom global order + poster badge style.
let req = Request::builder()
.method("PUT")
.uri("/api/admin/settings")
.header("content-type", "application/json")
.header("authorization", format!("Bearer {token}"))
.body(json_body(serde_json::json!({
"image_source": "t",
"free_api_key_enabled": true,
"ratings_order": "imdb,tmdb",
"ratings_exclude": "rt",
"poster_badge_style": "h",
})))
.unwrap();
let res = app.clone().oneshot(req).await.unwrap();
assert_eq!(res.status(), StatusCode::OK);
// The public endpoint should reflect those values (cache invalidated on update).
let req = Request::builder()
.uri("/api/free-key/settings")
.body(Body::empty())
.unwrap();
let res = app.oneshot(req).await.unwrap();
assert_eq!(res.status(), StatusCode::OK);
let body = res.into_body().collect().await.unwrap().to_bytes();
let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
assert_eq!(json["ratings_order"], "imdb,tmdb");
assert_eq!(json["ratings_exclude"], "rt");
assert_eq!(json["poster_badge_style"], "h");
}
#[tokio::test]
async fn free_key_settings_reflects_env_override() {
let (app, _state) = common::setup_test_app_with_options(TestAppOptions {
free_key_enabled: Some(true),
..Default::default()
})
.await;
// No DB toggle needed — the env override forces the free key on.
let req = Request::builder()
.uri("/api/free-key/settings")
.body(Body::empty())
.unwrap();
let res = app.oneshot(req).await.unwrap();
assert_eq!(res.status(), StatusCode::OK);
}
#[tokio::test]
async fn env_var_auth_status_reflects_override() {
let (app, _state) = common::setup_test_app_with_options(TestAppOptions {
free_key_enabled: Some(true),
..Default::default()
})
.await;
let req = Request::builder()
.uri("/api/auth/status")
.body(Body::empty())
.unwrap();
let res = app.oneshot(req).await.unwrap();
assert_eq!(res.status(), StatusCode::OK);
let body = res.into_body().collect().await.unwrap().to_bytes();
let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
assert_eq!(json["free_api_key_enabled"], true);
}