mod common; use axum::http::StatusCode; use serde_json::json; use sqlx::PgPool; use tower::ServiceExt; use uuid::Uuid; async fn put_reaction( app: &axum::Router, cookie: &str, manga_id: Uuid, reaction: &str, ) -> StatusCode { let resp = app .clone() .oneshot(common::put_json_with_cookie( &format!("/api/v1/mangas/{manga_id}/reaction"), json!({ "reaction": reaction }), cookie, )) .await .unwrap(); resp.status() } async fn get_reaction(app: &axum::Router, cookie: &str, manga_id: Uuid) -> serde_json::Value { let resp = app .clone() .oneshot(common::get_with_cookie( &format!("/api/v1/me/reactions/{manga_id}"), cookie, )) .await .unwrap(); assert_eq!(resp.status(), StatusCode::OK); common::body_json(resp).await } #[sqlx::test(migrations = "./migrations")] async fn put_creates_and_reads_back(pool: PgPool) { let h = common::harness(pool); let (_, cookie) = common::register_user(&h.app).await; let manga_id = common::seed_manga_via_api(&h.app, &cookie, "Berserk").await; assert_eq!(put_reaction(&h.app, &cookie, manga_id, "like").await, StatusCode::OK); let body = get_reaction(&h.app, &cookie, manga_id).await; assert_eq!(body["reaction"], "like"); assert_eq!(body["manga_id"], manga_id.to_string()); } #[sqlx::test(migrations = "./migrations")] async fn put_toggles_like_to_dislike(pool: PgPool) { let h = common::harness(pool); let (_, cookie) = common::register_user(&h.app).await; let manga_id = common::seed_manga_via_api(&h.app, &cookie, "Berserk").await; let _ = put_reaction(&h.app, &cookie, manga_id, "like").await; assert_eq!(put_reaction(&h.app, &cookie, manga_id, "dislike").await, StatusCode::OK); assert_eq!(get_reaction(&h.app, &cookie, manga_id).await["reaction"], "dislike"); } #[sqlx::test(migrations = "./migrations")] async fn delete_clears_the_reaction(pool: PgPool) { let h = common::harness(pool); let (_, cookie) = common::register_user(&h.app).await; let manga_id = common::seed_manga_via_api(&h.app, &cookie, "Berserk").await; let _ = put_reaction(&h.app, &cookie, manga_id, "like").await; let resp = h .app .clone() .oneshot(common::delete_with_cookie( &format!("/api/v1/mangas/{manga_id}/reaction"), &cookie, )) .await .unwrap(); assert_eq!(resp.status(), StatusCode::NO_CONTENT); assert_eq!(get_reaction(&h.app, &cookie, manga_id).await["reaction"], serde_json::Value::Null); } #[sqlx::test(migrations = "./migrations")] async fn get_unset_returns_null(pool: PgPool) { let h = common::harness(pool); let (_, cookie) = common::register_user(&h.app).await; let manga_id = common::seed_manga_via_api(&h.app, &cookie, "Berserk").await; assert_eq!(get_reaction(&h.app, &cookie, manga_id).await["reaction"], serde_json::Value::Null); } #[sqlx::test(migrations = "./migrations")] async fn reactions_are_per_user(pool: PgPool) { let h = common::harness(pool); let (_, a) = common::register_user(&h.app).await; let (_, b) = common::register_user(&h.app).await; let manga_id = common::seed_manga_via_api(&h.app, &a, "Berserk").await; let _ = put_reaction(&h.app, &a, manga_id, "like").await; // B sees no reaction of their own. assert_eq!(get_reaction(&h.app, &b, manga_id).await["reaction"], serde_json::Value::Null); } #[sqlx::test(migrations = "./migrations")] async fn put_unknown_manga_is_404(pool: PgPool) { let h = common::harness(pool); let (_, cookie) = common::register_user(&h.app).await; assert_eq!( put_reaction(&h.app, &cookie, Uuid::new_v4(), "like").await, StatusCode::NOT_FOUND ); } #[sqlx::test(migrations = "./migrations")] async fn put_invalid_reaction_is_422(pool: PgPool) { let h = common::harness(pool); let (_, cookie) = common::register_user(&h.app).await; let manga_id = common::seed_manga_via_api(&h.app, &cookie, "Berserk").await; assert_eq!( put_reaction(&h.app, &cookie, manga_id, "meh").await, StatusCode::UNPROCESSABLE_ENTITY ); } #[sqlx::test(migrations = "./migrations")] async fn get_requires_authentication(pool: PgPool) { let h = common::harness(pool); let resp = h .app .clone() .oneshot(common::get(&format!("/api/v1/me/reactions/{}", Uuid::new_v4()))) .await .unwrap(); assert_eq!(resp.status(), StatusCode::UNAUTHORIZED); }