diff --git a/tests/server/cups_api.rs b/tests/server/cups_api.rs new file mode 100644 index 0000000..ac690cc --- /dev/null +++ b/tests/server/cups_api.rs @@ -0,0 +1,361 @@ +use crate::helpers::{ + create_default_cafe, create_default_roast, create_default_roaster, spawn_app_with_auth, +}; +use brewlog::domain::cups::{Cup, CupWithDetails, NewCup, UpdateCup}; +use brewlog::domain::ids::{CafeId, RoastId}; + +#[tokio::test] +async fn creating_a_cup_returns_a_201_for_valid_data() { + let app = spawn_app_with_auth().await; + let client = reqwest::Client::new(); + + let roaster = create_default_roaster(&app).await; + let roast = create_default_roast(&app, roaster.id).await; + let cafe = create_default_cafe(&app).await; + + let new_cup = NewCup { + roast_id: roast.id, + cafe_id: cafe.id, + notes: Some("Excellent pour-over".to_string()), + rating: Some(5), + }; + + let response = client + .post(app.api_url("/cups")) + .bearer_auth(app.auth_token.as_ref().unwrap()) + .json(&new_cup) + .send() + .await + .expect("Failed to execute request"); + + assert_eq!(response.status(), 201); + + let cup: Cup = response.json().await.expect("Failed to parse response"); + assert_eq!(cup.roast_id, roast.id); + assert_eq!(cup.cafe_id, cafe.id); + assert_eq!(cup.notes, Some("Excellent pour-over".to_string())); + assert_eq!(cup.rating, Some(5)); +} + +#[tokio::test] +async fn creating_a_cup_without_optional_fields_returns_a_201() { + let app = spawn_app_with_auth().await; + let client = reqwest::Client::new(); + + let roaster = create_default_roaster(&app).await; + let roast = create_default_roast(&app, roaster.id).await; + let cafe = create_default_cafe(&app).await; + + let new_cup = NewCup { + roast_id: roast.id, + cafe_id: cafe.id, + notes: None, + rating: None, + }; + + let response = client + .post(app.api_url("/cups")) + .bearer_auth(app.auth_token.as_ref().unwrap()) + .json(&new_cup) + .send() + .await + .expect("Failed to execute request"); + + assert_eq!(response.status(), 201); + + let cup: Cup = response.json().await.expect("Failed to parse response"); + assert_eq!(cup.notes, None); + assert_eq!(cup.rating, None); +} + +#[tokio::test] +async fn creating_a_cup_requires_authentication() { + let app = spawn_app_with_auth().await; + let client = reqwest::Client::new(); + + let new_cup = NewCup { + roast_id: RoastId::new(1), + cafe_id: CafeId::new(1), + notes: None, + rating: None, + }; + + let response = client + .post(app.api_url("/cups")) + .json(&new_cup) + .send() + .await + .expect("Failed to execute request"); + + assert_eq!(response.status(), 401); +} + +#[tokio::test] +async fn listing_cups_returns_a_200_with_empty_list() { + let app = spawn_app_with_auth().await; + let client = reqwest::Client::new(); + + let response = client + .get(app.api_url("/cups")) + .send() + .await + .expect("Failed to execute request"); + + assert_eq!(response.status(), 200); + + let cups: Vec = response.json().await.expect("Failed to parse response"); + assert_eq!(cups.len(), 0); +} + +#[tokio::test] +async fn listing_cups_returns_a_200_with_enriched_data() { + let app = spawn_app_with_auth().await; + let client = reqwest::Client::new(); + + let roaster = create_default_roaster(&app).await; + let roast = create_default_roast(&app, roaster.id).await; + let cafe = create_default_cafe(&app).await; + + let new_cup = NewCup { + roast_id: roast.id, + cafe_id: cafe.id, + notes: None, + rating: Some(4), + }; + + client + .post(app.api_url("/cups")) + .bearer_auth(app.auth_token.as_ref().unwrap()) + .json(&new_cup) + .send() + .await + .expect("Failed to create cup"); + + let response = client + .get(app.api_url("/cups")) + .send() + .await + .expect("Failed to execute request"); + + assert_eq!(response.status(), 200); + + let cups: Vec = response.json().await.expect("Failed to parse response"); + assert_eq!(cups.len(), 1); + assert_eq!(cups[0].roast_name, "Test Roast"); + assert_eq!(cups[0].roaster_name, "Test Roasters"); + assert_eq!(cups[0].cafe_name, "Blue Bottle"); +} + +#[tokio::test] +async fn getting_a_cup_returns_a_200_with_details() { + let app = spawn_app_with_auth().await; + let client = reqwest::Client::new(); + + let roaster = create_default_roaster(&app).await; + let roast = create_default_roast(&app, roaster.id).await; + let cafe = create_default_cafe(&app).await; + + let new_cup = NewCup { + roast_id: roast.id, + cafe_id: cafe.id, + notes: Some("Great".to_string()), + rating: Some(3), + }; + + let create_response = client + .post(app.api_url("/cups")) + .bearer_auth(app.auth_token.as_ref().unwrap()) + .json(&new_cup) + .send() + .await + .expect("Failed to create cup"); + + let cup: Cup = create_response + .json() + .await + .expect("Failed to parse response"); + + let response = client + .get(app.api_url(&format!("/cups/{}", cup.id))) + .send() + .await + .expect("Failed to execute request"); + + assert_eq!(response.status(), 200); + + let fetched: CupWithDetails = response.json().await.expect("Failed to parse response"); + assert_eq!(fetched.cup.id, cup.id); + assert_eq!(fetched.roast_name, "Test Roast"); + assert_eq!(fetched.cafe_name, "Blue Bottle"); +} + +#[tokio::test] +async fn getting_a_nonexistent_cup_returns_a_404() { + let app = spawn_app_with_auth().await; + let client = reqwest::Client::new(); + + let response = client + .get(app.api_url("/cups/999999")) + .send() + .await + .expect("Failed to execute request"); + + assert_eq!(response.status(), 404); +} + +#[tokio::test] +async fn updating_a_cup_returns_a_200_for_valid_data() { + let app = spawn_app_with_auth().await; + let client = reqwest::Client::new(); + + let roaster = create_default_roaster(&app).await; + let roast = create_default_roast(&app, roaster.id).await; + let cafe = create_default_cafe(&app).await; + + let new_cup = NewCup { + roast_id: roast.id, + cafe_id: cafe.id, + notes: None, + rating: None, + }; + + let create_response = client + .post(app.api_url("/cups")) + .bearer_auth(app.auth_token.as_ref().unwrap()) + .json(&new_cup) + .send() + .await + .expect("Failed to create cup"); + + let cup: Cup = create_response + .json() + .await + .expect("Failed to parse response"); + + let update = UpdateCup { + notes: Some("Updated notes".to_string()), + rating: Some(4), + }; + + let response = client + .put(app.api_url(&format!("/cups/{}", cup.id))) + .bearer_auth(app.auth_token.as_ref().unwrap()) + .json(&update) + .send() + .await + .expect("Failed to execute request"); + + assert_eq!(response.status(), 200); + + let updated: Cup = response.json().await.expect("Failed to parse response"); + assert_eq!(updated.notes, Some("Updated notes".to_string())); + assert_eq!(updated.rating, Some(4)); +} + +#[tokio::test] +async fn updating_a_cup_with_no_changes_returns_a_400() { + let app = spawn_app_with_auth().await; + let client = reqwest::Client::new(); + + let roaster = create_default_roaster(&app).await; + let roast = create_default_roast(&app, roaster.id).await; + let cafe = create_default_cafe(&app).await; + + let new_cup = NewCup { + roast_id: roast.id, + cafe_id: cafe.id, + notes: None, + rating: None, + }; + + let create_response = client + .post(app.api_url("/cups")) + .bearer_auth(app.auth_token.as_ref().unwrap()) + .json(&new_cup) + .send() + .await + .expect("Failed to create cup"); + + let cup: Cup = create_response + .json() + .await + .expect("Failed to parse response"); + + let update = UpdateCup { + notes: None, + rating: None, + }; + + let response = client + .put(app.api_url(&format!("/cups/{}", cup.id))) + .bearer_auth(app.auth_token.as_ref().unwrap()) + .json(&update) + .send() + .await + .expect("Failed to execute request"); + + assert_eq!(response.status(), 400); +} + +#[tokio::test] +async fn deleting_a_cup_returns_a_204_for_valid_id() { + let app = spawn_app_with_auth().await; + let client = reqwest::Client::new(); + + let roaster = create_default_roaster(&app).await; + let roast = create_default_roast(&app, roaster.id).await; + let cafe = create_default_cafe(&app).await; + + let new_cup = NewCup { + roast_id: roast.id, + cafe_id: cafe.id, + notes: None, + rating: None, + }; + + let create_response = client + .post(app.api_url("/cups")) + .bearer_auth(app.auth_token.as_ref().unwrap()) + .json(&new_cup) + .send() + .await + .expect("Failed to create cup"); + + let cup: Cup = create_response + .json() + .await + .expect("Failed to parse response"); + + let response = client + .delete(app.api_url(&format!("/cups/{}", cup.id))) + .bearer_auth(app.auth_token.as_ref().unwrap()) + .send() + .await + .expect("Failed to execute request"); + + assert_eq!(response.status(), 204); + + // Verify cup was deleted + let get_response = client + .get(app.api_url(&format!("/cups/{}", cup.id))) + .send() + .await + .expect("Failed to execute request"); + + assert_eq!(get_response.status(), 404); +} + +#[tokio::test] +async fn deleting_a_nonexistent_cup_returns_a_404() { + let app = spawn_app_with_auth().await; + let client = reqwest::Client::new(); + + let response = client + .delete(app.api_url("/cups/999999")) + .bearer_auth(app.auth_token.as_ref().unwrap()) + .send() + .await + .expect("Failed to execute request"); + + assert_eq!(response.status(), 404); +} diff --git a/tests/server/helpers.rs b/tests/server/helpers.rs index 0f001e0..a6591d8 100644 --- a/tests/server/helpers.rs +++ b/tests/server/helpers.rs @@ -14,6 +14,7 @@ use brewlog::infrastructure::database::Database; use brewlog::infrastructure::repositories::bags::SqlBagRepository; use brewlog::infrastructure::repositories::brews::SqlBrewRepository; use brewlog::infrastructure::repositories::cafes::SqlCafeRepository; +use brewlog::infrastructure::repositories::cups::SqlCupRepository; use brewlog::infrastructure::repositories::gear::SqlGearRepository; use brewlog::infrastructure::repositories::roasters::SqlRoasterRepository; use brewlog::infrastructure::repositories::roasts::SqlRoastRepository; @@ -66,6 +67,7 @@ pub async fn spawn_app() -> TestApp { let gear_repo = Arc::new(SqlGearRepository::new(database.clone_pool())); let brew_repo = Arc::new(SqlBrewRepository::new(database.clone_pool())); let cafe_repo = Arc::new(SqlCafeRepository::new(database.clone_pool())); + let cup_repo = Arc::new(SqlCupRepository::new(database.clone_pool())); let timeline_repo = Arc::new(SqlTimelineEventRepository::new(database.clone_pool())); let user_repo: Arc = Arc::new(SqlUserRepository::new(database.clone_pool())); @@ -82,6 +84,7 @@ pub async fn spawn_app() -> TestApp { gear_repo, brew_repo, cafe_repo, + cup_repo, timeline_repo, user_repo, token_repo, @@ -101,6 +104,7 @@ async fn spawn_app_inner( gear_repo: Arc, brew_repo: Arc, cafe_repo: Arc, + cup_repo: Arc, timeline_repo: Arc, user_repo: Arc, token_repo: Arc, @@ -116,6 +120,7 @@ async fn spawn_app_inner( gear_repo.clone(), brew_repo.clone(), cafe_repo.clone(), + cup_repo.clone(), timeline_repo.clone(), user_repo.clone(), token_repo.clone(), @@ -179,6 +184,7 @@ pub async fn spawn_app_with_nominatim_mock() -> TestApp { let gear_repo = Arc::new(SqlGearRepository::new(database.clone_pool())); let brew_repo = Arc::new(SqlBrewRepository::new(database.clone_pool())); let cafe_repo = Arc::new(SqlCafeRepository::new(database.clone_pool())); + let cup_repo = Arc::new(SqlCupRepository::new(database.clone_pool())); let timeline_repo = Arc::new(SqlTimelineEventRepository::new(database.clone_pool())); let user_repo: Arc = Arc::new(SqlUserRepository::new(database.clone_pool())); @@ -195,6 +201,7 @@ pub async fn spawn_app_with_nominatim_mock() -> TestApp { gear_repo, brew_repo, cafe_repo, + cup_repo, timeline_repo, user_repo, token_repo, diff --git a/tests/server/main.rs b/tests/server/main.rs index 9b79988..36accd0 100644 --- a/tests/server/main.rs +++ b/tests/server/main.rs @@ -3,6 +3,7 @@ pub mod backup; pub mod bags_api; pub mod brews_api; pub mod cafes_api; +pub mod cups_api; pub mod datastar; pub mod gear_api; pub mod helpers;