1
0
Fork 0
mirror of https://github.com/mealie-recipes/mealie.git synced 2025-08-05 13:35:23 +02:00

Refactor/conver to controllers (#923)

* add dependency injection for get_repositories

* convert events api to controller

* update generic typing

* add abstract controllers

* update test naming

* migrate admin services to controllers

* add additional admin route tests

* remove print

* add public shared dependencies

* add types

* fix typo

* add static variables for recipe json keys

* add coverage gutters config

* update controller routers

* add generic success response

* add category/tag/tool tests

* add token refresh test

* add coverage utilities

* covert comments to controller

* add todo

* add helper properties

* delete old service

* update test notes

* add unit test for pretty_stats

* remove dead code from post_webhooks

* update group routes to use controllers

* add additional group test coverage

* abstract common permission checks

* convert ingredient parser to controller

* update recipe crud to use controller

* remove dead-code

* add class lifespan tracker for debugging

* convert bulk export to controller

* migrate tools router to controller

* update recipe share to controller

* move customer router to _base

* ignore prints in flake8

* convert units and foods to new controllers

* migrate user routes to controllers

* centralize error handling

* fix invalid ref

* reorder fields

* update routers to share common handling

* update tests

* remove prints

* fix cookbooks delete

* fix cookbook get

* add controller for mealplanner

* cover report routes to controller

* remove __future__ imports

* remove dead code

* remove all base_http children and remove dead code
This commit is contained in:
Hayden 2022-01-13 13:06:52 -09:00 committed by GitHub
parent 5823a32daf
commit c4540f1395
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
164 changed files with 3111 additions and 3213 deletions

View file

@ -0,0 +1,160 @@
from pathlib import Path
import pytest
import sqlalchemy
from fastapi.testclient import TestClient
from mealie.core.dependencies.dependencies import validate_file_token
from mealie.repos.repository_factory import AllRepositories
from mealie.schema.recipe.recipe_bulk_actions import ExportTypes
from mealie.schema.recipe.recipe_category import TagIn
from tests.utils.factories import random_string
from tests.utils.fixture_schemas import TestUser
class Routes:
create_recipes = "/api/recipes"
bulk_tag = "api/recipes/bulk-actions/tag"
bulk_categorize = "api/recipes/bulk-actions/categorize"
bulk_delete = "api/recipes/bulk-actions/delete"
bulk_export = "api/recipes/bulk-actions/export"
bulk_export_download = bulk_export + "/download"
bulk_export_purge = bulk_export + "/purge"
@pytest.fixture(scope="function")
def ten_slugs(api_client: TestClient, unique_user: TestUser, database: AllRepositories) -> list[str]:
slugs = []
for _ in range(10):
payload = {"name": random_string(length=20)}
response = api_client.post(Routes.create_recipes, json=payload, headers=unique_user.token)
assert response.status_code == 201
response_data = response.json()
slugs.append(response_data)
yield slugs
for slug in slugs:
try:
database.recipes.delete(slug)
except sqlalchemy.exc.NoResultFound:
pass
def test_bulk_tag_recipes(
api_client: TestClient, unique_user: TestUser, database: AllRepositories, ten_slugs: list[str]
):
# Setup Tags
tags = []
for _ in range(3):
tag_name = random_string()
tag = database.tags.create(TagIn(name=tag_name))
tags.append(tag.dict())
payload = {"recipes": ten_slugs, "tags": tags}
response = api_client.post(Routes.bulk_tag, json=payload, headers=unique_user.token)
assert response.status_code == 200
# Validate Recipes are Tagged
for slug in ten_slugs:
recipe = database.recipes.get_one(slug)
for tag in recipe.tags:
assert tag.slug in [x["slug"] for x in tags]
def test_bulk_categorize_recipes(
api_client: TestClient,
unique_user: TestUser,
database: AllRepositories,
ten_slugs: list[str],
):
# Setup Tags
categories = []
for _ in range(3):
cat_name = random_string()
cat = database.tags.create(TagIn(name=cat_name))
categories.append(cat.dict())
payload = {"recipes": ten_slugs, "categories": categories}
response = api_client.post(Routes.bulk_categorize, json=payload, headers=unique_user.token)
assert response.status_code == 200
# Validate Recipes are Categorized
for slug in ten_slugs:
recipe = database.recipes.get_one(slug)
for cat in recipe.recipe_category:
assert cat.slug in [x["slug"] for x in categories]
def test_bulk_delete_recipes(
api_client: TestClient,
unique_user: TestUser,
database: AllRepositories,
ten_slugs: list[str],
):
payload = {"recipes": ten_slugs}
response = api_client.post(Routes.bulk_delete, json=payload, headers=unique_user.token)
assert response.status_code == 200
# Validate Recipes are Tagged
for slug in ten_slugs:
recipe = database.recipes.get_one(slug)
assert recipe is None
def test_bulk_export_recipes(api_client: TestClient, unique_user: TestUser, ten_slugs: list[str]):
payload = {
"recipes": ten_slugs,
"export_type": ExportTypes.JSON.value,
}
response = api_client.post(Routes.bulk_export, json=payload, headers=unique_user.token)
assert response.status_code == 202
# Get All Exports Available
response = api_client.get(Routes.bulk_export, headers=unique_user.token)
assert response.status_code == 200
response_data = response.json()
assert len(response_data) == 1
export_path = response_data[0]["path"]
# Get Export Token
response = api_client.get(f"{Routes.bulk_export_download}?path={export_path}", headers=unique_user.token)
assert response.status_code == 200
response_data = response.json()
assert validate_file_token(response_data["fileToken"]) == Path(export_path)
# Use Export Token to donwload export
response = api_client.get("/api/utils/download?token=" + response_data["fileToken"])
assert response.status_code == 200
# Smoke Test to check that a file was downloaded
assert response.headers["Content-Type"] == "application/octet-stream"
assert len(response.content) > 0
# Purge Export
response = api_client.delete(Routes.bulk_export_purge, headers=unique_user.token)
assert response.status_code == 200
# Validate Export was purged
response = api_client.get(Routes.bulk_export, headers=unique_user.token)
assert response.status_code == 200
response_data = response.json()
assert len(response_data) == 0

View file

@ -152,3 +152,17 @@ def test_delete(api_client: TestClient, api_routes: AppRoutes, recipe_data: Reci
recipe_url = api_routes.recipes_recipe_slug(recipe_data.expected_slug)
response = api_client.delete(recipe_url, headers=unique_user.token)
assert response.status_code == 200
def test_recipe_crud_404(api_client: TestClient, api_routes: AppRoutes, unique_user: TestUser):
response = api_client.put(api_routes.recipes_recipe_slug("test"), json={"test": "stest"}, headers=unique_user.token)
assert response.status_code == 404
response = api_client.get(api_routes.recipes_recipe_slug("test"), headers=unique_user.token)
assert response.status_code == 404
response = api_client.delete(api_routes.recipes_recipe_slug("test"), headers=unique_user.token)
assert response.status_code == 404
response = api_client.patch(api_routes.recipes_create_url, json={"test": "stest"}, headers=unique_user.token)
assert response.status_code == 404

View file

@ -0,0 +1,66 @@
import pytest
import sqlalchemy
from fastapi.testclient import TestClient
from mealie.repos.repository_factory import AllRepositories
from tests.utils.factories import random_string
from tests.utils.fixture_schemas import TestUser
class Routes:
create_recipes = "/api/recipes"
def base(item_id: int) -> str:
return f"api/users/{item_id}/favorites"
def toggle(item_id: int, slug: str) -> str:
return f"{Routes.base(item_id)}/{slug}"
@pytest.fixture(scope="function")
def ten_slugs(api_client: TestClient, unique_user: TestUser, database: AllRepositories) -> list[str]:
slugs = []
for _ in range(10):
payload = {"name": random_string(length=20)}
response = api_client.post(Routes.create_recipes, json=payload, headers=unique_user.token)
assert response.status_code == 201
response_data = response.json()
slugs.append(response_data)
yield slugs
for slug in slugs:
try:
database.recipes.delete(slug)
except sqlalchemy.exc.NoResultFound:
pass
def test_recipe_favorites(api_client: TestClient, unique_user: TestUser, ten_slugs: list[str]):
# Check that the user has no favorites
response = api_client.get(Routes.base(unique_user.user_id), headers=unique_user.token)
assert response.status_code == 200
assert response.json()["favoriteRecipes"] == []
# Add a few recipes to the user's favorites
for slug in ten_slugs:
response = api_client.post(Routes.toggle(unique_user.user_id, slug), headers=unique_user.token)
assert response.status_code == 200
# Check that the user has the recipes in their favorites
response = api_client.get(Routes.base(unique_user.user_id), headers=unique_user.token)
assert response.status_code == 200
assert len(response.json()["favoriteRecipes"]) == 10
# Remove a few recipes from the user's favorites
for slug in ten_slugs[:5]:
response = api_client.delete(Routes.toggle(unique_user.user_id, slug), headers=unique_user.token)
assert response.status_code == 200
# Check that the user has the recipes in their favorites
response = api_client.get(Routes.base(unique_user.user_id), headers=unique_user.token)
assert response.status_code == 200
assert len(response.json()["favoriteRecipes"]) == 5

View file

@ -0,0 +1,47 @@
import pytest
from fastapi.testclient import TestClient
from mealie.schema.recipe.recipe_ingredient import RegisteredParser
from tests.unit_tests.test_ingredient_parser import TestIngredient, crf_exists, test_ingredients
from tests.utils.fixture_schemas import TestUser
class Routes:
ingredient = "/api/parser/ingredient"
ingredients = "/api/parser/ingredients"
def assert_ingredient(api_response: dict, test_ingredient: TestIngredient):
assert api_response["ingredient"]["quantity"] == test_ingredient.quantity
assert api_response["ingredient"]["unit"]["name"] == test_ingredient.unit
assert api_response["ingredient"]["food"]["name"] == test_ingredient.food
assert api_response["ingredient"]["note"] == test_ingredient.comments
@pytest.mark.skipif(not crf_exists(), reason="CRF++ not installed")
@pytest.mark.parametrize("test_ingredient", test_ingredients)
def test_recipe_ingredient_parser_nlp(api_client: TestClient, test_ingredient: TestIngredient, unique_user: TestUser):
payload = {"parser": RegisteredParser.nlp, "ingredient": test_ingredient.input}
response = api_client.post(Routes.ingredient, json=payload, headers=unique_user.token)
assert response.status_code == 200
assert_ingredient(response.json(), test_ingredient)
@pytest.mark.skipif(not crf_exists(), reason="CRF++ not installed")
def test_recipe_ingredients_parser_nlp(api_client: TestClient, unique_user: TestUser):
payload = {"parser": RegisteredParser.nlp, "ingredients": [x.input for x in test_ingredients]}
response = api_client.post(Routes.ingredients, json=payload, headers=unique_user.token)
assert response.status_code == 200
for api_ingredient, test_ingredient in zip(response.json(), test_ingredients):
assert_ingredient(api_ingredient, test_ingredient)
@pytest.mark.skip("TODO: Implement")
def test_recipe_ingredient_parser_brute(api_client: TestClient):
pass
@pytest.mark.skip("TODO: Implement")
def test_recipe_ingredients_parser_brute(api_client: TestClient):
pass

View file

@ -0,0 +1,125 @@
import pytest
import sqlalchemy
from fastapi.testclient import TestClient
from mealie.repos.repository_factory import AllRepositories
from mealie.schema.recipe.recipe_share_token import RecipeShareTokenSave
from tests.utils.factories import random_string
from tests.utils.fixture_schemas import TestUser
class Routes:
base = "/api/shared/recipes"
create_recipes = "/api/recipes"
@staticmethod
def item(item_id: str):
return f"{Routes.base}/{item_id}"
@pytest.fixture(scope="function")
def slug(api_client: TestClient, unique_user: TestUser, database: AllRepositories) -> str:
payload = {"name": random_string(length=20)}
response = api_client.post(Routes.create_recipes, json=payload, headers=unique_user.token)
assert response.status_code == 201
response_data = response.json()
yield response_data
try:
database.recipes.delete(response_data)
except sqlalchemy.exc.NoResultFound:
pass
def test_recipe_share_tokens_get_all(
api_client: TestClient,
unique_user: TestUser,
database: AllRepositories,
slug: str,
):
# Create 5 Tokens
recipe = database.recipes.get_one(slug)
tokens = []
for _ in range(5):
token = database.recipe_share_tokens.create(
RecipeShareTokenSave(recipe_id=recipe.id, group_id=unique_user.group_id)
)
tokens.append(token)
# Get All Tokens
response = api_client.get(Routes.base, headers=unique_user.token)
assert response.status_code == 200
response_data = response.json()
assert len(response_data) == 5
def test_recipe_share_tokens_get_all_with_id(
api_client: TestClient,
unique_user: TestUser,
database: AllRepositories,
slug: str,
):
# Create 5 Tokens
recipe = database.recipes.get_one(slug)
tokens = []
for _ in range(3):
token = database.recipe_share_tokens.create(
RecipeShareTokenSave(recipe_id=recipe.id, group_id=unique_user.group_id)
)
tokens.append(token)
response = api_client.get(Routes.base + "?recipe_id=" + str(recipe.id), headers=unique_user.token)
assert response.status_code == 200
response_data = response.json()
assert len(response_data) == 3
def test_recipe_share_tokens_create_and_get_one(
api_client: TestClient,
unique_user: TestUser,
database: AllRepositories,
slug: str,
):
recipe = database.recipes.get_one(slug)
payload = {
"recipe_id": recipe.id,
}
response = api_client.post(Routes.base, json=payload, headers=unique_user.token)
assert response.status_code == 201
response = api_client.get(Routes.item(response.json()["id"]), json=payload, headers=unique_user.token)
assert response.status_code == 200
response_data = response.json()
assert response_data["recipe"]["id"] == recipe.id
def test_recipe_share_tokens_delete_one(
api_client: TestClient,
unique_user: TestUser,
database: AllRepositories,
slug: str,
):
# Create Token
recipe = database.recipes.get_one(slug)
token = database.recipe_share_tokens.create(
RecipeShareTokenSave(recipe_id=recipe.id, group_id=unique_user.group_id)
)
# Delete Token
response = api_client.delete(Routes.item(token.id), headers=unique_user.token)
assert response.status_code == 200
# Get Token
token = database.recipe_share_tokens.get_one(token.id)
assert token is None

View file

@ -1,120 +0,0 @@
from dataclasses import dataclass
import pytest
from fastapi.testclient import TestClient
from tests.utils.factories import random_string
from tests.utils.fixture_schemas import TestUser
class Routes:
base = "/api/tools"
recipes = "/api/recipes"
def item(item_id: int) -> str:
return f"{Routes.base}/{item_id}"
def recipe(recipe_id: int) -> str:
return f"{Routes.recipes}/{recipe_id}"
@dataclass
class TestRecipeTool:
id: int
name: str
slug: str
on_hand: bool
recipes: list
@pytest.fixture(scope="function")
def tool(api_client: TestClient, unique_user: TestUser) -> TestRecipeTool:
data = {"name": random_string(10)}
response = api_client.post(Routes.base, json=data, headers=unique_user.token)
assert response.status_code == 201
as_json = response.json()
yield TestRecipeTool(
id=as_json["id"],
name=data["name"],
slug=as_json["slug"],
on_hand=as_json["onHand"],
recipes=[],
)
try:
response = api_client.delete(Routes.item(response.json()["id"]), headers=unique_user.token)
except Exception:
pass
def test_create_tool(api_client: TestClient, unique_user: TestUser):
data = {"name": random_string(10)}
response = api_client.post(Routes.base, json=data, headers=unique_user.token)
assert response.status_code == 201
def test_read_tool(api_client: TestClient, tool: TestRecipeTool, unique_user: TestUser):
response = api_client.get(Routes.item(tool.id), headers=unique_user.token)
assert response.status_code == 200
as_json = response.json()
assert as_json["id"] == tool.id
assert as_json["name"] == tool.name
def test_update_tool(api_client: TestClient, tool: TestRecipeTool, unique_user: TestUser):
update_data = {
"id": tool.id,
"name": random_string(10),
"slug": tool.slug,
"on_hand": True,
}
response = api_client.put(Routes.item(tool.id), json=update_data, headers=unique_user.token)
assert response.status_code == 200
as_json = response.json()
assert as_json["id"] == tool.id
assert as_json["name"] == update_data["name"]
def test_delete_tool(api_client: TestClient, tool: TestRecipeTool, unique_user: TestUser):
response = api_client.delete(Routes.item(tool.id), headers=unique_user.token)
assert response.status_code == 200
def test_recipe_tool_association(api_client: TestClient, tool: TestRecipeTool, unique_user: TestUser):
# Setup Recipe
recipe_data = {"name": random_string(10)}
response = api_client.post(Routes.recipes, json=recipe_data, headers=unique_user.token)
slug = response.json()
assert response.status_code == 201
# Get Recipe Data
response = api_client.get(Routes.recipe(slug), headers=unique_user.token)
as_json = response.json()
as_json["tools"] = [{"id": tool.id, "name": tool.name, "slug": tool.slug}]
# Update Recipe
response = api_client.put(Routes.recipe(slug), json=as_json, headers=unique_user.token)
assert response.status_code == 200
# Get Recipe Data
response = api_client.get(Routes.recipe(slug), headers=unique_user.token)
as_json = response.json()
assert as_json["tools"][0]["id"] == tool.id