diff --git a/backend/src/services/ron/recipe.rs b/backend/src/services/ron/recipe.rs index ca2f6ac..fafda6c 100644 --- a/backend/src/services/ron/recipe.rs +++ b/backend/src/services/ron/recipe.rs @@ -115,7 +115,15 @@ pub async fn rm_tags( ExtractRon(ron): ExtractRon, ) -> Result { check_user_rights_recipe(&connection, &context.user, ron.recipe_id).await?; - connection.rm_recipe_tags(ron.recipe_id, &ron.tags).await?; + connection + .rm_recipe_tags( + ron.recipe_id, + &ron.tags + .into_iter() + .map(|tag| tag.to_lowercase()) + .collect::>(), + ) + .await?; Ok(StatusCode::OK) } @@ -157,7 +165,7 @@ pub async fn set_language( pub async fn set_is_public( State(connection): State, Extension(context): Extension, - ExtractRon(ron): ExtractRon, + ExtractRon(ron): ExtractRon, ) -> Result { check_user_rights_recipe(&connection, &context.user, ron.recipe_id).await?; connection diff --git a/backend/tests/http.rs b/backend/tests/http.rs index 3fef86f..d792622 100644 --- a/backend/tests/http.rs +++ b/backend/tests/http.rs @@ -301,7 +301,65 @@ async fn create_recipe_and_edit_it() -> Result<(), Box> { .await; response.assert_status_ok(); - // TODO: Set other recipe fields. + let response = server + .patch("/ron-api/recipe/estimated_time") + .add_cookie(cookie.clone()) + .add_header(http::header::CONTENT_TYPE, common::consts::MIME_TYPE_RON) + .bytes( + ron_api::to_string(ron_api::SetRecipeEstimatedTime { + recipe_id, + estimated_time: Some(420), + }) + .unwrap() + .into(), + ) + .await; + response.assert_status_ok(); + + let response = server + .patch("/ron-api/recipe/difficulty") + .add_cookie(cookie.clone()) + .add_header(http::header::CONTENT_TYPE, common::consts::MIME_TYPE_RON) + .bytes( + ron_api::to_string(ron_api::SetRecipeDifficulty { + recipe_id, + difficulty: ron_api::Difficulty::Hard, + }) + .unwrap() + .into(), + ) + .await; + response.assert_status_ok(); + + let response = server + .patch("/ron-api/recipe/language") + .add_cookie(cookie.clone()) + .add_header(http::header::CONTENT_TYPE, common::consts::MIME_TYPE_RON) + .bytes( + ron_api::to_string(ron_api::SetRecipeLanguage { + recipe_id, + lang: "fr".into(), + }) + .unwrap() + .into(), + ) + .await; + response.assert_status_ok(); + + let response = server + .patch("/ron-api/recipe/is_public") + .add_cookie(cookie.clone()) + .add_header(http::header::CONTENT_TYPE, common::consts::MIME_TYPE_RON) + .bytes( + ron_api::to_string(ron_api::SetRecipeIsPublic { + recipe_id, + is_public: true, + }) + .unwrap() + .into(), + ) + .await; + response.assert_status_ok(); // Assert. let response = server @@ -327,7 +385,112 @@ async fn create_recipe_and_edit_it() -> Result<(), Box> { let element_servings = document.select(&servings_selector).next().unwrap(); assert_eq!(element_servings.attr("value").unwrap(), "42"); - // dbg!(response); + let estimated_time_selector = Selector::parse("#input-estimated-time").unwrap(); + let element_estimated_time = document.select(&estimated_time_selector).next().unwrap(); + assert_eq!(element_estimated_time.attr("value").unwrap(), "420"); + + let selected_difficulty_selector = + Selector::parse("#select-difficulty option[selected]").unwrap(); + let element_selected_difficulty = document + .select(&selected_difficulty_selector) + .next() + .unwrap(); + assert_eq!(element_selected_difficulty.inner_html(), "Hard"); + + let selected_language_selector = Selector::parse("#select-language option[selected]").unwrap(); + let element_selected_language = document.select(&selected_language_selector).next().unwrap(); + assert_eq!(element_selected_language.inner_html(), "Français"); + + let is_public_selector = Selector::parse("#input-is-public").unwrap(); + let element_is_public = document.select(&is_public_selector).next().unwrap(); + assert!(element_is_public.attr("checked").is_some()); + + Ok(()) +} + +#[tokio::test] +async fn recipe_tags() -> Result<(), Box> { + // Arrange. + let state = utils::common_state().await?; + let user_id = utils::create_user( + &state.db_connection, + "president@spaceball.planet", + "12345678", + ) + .await?; + let token = utils::sign_in( + &state.db_connection, + "president@spaceball.planet", + "12345678", + ) + .await?; + let recipe_id = utils::create_recipe(&state.db_connection, user_id, "spaghetti").await?; + let server = TestServer::new(app::make_service(state))?; + let cookie = Cookie::new("auth_token", token); + + // Act. + // Tags list must be empty. + let response = server + .get("/ron-api/recipe/tags") + .add_cookie(cookie.clone()) + .add_query_param("id", recipe_id) + .await; + response.assert_status_ok(); + let tags: ron_api::Tags = ron::de::from_bytes(response.as_bytes()).unwrap(); + assert!(tags.tags.is_empty()); + + // Add some tags. + let response = server + .post("/ron-api/recipe/tags") + .add_cookie(cookie.clone()) + .add_header(http::header::CONTENT_TYPE, common::consts::MIME_TYPE_RON) + .bytes( + ron_api::to_string(ron_api::Tags { + recipe_id, + tags: vec!["ABC".into(), "xyz".into()], + }) + .unwrap() + .into(), + ) + .await; + response.assert_status_ok(); + + let response = server + .get("/ron-api/recipe/tags") + .add_cookie(cookie.clone()) + .add_query_param("id", recipe_id) + .await; + response.assert_status_ok(); + let tags: ron_api::Tags = ron::de::from_bytes(response.as_bytes()).unwrap(); + assert_eq!(tags.tags.len(), 2); + assert!(tags.tags.contains(&"abc".to_string())); // Tags are in lower case. + assert!(tags.tags.contains(&"xyz".to_string())); + + // Remove some tags. + let response = server + .delete("/ron-api/recipe/tags") + .add_cookie(cookie.clone()) + .add_header(http::header::CONTENT_TYPE, common::consts::MIME_TYPE_RON) + .bytes( + ron_api::to_string(ron_api::Tags { + recipe_id, + tags: vec!["XYZ".into(), "qwe".into()], + }) + .unwrap() + .into(), + ) + .await; + response.assert_status_ok(); + + let response = server + .get("/ron-api/recipe/tags") + .add_cookie(cookie.clone()) + .add_query_param("id", recipe_id) + .await; + response.assert_status_ok(); + let tags: ron_api::Tags = ron::de::from_bytes(response.as_bytes()).unwrap(); + assert_eq!(tags.tags.len(), 1); + assert_eq!(tags.tags[0], "abc".to_string()); Ok(()) } diff --git a/backend/tests/utils/mod.rs b/backend/tests/utils/mod.rs index 3c209ab..e38298b 100644 --- a/backend/tests/utils/mod.rs +++ b/backend/tests/utils/mod.rs @@ -13,7 +13,7 @@ pub async fn common_state_with_email_service( ) -> Result> { let db_connection = db::Connection::new_in_memory().await?; let config = config::Config::default(); - let log = log::Log::new_no_log(); + let log = log::Log::new_to_stdout_only_with_max_level(Some(tracing::Level::ERROR)); Ok(app::AppState { config, db_connection, diff --git a/common/src/ron_api.rs b/common/src/ron_api.rs index 65f39f3..175f48e 100644 --- a/common/src/ron_api.rs +++ b/common/src/ron_api.rs @@ -109,7 +109,7 @@ pub struct SetRecipeLanguage { } #[derive(Serialize, Deserialize, Clone)] -pub struct SetIsPublic { +pub struct SetRecipeIsPublic { pub recipe_id: i64, pub is_public: bool, } diff --git a/frontend/src/pages/recipe_edit.rs b/frontend/src/pages/recipe_edit.rs index 623767b..4524e0d 100644 --- a/frontend/src/pages/recipe_edit.rs +++ b/frontend/src/pages/recipe_edit.rs @@ -231,7 +231,7 @@ pub fn setup_page(recipe_id: i64) { { let is_public: HtmlInputElement = by_id("input-is-public"); EventListener::new(&is_public.clone(), "input", move |_event| { - let body = ron_api::SetIsPublic { + let body = ron_api::SetRecipeIsPublic { recipe_id, is_public: is_public.checked(), }; diff --git a/frontend/src/request.rs b/frontend/src/request.rs index 40c1005..68fe5a8 100644 --- a/frontend/src/request.rs +++ b/frontend/src/request.rs @@ -59,10 +59,7 @@ where { let mut url = format!("/ron-api/{}?", api_name); serde_html_form::ser::push_to_string(&mut url, params).unwrap(); - let request_builder = method_fn(&url).header( - CONTENT_TYPE, - common::consts::MIME_TYPE_RON.to_str().unwrap(), - ); + let request_builder = method_fn(&url); send_req(request_builder.build()?).await }