preparationSteps) {
+ // Not used by JPA/Spring
+ this.id = id;
+ this.name = name;
+ this.ingredients = ingredients;
+ this.preparationSteps = preparationSteps;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Returns an unmodifiable view of the ingredients list.
+ *
+ * The returned list cannot be modified directly. To modify ingredients,
+ * create a copy using {@link List#copyOf(Collection)} or create your own list,
+ * populate it, and use {@link #setIngredients(List)} to update.
+ *
+ * @return An unmodifiable list of ingredients.
+ * @see #setIngredients(List)
+ */
+ // TODO: Replace String with Embeddable Ingredient Class
+ public List getIngredients() {
+ // Disallow modifying the returned list.
+ // You can still copy it with List.copyOf(...)
+ return Collections.unmodifiableList(ingredients);
+ }
+
+
+ // TODO: Replace String with Embeddable Ingredient Class
+ public void setIngredients(List ingredients) {
+ this.ingredients = ingredients;
+ }
+
+ /**
+ * Returns an unmodifiable view of the preparation steps list.
+ *
+ * The returned list cannot be modified directly. To modify preparation steps,
+ * create a copy using {@link List#copyOf(Collection)} or create your own list,
+ * populate it, and use {@link #setPreparationSteps(List)} to update.
+ *
+ * @return An unmodifiable list of preparation steps in order.
+ * @see #setPreparationSteps(List)
+ */
+ public List getPreparationSteps() {
+ // Disallow modifying the returned list.
+ // You can still copy it with List.copyOf(...)
+ return Collections.unmodifiableList(preparationSteps);
+ }
+
+ public void setPreparationSteps(List preparationSteps) {
+ this.preparationSteps = preparationSteps;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ // Faster/Better equals than reflectionEquals
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Recipe recipe = (Recipe) o;
+ return Objects.equals(id, recipe.id); // Check only for ID, as its unique!
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id); // Use ID only, as its unique.
+ }
+
+ @Override
+ public String toString() {
+ return "Recipe{" +
+ "name='" + name + '\'' +
+ ", ingredientsCount=" + ingredients.size() +
+ ", preparationStepsCount=" + preparationSteps.size() +
+ "}";
+ }
+
+ /**
+ * Returns a more detailed string than {@link #toString()} of this recipe, including
+ * the full contents of all ingredients and preparation steps.
+ *
+ * Intended only for debugging.
+ *
+ * @return A detailed string representation containing all recipe data.
+ * @see #toString()
+ */
+ @SuppressWarnings("unused")
+ public String toDetailedString() {
+ return "Recipe{" +
+ "name='" + name + '\'' +
+ ", ingredients=" + ingredients +
+ ", preparationSteps=" + preparationSteps +
+ '}';
+ }
+
+}
diff --git a/commons/src/test/java/commons/RecipeTest.java b/commons/src/test/java/commons/RecipeTest.java
new file mode 100644
index 0000000..db70bf2
--- /dev/null
+++ b/commons/src/test/java/commons/RecipeTest.java
@@ -0,0 +1,121 @@
+package commons;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class RecipeTest {
+
+ Recipe recipe;
+ static final Long RECIPE_ID = 1L;
+
+ @BeforeEach
+ void setupRecipe() {
+ this.recipe = new Recipe(RECIPE_ID, "Chocolate Cake");
+ }
+
+ @Test
+ void getId() {
+ assertEquals(RECIPE_ID, recipe.getId());
+ }
+
+ @Test
+ void setId() {
+ recipe.setId(RECIPE_ID + 1);
+ assertEquals(RECIPE_ID + 1, recipe.getId());
+ }
+
+ @Test
+ void getName() {
+ assertEquals("Chocolate Cake", recipe.getName());
+ }
+
+ @Test
+ void setName() {
+ recipe.setName("Steak");
+ assertEquals("Steak", recipe.getName());
+ }
+
+ @Test
+ void getIngredientsAddThrow() {
+ // TODO: Change to actual Ingredient class later
+ assertThrows(UnsupportedOperationException.class, () -> recipe.getIngredients().add("Lasagna"));
+ }
+
+ @Test
+ void getIngredientsClearThrow() {
+ assertThrows(UnsupportedOperationException.class, () -> recipe.getIngredients().clear());
+ }
+
+ @Test
+ void setIngredients() {
+ // TODO: Change to actual Ingredient class later
+ List ingredients = new ArrayList<>(List.of("Chocolate", "Flour", "Egg"));
+ recipe.setIngredients(ingredients);
+
+ assertEquals(recipe.getIngredients(), ingredients);
+ assertEquals(recipe.getIngredients().size(), ingredients.size());
+ }
+
+ @Test
+ void getPreparationStepsAddThrow() {
+ assertThrows(UnsupportedOperationException.class, () -> recipe.getPreparationSteps().add("Preheat Oven"));
+ }
+
+ @Test
+ void getPreparationStepsClearThrow() {
+ assertThrows(UnsupportedOperationException.class, () -> recipe.getPreparationSteps().clear());
+ }
+
+ @Test
+ void setPreparationSteps() {
+ List steps = new ArrayList<>(List.of("Preheat oven", "Mix stuff", "decorate"));
+ recipe.setPreparationSteps(steps);
+
+ assertEquals(recipe.getPreparationSteps(), steps);
+ assertEquals(recipe.getPreparationSteps().size(), steps.size());
+ }
+
+ @Test
+ void testEqualsSame() {
+ Recipe recipe2 = new Recipe(RECIPE_ID, "Chocolate Cake");
+ assertEquals(recipe, recipe2);
+ }
+
+ @Test
+ void testEqualsSameExceptId() {
+ Recipe recipe2 = new Recipe(RECIPE_ID + 1, "Chocolate Cake");
+ assertNotEquals(recipe, recipe2);
+ }
+
+ @Test
+ void testEqualsOnlySameId() {
+ Recipe recipe2 = new Recipe(RECIPE_ID, "Some random recipe");
+ assertEquals(recipe, recipe2); // Equals, we only look at ID!!!
+ }
+
+ @Test
+ void testHashCodeSame() {
+ Recipe recipe2 = new Recipe(RECIPE_ID, "Chocolate Cake");
+
+ assertEquals(recipe.hashCode(), recipe2.hashCode());
+ }
+
+ @Test
+ void testHashCodeSameExceptId() {
+ Recipe recipe2 = new Recipe(RECIPE_ID + 1, "Chocolate Cake");
+
+ assertNotEquals(recipe.hashCode(), recipe2.hashCode());
+ }
+
+ @Test
+ void testHashCodeOnlySameId() {
+ Recipe recipe2 = new Recipe(RECIPE_ID, "Some random recipe");
+
+ assertEquals(recipe.hashCode(), recipe2.hashCode()); // Same, only looks at ID!!!
+ }
+}
\ No newline at end of file
diff --git a/docs/recipe-api.md b/docs/recipe-api.md
new file mode 100644
index 0000000..e2b9b4f
--- /dev/null
+++ b/docs/recipe-api.md
@@ -0,0 +1,243 @@
+## Recipe API
+
+Base path: `/api`
+
+This API allows clients to create, read, update, and delete `Recipe` resources.
+
+---
+
+## Endpoints
+
+### Get a Recipe by ID
+
+**GET** `/api/recipe/{id}`
+
+Retrieves a specific recipe by its unique identifier.
+
+* **Path Parameters**
+
+ | Name | Type | Required | Description |
+ | ---- | ------ | -------- | ------------------------------ |
+ | `id` | `Long` | Yes | The ID of the recipe to fetch. |
+
+* **Responses**
+
+ | Status Code | Description |
+ | --------------- | ------------------------------------- |
+ | `200 OK` | Returns the recipe with the given ID. |
+ | `404 Not Found` | No recipe exists with that ID. |
+
+* **Example Request**
+
+ ```bash
+ curl -X GET "http://SERVER_ADDRESS/api/recipe/1" \
+ -H "Accept: application/json"
+ ```
+
+* **Example Response (200)**
+
+ ```json
+ {
+ "id": 1,
+ "name": "Pancakes",
+ "ingredients": ["Ingredient 1", "Ingredient 2"],
+ "preparationSteps": ["Step 1", "Step 2"]
+ }
+ ```
+
+---
+
+### List Recipes
+
+**GET** `/api/recipes`
+
+Retrieves a list of recipes. Supports optional pagination via a `limit` query parameter.
+
+* **Query Parameters**
+
+ | Name | Type | Required | Description |
+ | ------- | --------- | -------- | ---------------------------------------------------------------------------- |
+ | `limit` | `Integer` | No | Maximum number of recipes to return. If not provided, returns *all* recipes. |
+
+* **Responses**
+
+ | Status Code | Description |
+ | ----------- | --------------------------------------------- |
+ | `200 OK` | Returns a list of recipes (possibly limited). |
+
+* **Example Request (no limit)**
+
+ ```bash
+ curl -X GET "http://SERVER_ADDRESS/api/recipes" \
+ -H "Accept: application/json"
+ ```
+
+* **Example Request (with limit)**
+
+ ```bash
+ curl -X GET "http://SERVER_ADDRESS/api/recipes?limit=10" \
+ -H "Accept: application/json"
+ ```
+
+* **Example Response (200)**
+
+ ```json
+ [
+ {
+ "id": 10,
+ "name": "Recipe 0",
+ "ingredients": ["Flour", "Milk"],
+ "preparationSteps": ["Do something", "Do something else"]
+ },
+ {
+ "id": 11,
+ "name": "Recipe 1",
+ "ingredients": ["Flour", "Milk"],
+ "preparationSteps": ["Do something", "Do something else"]
+ }
+ // etc. max {limit} items
+ ]
+ ```
+
+---
+
+### Create a New Recipe
+
+**PUT** `/api/recipe/new`
+
+Creates a new recipe. The recipe name must be unique in the repository.
+
+* **Request Body**
+
+ A JSON object representing the recipe. Example:
+
+ ```json
+ {
+ "name": "Pancakes",
+ "ingredients": ["Flour", "Milk", "Eggs"],
+ "preparationSteps": ["Step 1", "Step 2"]
+ }
+ ```
+
+* **Responses**
+
+ | Status Code | Description |
+ | ----------------- | -------------------------------------------------------------------------------------------- |
+ | `200 OK` | The recipe was successfully created. Returns the created recipe (including its assigned ID). |
+ | `400 Bad Request` | A recipe with the same name already exists. |
+
+* **Example Request**
+
+ ```bash
+ curl -X PUT "https://SERVER_ADDRESS/api/recipe/new" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "name": "Pancakes",
+ "ingredients": ["Flour", "Milk", "Eggs"],
+ "preparationSteps": ["Step 1", "Step 2"]
+ }'
+ ```
+
+* **Example Response (200)**
+
+ ```json
+ {
+ "id": 125,
+ "name": "Pancakes",
+ "ingredients": ["Flour", "Milk", "Eggs"],
+ "preparationSteps": ["Step 1", "Step 2"]
+ }
+ ```
+
+---
+
+### Update an Existing Recipe
+
+**POST** `/api/recipe/{id}`
+
+Replaces or updates the recipe with the given ID.
+
+* **Path Parameters**
+
+ | Name | Type | Required | Description |
+ | ---- | ------ | -------- | ------------------------------- |
+ | `id` | `Long` | Yes | The ID of the recipe to update. |
+
+* **Request Body**
+
+ A JSON object containing the new recipe data. It is expected to include the ID (or the server-side save will override it), or at least map correctly to the stored entity.
+
+ Example:
+
+ ```json
+ {
+ "id": 123,
+ "name": "Better Pancakes",
+ "ingredients": ["Flour", "Almond milk", "Eggs"],
+ "preparationSteps": ["Step 10", "Step 11"]
+ }
+ ```
+
+* **Responses**
+
+ | Status Code | Description |
+ | ----------------- | ---------------------------------------------------------------- |
+ | `200 OK` | The recipe was successfully updated. Returns the updated recipe. |
+ | `400 Bad Request` | No recipe exists with the given ID. |
+
+ * **Example Request**
+
+ ```bash
+ curl -X POST "https://your-domain.com/api/recipe/123" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "id": 123,
+ "name": "Better Pancakes",
+ "ingredients": ["Flour", "Almond milk", "Eggs"],
+ "preparationSteps": ["Step 10", "Step 11"]
+ }'
+ ```
+
+* **Example Response (200)**
+
+ ```json
+ {
+ "id": 123,
+ "name": "Updated Pancakes",
+ "ingredients": ["Flour", "Almond milk", "Eggs"],
+ "preparationSteps": "Mix and fry differently."
+ }
+ ```
+
+---
+
+### Delete a Recipe
+
+**DELETE** `/api/recipe/{id}`
+
+Deletes the recipe with the given ID.
+
+* **Path Parameters**
+
+ | Name | Type | Required | Description |
+ | ---- | ------ | -------- | ------------------------------- |
+ | `id` | `Long` | Yes | The ID of the recipe to delete. |
+
+* **Responses**
+
+ | Status Code | Description |
+ | ----------------- | ---------------------------------------------------- |
+ | `200 OK` | The recipe was successfully deleted. Returns `true`. |
+ | `400 Bad Request` | No recipe exists with the given ID. |
+
+* **Example Request**
+
+ ```bash
+ curl -X DELETE "https://your-domain.com/api/recipe/123"
+ ```
+
+* **Example Response (200)**
+
+ ```json
+ true
+ ```
\ No newline at end of file
diff --git a/server/src/main/java/server/api/RecipeController.java b/server/src/main/java/server/api/RecipeController.java
new file mode 100644
index 0000000..a1d79fe
--- /dev/null
+++ b/server/src/main/java/server/api/RecipeController.java
@@ -0,0 +1,130 @@
+package server.api;
+
+import commons.Recipe;
+
+import org.springframework.data.domain.Example;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.http.ResponseEntity;
+
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import server.database.RecipeRepository;
+
+import java.util.List;
+import java.util.Optional;
+
+@RestController
+@RequestMapping("/api")
+public class RecipeController {
+ private final RecipeRepository recipeRepository; // JPA repository used in this controller
+
+ public RecipeController(RecipeRepository recipeRepository) {
+ this.recipeRepository = recipeRepository;
+ }
+
+ /**
+ * Mapping for GET /recipe/{id}
+ *
+ * Gets a specific recipe by its unique id.
+ *
+ * @param id id of the recipe
+ * @return The recipe if it exists in the repository; otherwise returns 404 Not Found status
+ */
+ @GetMapping("/recipe/{id}")
+ public ResponseEntity getRecipe(@PathVariable Long id) {
+ if (!recipeRepository.existsById(id)) {
+ return ResponseEntity.notFound().build();
+ }
+ return ResponseEntity.ok(recipeRepository.findById(id).get());
+ }
+
+ /**
+ * Mapping for GET /recipes(?limit=)
+ *
+ * If the limit parameter is unspecified, return all recipes in the repository.
+ * @param limit Integer limit of items you want to get
+ * @return The list of recipes
+ */
+ @GetMapping("/recipes")
+ public ResponseEntity> getRecipes(@RequestParam Optional limit) {
+ if (limit.isPresent()) {
+ return ResponseEntity.ok(
+ recipeRepository.findAll(
+ PageRequest.of(0, limit.get())
+ ).toList());
+ }
+ return ResponseEntity.ok(recipeRepository.findAll());
+ }
+
+ /**
+ * Mapping for POST /recipe/{id}
+ * @param id The recipe id to replace
+ * @param recipe The new recipe to be replaced from the original
+ * @return The changed recipe; returns 400 Bad Request if the recipe does not exist
+ */
+ @PostMapping("/recipe/{id}")
+ public ResponseEntity updateRecipe(@PathVariable Long id, @RequestBody Recipe recipe) {
+ if (!recipeRepository.existsById(id)) {
+ return ResponseEntity.badRequest().build();
+ }
+
+ // TODO: Send WS update to all subscribers with the updated recipe
+
+ return ResponseEntity.ok(recipeRepository.save(recipe));
+ }
+
+ /**
+ * Mapping for PUT /recipe/new
+ *
+ * Inserts a new recipe into the repository
+ *
+ * @param recipe The new recipe as a request body
+ * @return 200 OK with the recipe you added; or 400 Bad Request if the recipe already exists by name
+ */
+ @PutMapping("/recipe/new")
+ public ResponseEntity createRecipe(@RequestBody Recipe recipe) {
+
+ // We initialize a new example recipe with the name of input recipe
+ // This is the only attribute we are concerned about making sure it's unique
+ Recipe example = new Recipe();
+ example.setName(recipe.getName());
+
+ /* Here we use very funny JPA magic repository.exists(Example)
+ We check if any recipe in the repository has the same name as the input
+ */
+ if (recipeRepository.exists(Example.of(example))) {
+ return ResponseEntity.badRequest().build();
+ }
+
+ // TODO: Send WS update to all subscribers with the new recipe
+
+ return ResponseEntity.ok(recipeRepository.save(recipe));
+ }
+
+ /**
+ * Mapping for DELETE /recipe/{id}
+ *
+ * Deletes a recipe identified by its id.
+ *
+ * @param id The id of the recipe to be deleted.
+ * @return 200 OK with true; or 400 Bad Request if the recipe doesn't exist.
+ */
+ @DeleteMapping("/recipe/{id}")
+ public ResponseEntity deleteRecipe(@PathVariable Long id) {
+ if (!recipeRepository.existsById(id)) {
+ return ResponseEntity.badRequest().build();
+ }
+ recipeRepository.deleteById(id);
+
+ // TODO: Send WS update to propagate deletion
+ return ResponseEntity.ok(true);
+ }
+}
diff --git a/server/src/main/java/server/database/RecipeRepository.java b/server/src/main/java/server/database/RecipeRepository.java
new file mode 100644
index 0000000..60ec130
--- /dev/null
+++ b/server/src/main/java/server/database/RecipeRepository.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2021 Delft University of Technology
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package server.database;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import commons.Recipe;
+
+public interface RecipeRepository extends JpaRepository {}
\ No newline at end of file
diff --git a/server/src/main/resources/application-mock-data-test.properties b/server/src/main/resources/application-mock-data-test.properties
new file mode 100644
index 0000000..c68d6fa
--- /dev/null
+++ b/server/src/main/resources/application-mock-data-test.properties
@@ -0,0 +1,18 @@
+spring.datasource.driverClassName=org.h2.Driver
+spring.datasource.username=sa
+spring.datasource.password=
+spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
+
+# use one of these alternatives...
+# ... purely in-memory, wiped on restart, but great for testing
+spring.datasource.url=jdbc:h2:mem:testdb
+# ... persisted on disk (in project directory)
+#spring.datasource.url=jdbc:h2:file:./h2-database
+
+# enable DB view on http://localhost:8080/h2-console
+spring.h2.console.enabled=true
+
+# strategy for table (re-)generation
+spring.jpa.hibernate.ddl-auto=update
+# show auto-generated SQL commands
+#spring.jpa.hibernate.show_sql=true
diff --git a/server/src/test/java/server/api/RecipeControllerTest.java b/server/src/test/java/server/api/RecipeControllerTest.java
new file mode 100644
index 0000000..f1a766e
--- /dev/null
+++ b/server/src/test/java/server/api/RecipeControllerTest.java
@@ -0,0 +1,149 @@
+package server.api;
+
+
+import commons.Recipe;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+import org.springframework.http.HttpStatus;
+import org.springframework.test.context.ActiveProfiles;
+import server.database.RecipeRepository;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.LongStream;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+// Spring Boot unit testing magic
+// Before each test the state of the repository is reset by
+// rolling back transaction of changes from previous test
+@DataJpaTest
+
+// This test sources its application profile from
+// resources/application-mock-data-test.properties
+// This config uses an in-memory database
+@ActiveProfiles("mock-data-test")
+public class RecipeControllerTest {
+ private RecipeController controller;
+ private List recipes;
+ private final RecipeRepository recipeRepository;
+ private List recipeIds;
+ public static final int NUM_RECIPES = 10;
+
+ // Injects a test repository into the test class
+ @Autowired
+ public RecipeControllerTest(RecipeRepository recipeRepository) {
+ this.recipeRepository = recipeRepository;
+ }
+
+ @BeforeEach
+ public void setup(TestInfo info) {
+ recipes = LongStream
+ .range(0, NUM_RECIPES)
+ .mapToObj(x -> new Recipe(null, "Recipe " + x, List.of(), List.of()))
+ .toList();
+ controller = new RecipeController(recipeRepository);
+ Set tags = info.getTags();
+ List ids = new ArrayList<>();
+
+ // Some tests want initial data to be created.
+ if (tags.contains("test-from-init-data")) {
+ ids = LongStream
+ .range(0, NUM_RECIPES)
+ .map(idx -> recipeRepository.save(recipes.get((int) idx)).getId())
+ .boxed().toList();
+ }
+
+ // Some tests need to know the stored IDs of objects
+ if (tags.contains("need-ids")) {
+ recipeIds = ids;
+ }
+
+ // If no tags specified, the repository is initialized as empty.
+ }
+ @Test
+ public void createOneRecipe() {
+ controller.createRecipe(recipes.getFirst());
+
+ // There is 1 recipe in the repository
+ assertEquals(1, recipeRepository.count());
+ }
+ @Test
+ public void createManyRecipes() {
+ recipes.forEach(recipe -> controller.createRecipe(recipe));
+
+ // There are the same number of recipes in the repository as the input list
+ assertEquals(recipes.size(), recipeRepository.count());
+ }
+ @Test
+ @Tag("test-from-init-data")
+ public void getManyRecipes() {
+ // The number of recipes returned is the same as the entire input list
+ assertEquals(recipes.size(), controller.getRecipes(Optional.empty()).getBody().size());
+ }
+ @Test
+ @Tag("test-from-init-data")
+ public void getSomeRecipes() {
+ final int LIMIT = 5;
+ // The number of recipes returned is the same as the entire input list
+ assertEquals(LIMIT, controller.getRecipes(Optional.of(LIMIT)).getBody().size());
+ }
+ @Test
+ @Tag("test-from-init-data")
+ @Tag("need-ids")
+ public void findOneRecipeExists() {
+ final int CHECK_INDEX = 3;
+ // The third item in the input list is the same as the third item retrieved from the database
+ assertEquals(
+ recipes.get(CHECK_INDEX),
+ controller.getRecipe(recipeIds.get(CHECK_INDEX)).getBody());
+ }
+ @Test
+ public void findOneRecipeNotExists() {
+ final int CHECK_INDEX = 3;
+ // There does not exist a recipe with ID=3 since there are no items in the repository.
+ assertEquals(
+ HttpStatus.NOT_FOUND,
+ controller.getRecipe((long) CHECK_INDEX).getStatusCode());
+ }
+ @Test
+ @Tag("test-from-init-data")
+ @Tag("need-ids")
+ public void deleteOneRecipeGood() {
+ final int DELETE_INDEX = 5;
+
+ // The object has been successfully deleted
+ assertEquals(HttpStatus.OK, controller.deleteRecipe(recipeIds.get(DELETE_INDEX)).getStatusCode());
+ }
+ @Test
+ @Tag("test-from-init-data")
+ @Tag("need-ids")
+ public void deleteOneRecipeCountGood() {
+ final int DELETE_INDEX = 5;
+ controller.deleteRecipe(recipeIds.get(DELETE_INDEX));
+ // The count of items decreased by 1 after the 5th item has been removed.
+ assertEquals(recipeIds.size() - 1, recipeRepository.count());
+ }
+ @Test
+ public void deleteOneRecipeFail() {
+ final Long DELETE_INDEX = 5L;
+ assertEquals(HttpStatus.BAD_REQUEST, controller.deleteRecipe(DELETE_INDEX).getStatusCode());
+ }
+
+ @Test
+ @Tag("test-from-init-data")
+ @Tag("need-ids")
+ public void updateOneRecipeHasNewData() {
+ final int UPDATE_INDEX = 5;
+ Recipe newRecipe = controller.getRecipe(recipeIds.get(UPDATE_INDEX)).getBody();
+ newRecipe.setName("New recipe");
+ controller.updateRecipe(newRecipe.getId(), newRecipe);
+ assertEquals("New recipe", recipeRepository.getReferenceById(recipeIds.get(UPDATE_INDEX)).getName());
+ }
+}