From 2ffaecb7b7bc84afb87ec460a4fb731b9603e075 Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Mon, 18 Jan 2021 19:54:46 -0900 Subject: [PATCH 1/2] Mealplan CRUD Tests (#95) * dev-bug: fixed vscode freezes * test: refactor database init to support tests * mealplan CRUD testing Co-authored-by: Hayden --- .gitignore | 1 + frontend/src/api/mealplan.js | 1 + mealie/routes/meal_routes.py | 4 +- mealie/routes/recipe_routes.py | 4 +- mealie/services/meal_services.py | 4 +- mealie/tests/conftest.py | 7 +- mealie/tests/test_routes/__init__.py | 0 mealie/tests/test_routes/test_meal_routes.py | 100 ++++++++++++++++++ .../tests/test_routes/test_recipe_routes.py | 84 ++++++++++----- mealie/tests/test_routes/utils/__init__.py | 0 mealie/tests/test_routes/utils/routes_data.py | 64 +++++++++++ 11 files changed, 232 insertions(+), 37 deletions(-) create mode 100644 mealie/tests/test_routes/__init__.py create mode 100644 mealie/tests/test_routes/test_meal_routes.py create mode 100644 mealie/tests/test_routes/utils/__init__.py create mode 100644 mealie/tests/test_routes/utils/routes_data.py diff --git a/.gitignore b/.gitignore index 83305d643..93d96aca7 100644 --- a/.gitignore +++ b/.gitignore @@ -152,3 +152,4 @@ ENV/ node_modules/ mealie/data/debug/last_recipe.json *.sqlite +app_data/db/test.db diff --git a/frontend/src/api/mealplan.js b/frontend/src/api/mealplan.js index ca05ee12a..44a1be536 100644 --- a/frontend/src/api/mealplan.js +++ b/frontend/src/api/mealplan.js @@ -16,6 +16,7 @@ const mealPlanURLs = { export default { async create(postBody) { let response = await apiReq.post(mealPlanURLs.create, postBody); + console.log(JSON.stringify(postBody)); return response; }, diff --git a/mealie/routes/meal_routes.py b/mealie/routes/meal_routes.py index 0bf536b6d..08b9771e1 100644 --- a/mealie/routes/meal_routes.py +++ b/mealie/routes/meal_routes.py @@ -19,7 +19,7 @@ def get_all_meals(db: Session = Depends(generate_session)): @router.post("/api/meal-plan/create/") def set_meal_plan(data: MealPlan, db: Session = Depends(generate_session)): """ Creates a meal plan database entry """ - data.process_meals() + data.process_meals(db) data.save_to_db(db) # raise HTTPException( @@ -35,7 +35,7 @@ def update_meal_plan( plan_id: str, meal_plan: MealPlan, db: Session = Depends(generate_session) ): """ Updates a meal plan based off ID """ - meal_plan.process_meals() + meal_plan.process_meals(db) meal_plan.update(db, plan_id) # try: # meal_plan.process_meals() diff --git a/mealie/routes/recipe_routes.py b/mealie/routes/recipe_routes.py index d6e5d0372..304d8fbce 100644 --- a/mealie/routes/recipe_routes.py +++ b/mealie/routes/recipe_routes.py @@ -89,9 +89,9 @@ def parse_recipe_url(url: RecipeURLIn, db: Session = Depends(generate_session)): @router.post("/api/recipe/create/") def create_from_json(data: Recipe, db: Session = Depends(generate_session)) -> str: """ Takes in a JSON string and loads data into the database as a new entry""" - created_recipe = data.save_to_db(db) + new_recipe_slug = data.save_to_db(db) - return created_recipe + return new_recipe_slug @router.post("/api/recipe/{recipe_slug}/update/image/") diff --git a/mealie/services/meal_services.py b/mealie/services/meal_services.py index 0f138b9c3..f49e8dcc9 100644 --- a/mealie/services/meal_services.py +++ b/mealie/services/meal_services.py @@ -54,12 +54,12 @@ class MealPlan(BaseModel): } } - def process_meals(self): + def process_meals(self, session: Session): meals = [] for x, meal in enumerate(self.meals): try: - recipe = Recipe.get_by_slug(meal.slug) + recipe = Recipe.get_by_slug(session, meal.slug) meal_data = { "slug": recipe.slug, diff --git a/mealie/tests/conftest.py b/mealie/tests/conftest.py index 33e9232dc..550557742 100644 --- a/mealie/tests/conftest.py +++ b/mealie/tests/conftest.py @@ -19,7 +19,10 @@ def override_get_db(): db.close() -@fixture +@fixture(scope="session") def api_client(): + app.dependency_overrides[generate_session] = override_get_db - return TestClient(app) + yield TestClient(app) + + SQLITE_FILE.unlink() diff --git a/mealie/tests/test_routes/__init__.py b/mealie/tests/test_routes/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/mealie/tests/test_routes/test_meal_routes.py b/mealie/tests/test_routes/test_meal_routes.py new file mode 100644 index 000000000..2df010e51 --- /dev/null +++ b/mealie/tests/test_routes/test_meal_routes.py @@ -0,0 +1,100 @@ +import json + +from tests.test_routes.utils.routes_data import recipe_test_data + + +def cleanup(api_client): + api_client.delete(f"/api/recipe/{recipe_test_data[0].expected_slug}/delete/") + api_client.delete(f"/api/recipe/{recipe_test_data[1].expected_slug}/delete/") + + +meal_plan = { + "startDate": "2021-01-18", + "endDate": "2021-01-19", + "meals": [ + { + "slug": None, + "date": "2021-1-17", + "dateText": "Monday, January 18, 2021", + }, + { + "slug": None, + "date": "2021-1-18", + "dateText": "Tueday, January 19, 2021", + }, + ], +} + + +def test_create_mealplan(api_client): + slug_1 = api_client.post( + "/api/recipe/create-url/", json={"url": recipe_test_data[0].url} + ) + slug_2 = api_client.post( + "/api/recipe/create-url/", json={"url": recipe_test_data[1].url} + ) + + meal_plan["meals"][0]["slug"] = json.loads(slug_1.content) + meal_plan["meals"][1]["slug"] = json.loads(slug_2.content) + + response = api_client.post("/api/meal-plan/create/", json=meal_plan) + assert response.status_code == 200 + + +def test_read_mealplan(api_client): + response = api_client.get("/api/meal-plan/all/") + + assert response.status_code == 200 + + new_meal_plan = json.loads(response.text) + meals = new_meal_plan[0]["meals"] + + assert meals[0]["slug"] == meal_plan["meals"][0]["slug"] + assert meals[1]["slug"] == meal_plan["meals"][1]["slug"] + + cleanup(api_client) + + +def test_update_mealplan(api_client): + slug_1 = api_client.post( + "/api/recipe/create-url/", json={"url": recipe_test_data[0].url} + ) + slug_2 = api_client.post( + "/api/recipe/create-url/", json={"url": recipe_test_data[1].url} + ) + + response = api_client.get("/api/meal-plan/all/") + + existing_mealplan = json.loads(response.text) + existing_mealplan = existing_mealplan[0] + + ## Swap + plan_uid = existing_mealplan.get("uid") + existing_mealplan["meals"][0]["slug"] = json.loads(slug_2.content) + existing_mealplan["meals"][1]["slug"] = json.loads(slug_1.content) + + response = api_client.post( + f"/api/meal-plan/{plan_uid}/update/", json=existing_mealplan + ) + + assert response.status_code == 200 + + response = api_client.get("/api/meal-plan/all/") + existing_mealplan = json.loads(response.text) + existing_mealplan = existing_mealplan[0] + + assert existing_mealplan["meals"][0]["slug"] == json.loads(slug_2.content) + assert existing_mealplan["meals"][1]["slug"] == json.loads(slug_1.content) + + cleanup(api_client) + + +def test_delete_mealplan(api_client): + response = api_client.get("/api/meal-plan/all/") + existing_mealplan = json.loads(response.text) + existing_mealplan = existing_mealplan[0] + + plan_uid = existing_mealplan.get("uid") + response = api_client.delete(f"/api/meal-plan/{plan_uid}/delete/") + + assert response.status_code == 200 diff --git a/mealie/tests/test_routes/test_recipe_routes.py b/mealie/tests/test_routes/test_recipe_routes.py index dfaf93f9e..0946c0424 100644 --- a/mealie/tests/test_routes/test_recipe_routes.py +++ b/mealie/tests/test_routes/test_recipe_routes.py @@ -1,59 +1,85 @@ import json import pytest +from slugify import slugify +from tests.test_routes.utils.routes_data import (RecipeTestData, + raw_recipe_dict, + recipe_test_data) -class RecipeTestData: - def __init__(self, url, expected_slug) -> None: - self.url: str = url - self.expected_slug: str = expected_slug - - -test_data = [ - RecipeTestData( - url="https://www.bonappetit.com/recipe/rustic-shrimp-toasts", - expected_slug="rustic-shrimp-toasts", - ), - RecipeTestData( - url="https://www.allrecipes.com/recipe/282905/honey-garlic-shrimp/", - expected_slug="honey-garlic-shrimp", - ), -] - - -@pytest.mark.parametrize("recipe_data", test_data) -def test_create(api_client, recipe_data: RecipeTestData): - payload = json.dumps({"url": recipe_data.url}) - response = api_client.post("/api/recipe/create-url/", payload) +@pytest.mark.parametrize("recipe_data", recipe_test_data) +def test_create_by_url(api_client, recipe_data: RecipeTestData): + response = api_client.post("/api/recipe/create-url/", json={"url": recipe_data.url}) assert response.status_code == 201 assert json.loads(response.text) == recipe_data.expected_slug -@pytest.mark.parametrize("recipe_data", test_data) +def test_create_by_json(api_client): + response = api_client.post("/api/recipe/create/", json=raw_recipe_dict) + + assert response.status_code == 200 + assert json.loads(response.text) == "banana-bread" + + +def test_read_all_post(api_client): + response = api_client.post( + "/api/all-recipes/", json={"properties": ["slug", "description", "rating"]} + ) + assert response.status_code == 200 + + +@pytest.mark.parametrize("recipe_data", recipe_test_data) def test_read_update(api_client, recipe_data): response = api_client.get(f"/api/recipe/{recipe_data.expected_slug}/") assert response.status_code == 200 recipe = json.loads(response.content) - recipe["notes"] = [ + test_notes = [ {"title": "My Test Title1", "text": "My Test Text1"}, {"title": "My Test Title2", "text": "My Test Text2"}, ] + recipe["notes"] = test_notes - recipe["categories"] = ["one", "two", "three"] - - payload = json.dumps(recipe) + test_categories = ["one", "two", "three"] + recipe["categories"] = test_categories response = api_client.post( - f"/api/recipe/{recipe_data.expected_slug}/update/", payload + f"/api/recipe/{recipe_data.expected_slug}/update/", json=recipe ) assert response.status_code == 200 assert json.loads(response.text) == recipe_data.expected_slug + response = api_client.get(f"/api/recipe/{recipe_data.expected_slug}/") -@pytest.mark.parametrize("recipe_data", test_data) + recipe = json.loads(response.content) + + assert recipe["notes"] == test_notes + assert recipe["categories"] == test_categories + + +@pytest.mark.parametrize("recipe_data", recipe_test_data) +def test_rename(api_client, recipe_data): + response = api_client.get(f"/api/recipe/{recipe_data.expected_slug}/") + assert response.status_code == 200 + + recipe = json.loads(response.content) + new_name = recipe.get("name") + "-rename" + new_slug = slugify(new_name) + recipe["name"] = new_name + + response = api_client.post( + f"/api/recipe/{recipe_data.expected_slug}/update/", json=recipe + ) + + assert response.status_code == 200 + assert json.loads(response.text) == new_slug + + recipe_data.expected_slug = new_slug + + +@pytest.mark.parametrize("recipe_data", recipe_test_data) def test_delete(api_client, recipe_data): response = api_client.delete(f"/api/recipe/{recipe_data.expected_slug}/delete/") assert response.status_code == 200 diff --git a/mealie/tests/test_routes/utils/__init__.py b/mealie/tests/test_routes/utils/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/mealie/tests/test_routes/utils/routes_data.py b/mealie/tests/test_routes/utils/routes_data.py new file mode 100644 index 000000000..f7ca2852d --- /dev/null +++ b/mealie/tests/test_routes/utils/routes_data.py @@ -0,0 +1,64 @@ +class RecipeTestData: + def __init__(self, url, expected_slug) -> None: + self.url: str = url + self.expected_slug: str = expected_slug + + +recipe_test_data = [ + RecipeTestData( + url="https://www.bonappetit.com/recipe/rustic-shrimp-toasts", + expected_slug="rustic-shrimp-toasts", + ), + RecipeTestData( + url="https://www.allrecipes.com/recipe/282905/honey-garlic-shrimp/", + expected_slug="honey-garlic-shrimp", + ), +] + + +raw_recipe_dict = { + "name": "Banana Bread", + "description": "From Angie's mom", + "image": "banana-bread.jpg", + "recipeYield": "", + "recipeIngredient": [ + "4 bananas", + "1/2 cup butter", + "1/2 cup sugar", + "2 eggs", + "2 cups flour", + "1/2 tsp baking soda", + "1 tsp baking powder", + "pinch salt", + "1/4 cup nuts (we like pecans)", + ], + "recipeInstructions": [ + { + "@type": "Beat the eggs, then cream with the butter and sugar", + "text": "Beat the eggs, then cream with the butter and sugar", + }, + { + "@type": "Mix in bananas, then flour, baking soda/powder, salt, and nuts", + "text": "Mix in bananas, then flour, baking soda/powder, salt, and nuts", + }, + { + "@type": "Add to greased and floured pan", + "text": "Add to greased and floured pan", + }, + { + "@type": "Bake until brown/cracked, toothpick comes out clean", + "text": "Bake until brown/cracked, toothpick comes out clean", + }, + ], + "totalTime": "None", + "prepTime": None, + "performTime": None, + "slug": "", + "categories": [], + "tags": ["breakfast", " baking"], + "dateAdded": "2021-01-12", + "notes": [], + "rating": 0, + "orgURL": None, + "extras": {}, +} From 22a517b9c0049e5d465020835b49badceede27dd Mon Sep 17 00:00:00 2001 From: sephrat <34862846+sephrat@users.noreply.github.com> Date: Tue, 19 Jan 2021 20:06:48 +0100 Subject: [PATCH 2/2] Fix typos (#96) --- frontend/src/locales/en.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index 6b302ba08..f88bde5ab 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -109,12 +109,12 @@ }, "webhooks": { "meal-planner-webhooks": "Meal Planner Webhooks", - "the-urls-listed-below-will-recieve-webhooks-containing-the-recipe-data-for-the-meal-plan-on-its-scheduled-day-currently-webhooks-will-execute-at": "The URLs listed below will recieve webhooks containing the recipe data for the meal plan on it's scheduled day. Currently Webhooks will execute at { time }", + "the-urls-listed-below-will-recieve-webhooks-containing-the-recipe-data-for-the-meal-plan-on-its-scheduled-day-currently-webhooks-will-execute-at": "The URLs listed below will receive webhooks containing the recipe data for the meal plan on it's scheduled day. Currently Webhooks will execute at { time }", "test-webhooks": "Test Webhooks", "webhook-url": "Webhook URL", "save-webhooks": "Save Webhooks" }, - "new-version-available": "A New Version of Mealie is Avaiable, Visit the Repo ", + "new-version-available": "A New Version of Mealie is Available, Visit the Repo ", "backup": { "import-recipes": "Import Recipes", "import-themes": "Import Themes", @@ -130,11 +130,11 @@ "migrate": "Migrate", "failed-recipes": "Failed Recipes", "failed-images": "Failed Images", - "you-can-import-recipes-from-either-a-zip-file-or-a-directory-located-in-the-app-data-migraiton-folder-please-review-the-documentation-to-ensure-your-directory-structure-matches-what-is-expected": "You can import recipes from either a zip file or a directory located in the /app/data/migraiton/ folder. Please review the documentation to ensure your directory structure matches what is expected", + "you-can-import-recipes-from-either-a-zip-file-or-a-directory-located-in-the-app-data-migraiton-folder-please-review-the-documentation-to-ensure-your-directory-structure-matches-what-is-expected": "You can import recipes from either a zip file or a directory located in the /app/data/migration/ folder. Please review the documentation to ensure your directory structure matches what is expected", "nextcloud-data": "Nextcloud Data", "delete-confirmation": "Are you sure you want to delete this migration data?", "successfully-imported-from-nextcloud": "Successfully Imported from Nextcloud", "failed-imports": "Failed Imports", "upload-an-archive": "Upload an Archive" } -} \ No newline at end of file +}