mod common; use axum::http::StatusCode; use common::{ delete_authed, get, get_authed, patch_json_authed, post_json_authed, put_json_authed, response_body, setup_app, setup_app_with_auth, }; use tower::ServiceExt; #[tokio::test] async fn list_books_empty() { let app = setup_app().await; let resp = app.oneshot(get("/api/v1/books")).await.unwrap(); assert_eq!(resp.status(), StatusCode::OK); let body = response_body(resp).await; let items = body.as_array().expect("array response"); assert!(items.is_empty()); } #[tokio::test] async fn get_book_metadata_not_found() { let app = setup_app().await; let fake_id = uuid::Uuid::now_v7(); let resp = app .oneshot(get(&format!("/api/v1/books/{fake_id}/metadata"))) .await .unwrap(); assert_eq!(resp.status(), StatusCode::NOT_FOUND); } #[tokio::test] async fn list_books_with_filters() { let app = setup_app().await; let resp = app .oneshot(get("/api/v1/books?author=Tolkien&limit=10")) .await .unwrap(); assert_eq!(resp.status(), StatusCode::OK); } #[tokio::test] async fn list_series_empty() { let app = setup_app().await; let resp = app.oneshot(get("/api/v1/books/series")).await.unwrap(); assert_eq!(resp.status(), StatusCode::OK); } #[tokio::test] async fn list_authors_empty() { let app = setup_app().await; let resp = app .oneshot(get("/api/v1/books/authors?offset=0&limit=50")) .await .unwrap(); assert_eq!(resp.status(), StatusCode::OK); } #[tokio::test] async fn reading_progress_nonexistent_book() { let (app, _, _, viewer) = setup_app_with_auth().await; let fake_id = uuid::Uuid::now_v7(); let resp = app .clone() .oneshot(get_authed( &format!("/api/v1/books/{fake_id}/progress"), &viewer, )) .await .unwrap(); // Nonexistent book always returns 404. assert_eq!(resp.status(), StatusCode::NOT_FOUND); } #[tokio::test] async fn update_reading_progress_nonexistent_book() { let (app, _, _, viewer) = setup_app_with_auth().await; let fake_id = uuid::Uuid::now_v7(); let resp = app .clone() .oneshot(put_json_authed( &format!("/api/v1/books/{fake_id}/progress"), r#"{"current_page":42}"#, &viewer, )) .await .unwrap(); // Nonexistent book: handler verifies existence first, so always 404. assert_eq!(resp.status(), StatusCode::NOT_FOUND); } #[tokio::test] async fn reading_list_empty() { let (app, _, _, viewer) = setup_app_with_auth().await; let resp = app .clone() .oneshot(get_authed("/api/v1/books/reading-list", &viewer)) .await .unwrap(); assert_eq!(resp.status(), StatusCode::OK); } #[tokio::test] async fn import_media_requires_editor() { let (app, _, _, viewer) = setup_app_with_auth().await; let resp = app .clone() .oneshot(post_json_authed( "/api/v1/media/import", r#"{"path":"/tmp/test.txt"}"#, &viewer, )) .await .unwrap(); assert_eq!(resp.status(), StatusCode::FORBIDDEN); } #[tokio::test] async fn update_media_requires_editor() { let (app, _, _, viewer) = setup_app_with_auth().await; let fake_id = uuid::Uuid::now_v7(); let resp = app .clone() .oneshot(patch_json_authed( &format!("/api/v1/media/{fake_id}"), r#"{"title":"new"}"#, &viewer, )) .await .unwrap(); assert_eq!(resp.status(), StatusCode::FORBIDDEN); } #[tokio::test] async fn delete_media_requires_editor() { let (app, _, _, viewer) = setup_app_with_auth().await; let fake_id = uuid::Uuid::now_v7(); let resp = app .clone() .oneshot(delete_authed(&format!("/api/v1/media/{fake_id}"), &viewer)) .await .unwrap(); assert_eq!(resp.status(), StatusCode::FORBIDDEN); }