Spaces:
Paused
Paused
Download api/tests/preview.rs from stremio-db/a: direct link, hf CLI and curl.
- Browser
- Download file 31.8 kB
-
https://huggingface.co/spaces/stremio-db/a/resolve/main/api/tests/preview.rs
- Command line
-
hf download hf://spaces/stremio-db/a/api/tests/preview.rs
-
curl -L -o preview.rs https://huggingface.co/spaces/stremio-db/a/resolve/main/api/tests/preview.rs
31.8 kB
| mod common; | |
| use axum::body::Body; | |
| use axum::http::{Request, StatusCode}; | |
| use http_body_util::BodyExt; | |
| use serde_json; | |
| use tower::ServiceExt; | |
| fn authed_get(uri: &str, token: &str) -> Request<Body> { | |
| Request::builder() | |
| .uri(uri) | |
| .header("authorization", format!("Bearer {token}")) | |
| .body(Body::empty()) | |
| .unwrap() | |
| } | |
| async fn preview_requires_auth() { | |
| let (app, _state) = common::setup_test_app().await; | |
| let req = Request::builder() | |
| .uri("/api/admin/preview/poster") | |
| .body(Body::empty()) | |
| .unwrap(); | |
| let res = app.oneshot(req).await.unwrap(); | |
| assert_eq!(res.status(), StatusCode::UNAUTHORIZED); | |
| } | |
| async fn preview_returns_jpeg_with_defaults() { | |
| let (app, _state) = common::setup_test_app().await; | |
| let token = common::setup_admin(&app).await; | |
| let res = app.clone().oneshot(authed_get("/api/admin/preview/poster", &token)).await.unwrap(); | |
| assert_eq!(res.status(), StatusCode::OK); | |
| assert_eq!( | |
| res.headers().get("content-type").unwrap(), | |
| "image/jpeg" | |
| ); | |
| assert_eq!( | |
| res.headers().get("cache-control").unwrap(), | |
| "public, max-age=60" | |
| ); | |
| let body = res.into_body().collect().await.unwrap().to_bytes(); | |
| assert!(body.len() > 100, "JPEG should have substantial content"); | |
| // JPEG magic bytes | |
| assert_eq!(body[0], 0xFF); | |
| assert_eq!(body[1], 0xD8); | |
| } | |
| async fn preview_respects_ratings_limit() { | |
| let (app, _state) = common::setup_test_app().await; | |
| let token = common::setup_admin(&app).await; | |
| // Request with limit=1 — should produce a smaller image (fewer badges) | |
| let res_small = app.clone().oneshot(authed_get("/api/admin/preview/poster?ratings_limit=1", &token)).await.unwrap(); | |
| assert_eq!(res_small.status(), StatusCode::OK); | |
| let body_small = res_small.into_body().collect().await.unwrap().to_bytes(); | |
| // Request with limit=10 (show all 10 badges) | |
| let res_all = app.clone().oneshot(authed_get("/api/admin/preview/poster?ratings_limit=10", &token)).await.unwrap(); | |
| assert_eq!(res_all.status(), StatusCode::OK); | |
| let body_all = res_all.into_body().collect().await.unwrap().to_bytes(); | |
| // Both should be valid JPEGs | |
| assert_eq!(body_small[0], 0xFF); | |
| assert_eq!(body_all[0], 0xFF); | |
| // Both are valid and non-empty | |
| assert!(body_small.len() > 100); | |
| assert!(body_all.len() > 100); | |
| } | |
| async fn preview_respects_ratings_order() { | |
| let (app, _state) = common::setup_test_app().await; | |
| let token = common::setup_admin(&app).await; | |
| // Two different badge selections should produce different images | |
| let res1 = app.clone().oneshot(authed_get("/api/admin/preview/poster?ratings_limit=2&ratings_order=imdb,tmdb", &token)).await.unwrap(); | |
| assert_eq!(res1.status(), StatusCode::OK); | |
| let body1 = res1.into_body().collect().await.unwrap().to_bytes(); | |
| let res2 = app.clone().oneshot(authed_get("/api/admin/preview/poster?ratings_limit=2&ratings_order=rt,mc", &token)).await.unwrap(); | |
| assert_eq!(res2.status(), StatusCode::OK); | |
| let body2 = res2.into_body().collect().await.unwrap().to_bytes(); | |
| // Both valid JPEGs | |
| assert_eq!(body1[0], 0xFF); | |
| assert_eq!(body2[0], 0xFF); | |
| // Different badge selections should produce different image bytes | |
| assert_ne!(body1, body2); | |
| } | |
| async fn preview_respects_ratings_exclude() { | |
| let (app, _state) = common::setup_test_app().await; | |
| let token = common::setup_admin(&app).await; | |
| // Same order+limit, but excluding a source must produce a different image | |
| // (otherwise the cache key would collide and serve the wrong image). | |
| let res1 = app.clone().oneshot(authed_get("/api/admin/preview/poster?ratings_limit=3&ratings_order=imdb,rt,tmdb", &token)).await.unwrap(); | |
| assert_eq!(res1.status(), StatusCode::OK); | |
| let body1 = res1.into_body().collect().await.unwrap().to_bytes(); | |
| let res2 = app.clone().oneshot(authed_get("/api/admin/preview/poster?ratings_limit=3&ratings_order=imdb,rt,tmdb&ratings_exclude=rt", &token)).await.unwrap(); | |
| assert_eq!(res2.status(), StatusCode::OK); | |
| let body2 = res2.into_body().collect().await.unwrap().to_bytes(); | |
| assert_eq!(body1[0], 0xFF); | |
| assert_eq!(body2[0], 0xFF); | |
| assert_ne!(body1, body2, "excluding a source must change the rendered preview"); | |
| } | |
| async fn preview_rejects_invalid_ratings_exclude() { | |
| let (app, _state) = common::setup_test_app().await; | |
| let token = common::setup_admin(&app).await; | |
| let res = app.clone().oneshot(authed_get("/api/admin/preview/poster?ratings_exclude=bogus_source", &token)).await.unwrap(); | |
| assert_eq!(res.status(), StatusCode::BAD_REQUEST); | |
| } | |
| async fn preview_with_empty_order_still_works() { | |
| let (app, _state) = common::setup_test_app().await; | |
| let token = common::setup_admin(&app).await; | |
| let res = app.clone().oneshot(authed_get("/api/admin/preview/poster?ratings_order=&ratings_limit=3", &token)).await.unwrap(); | |
| assert_eq!(res.status(), StatusCode::OK); | |
| assert_eq!(res.headers().get("content-type").unwrap(), "image/jpeg"); | |
| } | |
| async fn preview_cache_returns_identical_bytes_for_same_params() { | |
| let (app, _state) = common::setup_test_app().await; | |
| let token = common::setup_admin(&app).await; | |
| let uri = "/api/admin/preview/poster?ratings_limit=2&ratings_order=imdb,tmdb"; | |
| let res1 = app.clone().oneshot(authed_get(uri, &token)).await.unwrap(); | |
| assert_eq!(res1.status(), StatusCode::OK); | |
| let body1 = res1.into_body().collect().await.unwrap().to_bytes(); | |
| let res2 = app.clone().oneshot(authed_get(uri, &token)).await.unwrap(); | |
| assert_eq!(res2.status(), StatusCode::OK); | |
| let body2 = res2.into_body().collect().await.unwrap().to_bytes(); | |
| // Second request should return identical bytes from cache | |
| assert_eq!(body1, body2); | |
| } | |
| async fn preview_cache_differs_for_different_params() { | |
| let (app, _state) = common::setup_test_app().await; | |
| let token = common::setup_admin(&app).await; | |
| let res1 = app.clone().oneshot(authed_get("/api/admin/preview/poster?ratings_limit=1&ratings_order=imdb", &token)).await.unwrap(); | |
| let body1 = res1.into_body().collect().await.unwrap().to_bytes(); | |
| let res2 = app.clone().oneshot(authed_get("/api/admin/preview/poster?ratings_limit=1&ratings_order=rt", &token)).await.unwrap(); | |
| let body2 = res2.into_body().collect().await.unwrap().to_bytes(); | |
| // Different rating params should produce different images (different cache keys) | |
| assert_ne!(body1, body2); | |
| } | |
| async fn preview_backdrop_edge_inset_shifts_only_active_axis() { | |
| let (app, _state) = common::setup_test_app().await; | |
| let token = common::setup_admin(&app).await; | |
| let fetch = |q: &str| { | |
| let app = app.clone(); | |
| let token = token.clone(); | |
| let uri = format!("/api/admin/preview/backdrop?{q}"); | |
| async move { | |
| let res = app.oneshot(authed_get(&uri, &token)).await.unwrap(); | |
| assert_eq!(res.status(), StatusCode::OK); | |
| res.into_body().collect().await.unwrap().to_bytes() | |
| } | |
| }; | |
| // Top-right anchors to both edges → both insets change the rendered image. | |
| let base = fetch("position=tr").await; | |
| assert_ne!(base, fetch("position=tr&edge_inset_x=12").await); | |
| assert_ne!(base, fetch("position=tr&edge_inset_y=8").await); | |
| // Top-center is horizontally centered → the horizontal inset is a no-op and | |
| // must reuse the same cache key / image, while the vertical inset applies. | |
| let tc = fetch("position=tc").await; | |
| assert_eq!(tc, fetch("position=tc&edge_inset_x=12").await); | |
| assert_ne!(tc, fetch("position=tc&edge_inset_y=8").await); | |
| } | |
| async fn preview_cache_populates_entry_count() { | |
| let (app, state) = common::setup_test_app().await; | |
| let token = common::setup_admin(&app).await; | |
| assert_eq!(state.preview_cache.entry_count(), 0); | |
| let res = app.clone().oneshot(authed_get("/api/admin/preview/poster?ratings_limit=2&ratings_order=imdb,rt", &token)).await.unwrap(); | |
| assert_eq!(res.status(), StatusCode::OK); | |
| // Force pending tasks to run so moka registers the insert | |
| state.preview_cache.run_pending_tasks().await; | |
| assert_eq!(state.preview_cache.entry_count(), 1); | |
| } | |
| async fn preview_cache_survives_settings_update() { | |
| let (app, state) = common::setup_test_app().await; | |
| let token = common::setup_admin(&app).await; | |
| // Warm the preview cache | |
| let res = app.clone().oneshot(authed_get("/api/admin/preview/poster?ratings_limit=3", &token)).await.unwrap(); | |
| assert_eq!(res.status(), StatusCode::OK); | |
| state.preview_cache.run_pending_tasks().await; | |
| assert!(state.preview_cache.entry_count() > 0, "cache should be populated"); | |
| // Update settings — cache keys encode the config, so no invalidation needed | |
| let req = Request::builder() | |
| .method("PUT") | |
| .uri("/api/admin/settings") | |
| .header("content-type", "application/json") | |
| .header("authorization", format!("Bearer {token}")) | |
| .body(Body::from( | |
| serde_json::json!({ | |
| "image_source": "t", | |
| "ratings_limit": 5, | |
| "ratings_order": "imdb,rt,mc,tmdb,trakt,mal,lb,rta" | |
| }) | |
| .to_string(), | |
| )) | |
| .unwrap(); | |
| let res = app.clone().oneshot(req).await.unwrap(); | |
| assert_eq!(res.status(), StatusCode::OK); | |
| // Cache should still have the original entry — different settings = different key | |
| state.preview_cache.run_pending_tasks().await; | |
| assert_eq!(state.preview_cache.entry_count(), 1, "existing cache entry should survive settings update"); | |
| } | |
| async fn preview_serves_from_filesystem_after_memory_eviction() { | |
| let (app, state) = common::setup_test_app().await; | |
| let token = common::setup_admin(&app).await; | |
| let uri = "/api/admin/preview/poster?ratings_limit=2&ratings_order=imdb,rt"; | |
| // First request — renders and writes to both memory + filesystem | |
| let res = app.clone().oneshot(authed_get(uri, &token)).await.unwrap(); | |
| assert_eq!(res.status(), StatusCode::OK); | |
| let body1 = res.into_body().collect().await.unwrap().to_bytes(); | |
| // Evict from memory cache to simulate TTL expiry | |
| state.preview_cache.invalidate_all(); | |
| state.preview_cache.run_pending_tasks().await; | |
| assert_eq!(state.preview_cache.entry_count(), 0); | |
| // Second request — should serve from filesystem, not re-render | |
| let res = app.clone().oneshot(authed_get(uri, &token)).await.unwrap(); | |
| assert_eq!(res.status(), StatusCode::OK); | |
| let body2 = res.into_body().collect().await.unwrap().to_bytes(); | |
| // Should return identical bytes from filesystem | |
| assert_eq!(body1, body2); | |
| // Memory cache should be re-populated from filesystem | |
| state.preview_cache.run_pending_tasks().await; | |
| assert_eq!(state.preview_cache.entry_count(), 1); | |
| } | |
| async fn preview_accessible_via_self_serve_auth() { | |
| let (app, _state) = common::setup_test_app().await; | |
| let api_key_token = common::setup_api_key_session(&app).await; | |
| let req = Request::builder() | |
| .uri("/api/key/me/preview/poster") | |
| .header("authorization", format!("Bearer {api_key_token}")) | |
| .body(Body::empty()) | |
| .unwrap(); | |
| let res = app.oneshot(req).await.unwrap(); | |
| assert_eq!(res.status(), StatusCode::OK); | |
| assert_eq!(res.headers().get("content-type").unwrap(), "image/jpeg"); | |
| } | |
| async fn preview_respects_poster_position() { | |
| let (app, _state) = common::setup_test_app().await; | |
| let token = common::setup_admin(&app).await; | |
| let res = app.clone().oneshot(authed_get("/api/admin/preview/poster?position=l", &token)).await.unwrap(); | |
| assert_eq!(res.status(), StatusCode::OK); | |
| assert_eq!(res.headers().get("content-type").unwrap(), "image/jpeg"); | |
| let body = res.into_body().collect().await.unwrap().to_bytes(); | |
| assert!(body.len() > 100); | |
| assert_eq!(body[0], 0xFF); | |
| assert_eq!(body[1], 0xD8); | |
| } | |
| async fn preview_cache_differs_for_different_positions() { | |
| let (app, _state) = common::setup_test_app().await; | |
| let token = common::setup_admin(&app).await; | |
| let res1 = app.clone().oneshot(authed_get("/api/admin/preview/poster?ratings_limit=2&ratings_order=imdb,rt&position=bc", &token)).await.unwrap(); | |
| let body1 = res1.into_body().collect().await.unwrap().to_bytes(); | |
| let res2 = app.clone().oneshot(authed_get("/api/admin/preview/poster?ratings_limit=2&ratings_order=imdb,rt&position=l", &token)).await.unwrap(); | |
| let body2 = res2.into_body().collect().await.unwrap().to_bytes(); | |
| assert_eq!(body1[0], 0xFF); | |
| assert_eq!(body2[0], 0xFF); | |
| assert_ne!(body1, body2); | |
| } | |
| /// Verify that the preview endpoint actually renders badges in the correct | |
| /// region of the image by checking pixel data, not just HTTP status codes. | |
| async fn preview_position_places_badges_in_correct_region() { | |
| let (app, _state) = common::setup_test_app().await; | |
| let token = common::setup_admin(&app).await; | |
| let params = "ratings_limit=3&ratings_order=imdb,tmdb,rt"; | |
| let res_tc = app.clone().oneshot(authed_get( | |
| &format!("/api/admin/preview/poster?{params}&position=tc"), &token, | |
| )).await.unwrap(); | |
| assert_eq!(res_tc.status(), StatusCode::OK); | |
| let body_tc = res_tc.into_body().collect().await.unwrap().to_bytes(); | |
| let res_bc = app.clone().oneshot(authed_get( | |
| &format!("/api/admin/preview/poster?{params}&position=bc"), &token, | |
| )).await.unwrap(); | |
| assert_eq!(res_bc.status(), StatusCode::OK); | |
| let body_bc = res_bc.into_body().collect().await.unwrap().to_bytes(); | |
| // The sample poster is a dark gray gradient (26–42 per channel). | |
| // Badge pixels are significantly brighter. Compute the y-centroid of | |
| // bright pixels to verify badge placement. | |
| fn badge_y_centroid(jpeg: &[u8]) -> f64 { | |
| let img = image::load_from_memory(jpeg).unwrap().to_rgba8(); | |
| let h = img.height() as f64; | |
| let (mut sum_y, mut count) = (0u64, 0u64); | |
| for (_, y, px) in img.enumerate_pixels() { | |
| if px[0].max(px[1]).max(px[2]) > 80 { | |
| sum_y += y as u64; | |
| count += 1; | |
| } | |
| } | |
| assert!(count > 0, "no badge pixels found"); | |
| sum_y as f64 / count as f64 / h | |
| } | |
| let tc_cy = badge_y_centroid(&body_tc); | |
| let bc_cy = badge_y_centroid(&body_bc); | |
| assert!(tc_cy < 0.33, | |
| "TopCenter: badge y-centroid {tc_cy:.2} should be in top third"); | |
| assert!(bc_cy > 0.67, | |
| "BottomCenter: badge y-centroid {bc_cy:.2} should be in bottom third"); | |
| } | |
| async fn preview_rejects_invalid_poster_position() { | |
| let (app, _state) = common::setup_test_app().await; | |
| let token = common::setup_admin(&app).await; | |
| let res = app.clone().oneshot(authed_get("/api/admin/preview/poster?position=invalid", &token)).await.unwrap(); | |
| assert_eq!(res.status(), StatusCode::BAD_REQUEST); | |
| } | |
| // --- Badge style tests --- | |
| async fn preview_accepts_all_badge_styles() { | |
| let (app, _state) = common::setup_test_app().await; | |
| let token = common::setup_admin(&app).await; | |
| for style in ["h", "v", "d"] { | |
| let res = app.clone().oneshot(authed_get( | |
| &format!("/api/admin/preview/poster?badge_style={style}"), &token | |
| )).await.unwrap(); | |
| assert_eq!(res.status(), StatusCode::OK, "badge_style={style} should be accepted"); | |
| assert_eq!(res.headers().get("content-type").unwrap(), "image/jpeg"); | |
| } | |
| } | |
| async fn preview_rejects_invalid_badge_style() { | |
| let (app, _state) = common::setup_test_app().await; | |
| let token = common::setup_admin(&app).await; | |
| let res = app.clone().oneshot(authed_get("/api/admin/preview/poster?badge_style=z", &token)).await.unwrap(); | |
| assert_eq!(res.status(), StatusCode::BAD_REQUEST); | |
| } | |
| async fn preview_badge_style_produces_different_images() { | |
| let (app, _state) = common::setup_test_app().await; | |
| let token = common::setup_admin(&app).await; | |
| let res_h = app.clone().oneshot(authed_get("/api/admin/preview/poster?ratings_limit=3&badge_style=h", &token)).await.unwrap(); | |
| let body_h = res_h.into_body().collect().await.unwrap().to_bytes(); | |
| let res_v = app.clone().oneshot(authed_get("/api/admin/preview/poster?ratings_limit=3&badge_style=v", &token)).await.unwrap(); | |
| let body_v = res_v.into_body().collect().await.unwrap().to_bytes(); | |
| assert_eq!(body_h[0], 0xFF); | |
| assert_eq!(body_v[0], 0xFF); | |
| assert_ne!(body_h, body_v, "horizontal and vertical badge styles should produce different images"); | |
| } | |
| // --- Label style tests --- | |
| async fn preview_accepts_all_label_styles() { | |
| let (app, _state) = common::setup_test_app().await; | |
| let token = common::setup_admin(&app).await; | |
| for style in ["t", "i", "o"] { | |
| let res = app.clone().oneshot(authed_get( | |
| &format!("/api/admin/preview/poster?label_style={style}"), &token | |
| )).await.unwrap(); | |
| assert_eq!(res.status(), StatusCode::OK, "label_style={style} should be accepted"); | |
| } | |
| } | |
| async fn preview_rejects_invalid_label_style() { | |
| let (app, _state) = common::setup_test_app().await; | |
| let token = common::setup_admin(&app).await; | |
| let res = app.clone().oneshot(authed_get("/api/admin/preview/poster?label_style=x", &token)).await.unwrap(); | |
| assert_eq!(res.status(), StatusCode::BAD_REQUEST); | |
| } | |
| async fn preview_label_style_produces_different_images() { | |
| let (app, _state) = common::setup_test_app().await; | |
| let token = common::setup_admin(&app).await; | |
| let res_text = app.clone().oneshot(authed_get("/api/admin/preview/poster?ratings_limit=3&label_style=t", &token)).await.unwrap(); | |
| let body_text = res_text.into_body().collect().await.unwrap().to_bytes(); | |
| let res_icon = app.clone().oneshot(authed_get("/api/admin/preview/poster?ratings_limit=3&label_style=i", &token)).await.unwrap(); | |
| let body_icon = res_icon.into_body().collect().await.unwrap().to_bytes(); | |
| assert_eq!(body_text[0], 0xFF); | |
| assert_eq!(body_icon[0], 0xFF); | |
| assert_ne!(body_text, body_icon, "text and icon label styles should produce different images"); | |
| } | |
| // --- Badge size tests --- | |
| async fn preview_accepts_all_badge_sizes() { | |
| let (app, _state) = common::setup_test_app().await; | |
| let token = common::setup_admin(&app).await; | |
| for size in ["xs", "s", "m", "l", "xl"] { | |
| let res = app.clone().oneshot(authed_get( | |
| &format!("/api/admin/preview/poster?badge_size={size}"), &token | |
| )).await.unwrap(); | |
| assert_eq!(res.status(), StatusCode::OK, "badge_size={size} should be accepted"); | |
| } | |
| } | |
| async fn preview_rejects_invalid_badge_size() { | |
| let (app, _state) = common::setup_test_app().await; | |
| let token = common::setup_admin(&app).await; | |
| let res = app.clone().oneshot(authed_get("/api/admin/preview/poster?badge_size=xxl", &token)).await.unwrap(); | |
| assert_eq!(res.status(), StatusCode::BAD_REQUEST); | |
| } | |
| async fn preview_badge_size_produces_different_images() { | |
| let (app, _state) = common::setup_test_app().await; | |
| let token = common::setup_admin(&app).await; | |
| let res_xs = app.clone().oneshot(authed_get("/api/admin/preview/poster?ratings_limit=3&badge_size=xs", &token)).await.unwrap(); | |
| let body_xs = res_xs.into_body().collect().await.unwrap().to_bytes(); | |
| let res_xl = app.clone().oneshot(authed_get("/api/admin/preview/poster?ratings_limit=3&badge_size=xl", &token)).await.unwrap(); | |
| let body_xl = res_xl.into_body().collect().await.unwrap().to_bytes(); | |
| assert_eq!(body_xs[0], 0xFF); | |
| assert_eq!(body_xl[0], 0xFF); | |
| assert_ne!(body_xs, body_xl, "xs and xl badge sizes should produce different images"); | |
| } | |
| // --- Badge direction tests --- | |
| async fn preview_accepts_all_badge_directions() { | |
| let (app, _state) = common::setup_test_app().await; | |
| let token = common::setup_admin(&app).await; | |
| for dir in ["d", "h", "v"] { | |
| let res = app.clone().oneshot(authed_get( | |
| &format!("/api/admin/preview/poster?badge_direction={dir}"), &token | |
| )).await.unwrap(); | |
| assert_eq!(res.status(), StatusCode::OK, "badge_direction={dir} should be accepted"); | |
| } | |
| } | |
| async fn preview_rejects_invalid_badge_direction() { | |
| let (app, _state) = common::setup_test_app().await; | |
| let token = common::setup_admin(&app).await; | |
| let res = app.clone().oneshot(authed_get("/api/admin/preview/poster?badge_direction=x", &token)).await.unwrap(); | |
| assert_eq!(res.status(), StatusCode::BAD_REQUEST); | |
| } | |
| // --- Logo preview tests --- | |
| async fn preview_logo_returns_png() { | |
| let (app, _state) = common::setup_test_app().await; | |
| let token = common::setup_admin(&app).await; | |
| let res = app.clone().oneshot(authed_get("/api/admin/preview/logo", &token)).await.unwrap(); | |
| assert_eq!(res.status(), StatusCode::OK); | |
| assert_eq!(res.headers().get("content-type").unwrap(), "image/png"); | |
| let body = res.into_body().collect().await.unwrap().to_bytes(); | |
| assert_eq!(&body[..4], &[0x89, b'P', b'N', b'G']); | |
| } | |
| async fn preview_logo_accepts_badge_style_and_size() { | |
| let (app, _state) = common::setup_test_app().await; | |
| let token = common::setup_admin(&app).await; | |
| let res = app.clone().oneshot(authed_get( | |
| "/api/admin/preview/logo?badge_style=h&badge_size=l&label_style=t", &token | |
| )).await.unwrap(); | |
| assert_eq!(res.status(), StatusCode::OK); | |
| } | |
| // --- Backdrop preview tests --- | |
| async fn preview_backdrop_returns_jpeg() { | |
| let (app, _state) = common::setup_test_app().await; | |
| let token = common::setup_admin(&app).await; | |
| let res = app.clone().oneshot(authed_get("/api/admin/preview/backdrop", &token)).await.unwrap(); | |
| assert_eq!(res.status(), StatusCode::OK); | |
| assert_eq!(res.headers().get("content-type").unwrap(), "image/jpeg"); | |
| let body = res.into_body().collect().await.unwrap().to_bytes(); | |
| assert_eq!(body[0], 0xFF); | |
| assert_eq!(body[1], 0xD8); | |
| } | |
| async fn preview_backdrop_accepts_badge_style_and_size() { | |
| let (app, _state) = common::setup_test_app().await; | |
| let token = common::setup_admin(&app).await; | |
| let res = app.clone().oneshot(authed_get( | |
| "/api/admin/preview/backdrop?badge_style=v&badge_size=xs&label_style=o", &token | |
| )).await.unwrap(); | |
| assert_eq!(res.status(), StatusCode::OK); | |
| } | |
| // --- Combined param tests --- | |
| async fn preview_all_new_params_combined() { | |
| let (app, _state) = common::setup_test_app().await; | |
| let token = common::setup_admin(&app).await; | |
| let res = app.clone().oneshot(authed_get( | |
| "/api/admin/preview/poster?ratings_limit=4&ratings_order=imdb,tmdb,rt,mc&position=tl&badge_style=v&label_style=i&badge_direction=v&badge_size=l", | |
| &token | |
| )).await.unwrap(); | |
| assert_eq!(res.status(), StatusCode::OK); | |
| let body = res.into_body().collect().await.unwrap().to_bytes(); | |
| assert_eq!(body[0], 0xFF); | |
| assert!(body.len() > 100); | |
| } | |
| async fn preview_self_serve_logo() { | |
| let (app, _state) = common::setup_test_app().await; | |
| let api_key_token = common::setup_api_key_session(&app).await; | |
| let req = Request::builder() | |
| .uri("/api/key/me/preview/logo?badge_style=h&badge_size=s") | |
| .header("authorization", format!("Bearer {api_key_token}")) | |
| .body(Body::empty()) | |
| .unwrap(); | |
| let res = app.oneshot(req).await.unwrap(); | |
| assert_eq!(res.status(), StatusCode::OK); | |
| assert_eq!(res.headers().get("content-type").unwrap(), "image/png"); | |
| } | |
| async fn preview_self_serve_backdrop() { | |
| let (app, _state) = common::setup_test_app().await; | |
| let api_key_token = common::setup_api_key_session(&app).await; | |
| let req = Request::builder() | |
| .uri("/api/key/me/preview/backdrop?badge_size=xl&label_style=t") | |
| .header("authorization", format!("Bearer {api_key_token}")) | |
| .body(Body::empty()) | |
| .unwrap(); | |
| let res = app.oneshot(req).await.unwrap(); | |
| assert_eq!(res.status(), StatusCode::OK); | |
| assert_eq!(res.headers().get("content-type").unwrap(), "image/jpeg"); | |
| } | |
| // --- Episode preview tests --- | |
| async fn preview_episode_returns_jpeg() { | |
| let (app, _state) = common::setup_test_app().await; | |
| let token = common::setup_admin(&app).await; | |
| let res = app.clone().oneshot(authed_get("/api/admin/preview/episode", &token)).await.unwrap(); | |
| assert_eq!(res.status(), StatusCode::OK); | |
| assert_eq!(res.headers().get("content-type").unwrap(), "image/jpeg"); | |
| let body = res.into_body().collect().await.unwrap().to_bytes(); | |
| assert!(body.len() > 100, "JPEG should have substantial content"); | |
| assert_eq!(body[0], 0xFF); | |
| assert_eq!(body[1], 0xD8); | |
| } | |
| async fn preview_episode_accepts_all_settings() { | |
| let (app, _state) = common::setup_test_app().await; | |
| let token = common::setup_admin(&app).await; | |
| let res = app.clone().oneshot(authed_get( | |
| "/api/admin/preview/episode?ratings_limit=3&ratings_order=imdb,tmdb,rt&position=tl&badge_style=h&label_style=i&badge_direction=h&badge_size=m&blur=true", | |
| &token | |
| )).await.unwrap(); | |
| assert_eq!(res.status(), StatusCode::OK); | |
| let body = res.into_body().collect().await.unwrap().to_bytes(); | |
| assert_eq!(body[0], 0xFF); | |
| assert!(body.len() > 100); | |
| } | |
| async fn preview_episode_blur_produces_different_image() { | |
| let (app, _state) = common::setup_test_app().await; | |
| let token = common::setup_admin(&app).await; | |
| let params = "ratings_limit=2&ratings_order=imdb,tmdb"; | |
| let res_noblur = app.clone().oneshot(authed_get( | |
| &format!("/api/admin/preview/episode?{params}"), &token | |
| )).await.unwrap(); | |
| let body_noblur = res_noblur.into_body().collect().await.unwrap().to_bytes(); | |
| let res_blur = app.clone().oneshot(authed_get( | |
| &format!("/api/admin/preview/episode?{params}&blur=true"), &token | |
| )).await.unwrap(); | |
| let body_blur = res_blur.into_body().collect().await.unwrap().to_bytes(); | |
| assert_eq!(body_noblur[0], 0xFF); | |
| assert_eq!(body_blur[0], 0xFF); | |
| assert_ne!(body_noblur, body_blur, "blur should produce a different image"); | |
| } | |
| async fn preview_episode_cache_returns_identical_bytes() { | |
| let (app, _state) = common::setup_test_app().await; | |
| let token = common::setup_admin(&app).await; | |
| let uri = "/api/admin/preview/episode?ratings_limit=2&ratings_order=imdb,tmdb"; | |
| let res1 = app.clone().oneshot(authed_get(uri, &token)).await.unwrap(); | |
| let body1 = res1.into_body().collect().await.unwrap().to_bytes(); | |
| let res2 = app.clone().oneshot(authed_get(uri, &token)).await.unwrap(); | |
| let body2 = res2.into_body().collect().await.unwrap().to_bytes(); | |
| assert_eq!(body1, body2); | |
| } | |
| async fn preview_episode_position_produces_different_images() { | |
| let (app, _state) = common::setup_test_app().await; | |
| let token = common::setup_admin(&app).await; | |
| let params = "ratings_limit=3&ratings_order=imdb,tmdb,rt"; | |
| let res_tr = app.clone().oneshot(authed_get( | |
| &format!("/api/admin/preview/episode?{params}&position=tr"), &token | |
| )).await.unwrap(); | |
| let body_tr = res_tr.into_body().collect().await.unwrap().to_bytes(); | |
| let res_bc = app.clone().oneshot(authed_get( | |
| &format!("/api/admin/preview/episode?{params}&position=bc"), &token | |
| )).await.unwrap(); | |
| let body_bc = res_bc.into_body().collect().await.unwrap().to_bytes(); | |
| assert_eq!(body_tr[0], 0xFF); | |
| assert_eq!(body_bc[0], 0xFF); | |
| assert_ne!(body_tr, body_bc, "different positions should produce different images"); | |
| } | |
| async fn preview_episode_self_serve() { | |
| let (app, _state) = common::setup_test_app().await; | |
| let api_key_token = common::setup_api_key_session(&app).await; | |
| let req = Request::builder() | |
| .uri("/api/key/me/preview/episode?badge_size=s&blur=true") | |
| .header("authorization", format!("Bearer {api_key_token}")) | |
| .body(Body::empty()) | |
| .unwrap(); | |
| let res = app.oneshot(req).await.unwrap(); | |
| assert_eq!(res.status(), StatusCode::OK); | |
| assert_eq!(res.headers().get("content-type").unwrap(), "image/jpeg"); | |
| } | |
| async fn preview_episode_accepts_all_badge_styles() { | |
| let (app, _state) = common::setup_test_app().await; | |
| let token = common::setup_admin(&app).await; | |
| for style in ["h", "v", "d"] { | |
| let res = app.clone().oneshot(authed_get( | |
| &format!("/api/admin/preview/episode?badge_style={style}"), &token | |
| )).await.unwrap(); | |
| assert_eq!(res.status(), StatusCode::OK, "episode badge_style={style} should be accepted"); | |
| } | |
| } | |
| async fn preview_episode_rejects_invalid_badge_style() { | |
| let (app, _state) = common::setup_test_app().await; | |
| let token = common::setup_admin(&app).await; | |
| let res = app.clone().oneshot(authed_get("/api/admin/preview/episode?badge_style=z", &token)).await.unwrap(); | |
| assert_eq!(res.status(), StatusCode::BAD_REQUEST); | |
| } | |
| async fn preview_episode_accepts_all_badge_sizes() { | |
| let (app, _state) = common::setup_test_app().await; | |
| let token = common::setup_admin(&app).await; | |
| for size in ["xs", "s", "m", "l", "xl"] { | |
| let res = app.clone().oneshot(authed_get( | |
| &format!("/api/admin/preview/episode?badge_size={size}"), &token | |
| )).await.unwrap(); | |
| assert_eq!(res.status(), StatusCode::OK, "episode badge_size={size} should be accepted"); | |
| } | |
| } | |
| async fn preview_episode_accepts_all_badge_directions() { | |
| let (app, _state) = common::setup_test_app().await; | |
| let token = common::setup_admin(&app).await; | |
| for dir in ["d", "h", "v"] { | |
| let res = app.clone().oneshot(authed_get( | |
| &format!("/api/admin/preview/episode?badge_direction={dir}"), &token | |
| )).await.unwrap(); | |
| assert_eq!(res.status(), StatusCode::OK, "episode badge_direction={dir} should be accepted"); | |
| } | |
| } | |
| async fn preview_episode_badge_style_produces_different_images() { | |
| let (app, _state) = common::setup_test_app().await; | |
| let token = common::setup_admin(&app).await; | |
| let res_h = app.clone().oneshot(authed_get("/api/admin/preview/episode?ratings_limit=3&badge_style=h", &token)).await.unwrap(); | |
| let body_h = res_h.into_body().collect().await.unwrap().to_bytes(); | |
| let res_v = app.clone().oneshot(authed_get("/api/admin/preview/episode?ratings_limit=3&badge_style=v", &token)).await.unwrap(); | |
| let body_v = res_v.into_body().collect().await.unwrap().to_bytes(); | |
| assert_eq!(body_h[0], 0xFF); | |
| assert_eq!(body_v[0], 0xFF); | |
| assert_ne!(body_h, body_v, "horizontal and vertical episode badge styles should differ"); | |
| } | |
| async fn preview_episode_rejects_invalid_position() { | |
| let (app, _state) = common::setup_test_app().await; | |
| let token = common::setup_admin(&app).await; | |
| let res = app.clone().oneshot(authed_get("/api/admin/preview/episode?position=invalid", &token)).await.unwrap(); | |
| assert_eq!(res.status(), StatusCode::BAD_REQUEST); | |
| } | |
| async fn preview_episode_requires_auth() { | |
| let (app, _state) = common::setup_test_app().await; | |
| let req = Request::builder() | |
| .uri("/api/admin/preview/episode") | |
| .body(Body::empty()) | |
| .unwrap(); | |
| let res = app.oneshot(req).await.unwrap(); | |
| assert_eq!(res.status(), StatusCode::UNAUTHORIZED); | |
| } | |