mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-08-02 20:15:24 +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:
parent
5823a32daf
commit
c4540f1395
164 changed files with 3111 additions and 3213 deletions
52
tests/integration_tests/admin_tests/test_admin_about.py
Normal file
52
tests/integration_tests/admin_tests/test_admin_about.py
Normal file
|
@ -0,0 +1,52 @@
|
|||
from fastapi.testclient import TestClient
|
||||
|
||||
from mealie.core.config import get_app_settings
|
||||
from mealie.core.settings.static import APP_VERSION
|
||||
from tests.utils.fixture_schemas import TestUser
|
||||
|
||||
|
||||
class Routes:
|
||||
base = "/api/admin/about"
|
||||
statistics = f"{base}/statistics"
|
||||
check = f"{base}/check"
|
||||
|
||||
|
||||
def test_admin_about_get_app_info(api_client: TestClient, admin_user: TestUser):
|
||||
response = api_client.get(Routes.base, headers=admin_user.token)
|
||||
|
||||
as_dict = response.json()
|
||||
|
||||
settings = get_app_settings()
|
||||
|
||||
assert as_dict["version"] == APP_VERSION
|
||||
assert as_dict["demoStatus"] == settings.IS_DEMO
|
||||
assert as_dict["apiPort"] == settings.API_PORT
|
||||
assert as_dict["apiDocs"] == settings.API_DOCS
|
||||
assert as_dict["dbType"] == settings.DB_ENGINE
|
||||
# assert as_dict["dbUrl"] == settings.DB_URL_PUBLIC
|
||||
assert as_dict["defaultGroup"] == settings.DEFAULT_GROUP
|
||||
|
||||
|
||||
def test_admin_about_get_app_statistics(api_client: TestClient, admin_user: TestUser):
|
||||
response = api_client.get(Routes.statistics, headers=admin_user.token)
|
||||
|
||||
as_dict = response.json()
|
||||
|
||||
# Smoke Test - Test the endpoint returns something thats a number
|
||||
assert as_dict["totalRecipes"] >= 0
|
||||
assert as_dict["uncategorizedRecipes"] >= 0
|
||||
assert as_dict["untaggedRecipes"] >= 0
|
||||
assert as_dict["totalUsers"] >= 0
|
||||
assert as_dict["totalGroups"] >= 0
|
||||
|
||||
|
||||
def test_admin_about_check_app_config(api_client: TestClient, admin_user: TestUser):
|
||||
response = api_client.get(Routes.check, headers=admin_user.token)
|
||||
|
||||
as_dict = response.json()
|
||||
|
||||
# Smoke Test - Test the endpoint returns something thats a the expected shape
|
||||
assert as_dict["emailReady"] in [True, False]
|
||||
assert as_dict["ldapReady"] in [True, False]
|
||||
assert as_dict["baseUrlSet"] in [True, False]
|
||||
assert as_dict["isUpToDate"] in [True, False]
|
|
@ -0,0 +1,24 @@
|
|||
from fastapi.testclient import TestClient
|
||||
|
||||
from mealie.services.server_tasks.background_executory import BackgroundExecutor
|
||||
from tests.utils.fixture_schemas import TestUser
|
||||
|
||||
|
||||
class Routes:
|
||||
base = "/api/admin/server-tasks"
|
||||
|
||||
|
||||
def test_admin_server_tasks_test_and_get(api_client: TestClient, admin_user: TestUser):
|
||||
# Bootstrap Timer
|
||||
BackgroundExecutor.sleep_time = 0.1
|
||||
|
||||
response = api_client.post(Routes.base, headers=admin_user.token)
|
||||
assert response.status_code == 201
|
||||
|
||||
response = api_client.get(Routes.base, headers=admin_user.token)
|
||||
as_dict = response.json()
|
||||
|
||||
assert len(as_dict) == 1
|
||||
|
||||
# Reset Timer
|
||||
BackgroundExecutor.sleep_time = 60
|
107
tests/integration_tests/category_tag_tool_tests/test_category.py
Normal file
107
tests/integration_tests/category_tag_tool_tests/test_category.py
Normal file
|
@ -0,0 +1,107 @@
|
|||
from dataclasses import dataclass
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from mealie.schema.static import recipe_keys
|
||||
from tests.utils.factories import random_string
|
||||
from tests.utils.fixture_schemas import TestUser
|
||||
|
||||
|
||||
class Routes:
|
||||
base = "/api/categories"
|
||||
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 TestRecipeCategory:
|
||||
id: int
|
||||
name: str
|
||||
slug: str
|
||||
recipes: list
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def category(api_client: TestClient, unique_user: TestUser) -> TestRecipeCategory:
|
||||
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 TestRecipeCategory(
|
||||
id=as_json["id"],
|
||||
name=data["name"],
|
||||
slug=as_json["slug"],
|
||||
recipes=[],
|
||||
)
|
||||
|
||||
try:
|
||||
response = api_client.delete(Routes.item(response.json()["slug"]), headers=unique_user.token)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def test_create_category(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_category(api_client: TestClient, category: TestRecipeCategory, unique_user: TestUser):
|
||||
response = api_client.get(Routes.item(category.slug), headers=unique_user.token)
|
||||
assert response.status_code == 200
|
||||
|
||||
as_json = response.json()
|
||||
assert as_json["id"] == category.id
|
||||
assert as_json["name"] == category.name
|
||||
|
||||
|
||||
def test_update_category(api_client: TestClient, category: TestRecipeCategory, unique_user: TestUser):
|
||||
update_data = {
|
||||
"id": category.id,
|
||||
"name": random_string(10),
|
||||
"slug": category.slug,
|
||||
}
|
||||
|
||||
response = api_client.put(Routes.item(category.slug), json=update_data, headers=unique_user.token)
|
||||
assert response.status_code == 200
|
||||
|
||||
as_json = response.json()
|
||||
assert as_json["id"] == category.id
|
||||
assert as_json["name"] == update_data["name"]
|
||||
|
||||
|
||||
def test_delete_category(api_client: TestClient, category: TestRecipeCategory, unique_user: TestUser):
|
||||
response = api_client.delete(Routes.item(category.slug), headers=unique_user.token)
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_recipe_category_association(api_client: TestClient, category: TestRecipeCategory, 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[recipe_keys.recipe_category] = [{"id": category.id, "name": category.name, "slug": category.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[recipe_keys.recipe_category][0]["slug"] == category.slug
|
106
tests/integration_tests/category_tag_tool_tests/test_tags.py
Normal file
106
tests/integration_tests/category_tag_tool_tests/test_tags.py
Normal file
|
@ -0,0 +1,106 @@
|
|||
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/tags"
|
||||
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 TestRecipeTag:
|
||||
id: int
|
||||
name: str
|
||||
slug: str
|
||||
recipes: list
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def tag(api_client: TestClient, unique_user: TestUser) -> TestRecipeTag:
|
||||
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 TestRecipeTag(
|
||||
id=as_json["id"],
|
||||
name=data["name"],
|
||||
slug=as_json["slug"],
|
||||
recipes=[],
|
||||
)
|
||||
|
||||
try:
|
||||
response = api_client.delete(Routes.item(response.json()["slug"]), headers=unique_user.token)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def test_create_tag(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_tag(api_client: TestClient, tag: TestRecipeTag, unique_user: TestUser):
|
||||
response = api_client.get(Routes.item(tag.slug), headers=unique_user.token)
|
||||
assert response.status_code == 200
|
||||
|
||||
as_json = response.json()
|
||||
assert as_json["id"] == tag.id
|
||||
assert as_json["name"] == tag.name
|
||||
|
||||
|
||||
def test_update_tag(api_client: TestClient, tag: TestRecipeTag, unique_user: TestUser):
|
||||
update_data = {
|
||||
"id": tag.id,
|
||||
"name": random_string(10),
|
||||
"slug": tag.slug,
|
||||
}
|
||||
|
||||
response = api_client.put(Routes.item(tag.slug), json=update_data, headers=unique_user.token)
|
||||
assert response.status_code == 200
|
||||
|
||||
as_json = response.json()
|
||||
assert as_json["id"] == tag.id
|
||||
assert as_json["name"] == update_data["name"]
|
||||
|
||||
|
||||
def test_delete_tag(api_client: TestClient, tag: TestRecipeTag, unique_user: TestUser):
|
||||
response = api_client.delete(Routes.item(tag.slug), headers=unique_user.token)
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_recipe_tag_association(api_client: TestClient, tag: TestRecipeTag, 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["tags"] = [{"id": tag.id, "name": tag.name, "slug": tag.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["tags"][0]["slug"] == tag.slug
|
|
@ -63,7 +63,6 @@ def test_read_tool(api_client: TestClient, tool: TestRecipeTool, unique_user: Te
|
|||
assert response.status_code == 200
|
||||
|
||||
as_json = response.json()
|
||||
|
||||
assert as_json["id"] == tool.id
|
||||
assert as_json["name"] == tool.name
|
||||
|
||||
|
@ -80,7 +79,6 @@ def test_update_tool(api_client: TestClient, tool: TestRecipeTool, unique_user:
|
|||
assert response.status_code == 200
|
||||
|
||||
as_json = response.json()
|
||||
|
||||
assert as_json["id"] == tool.id
|
||||
assert as_json["name"] == update_data["name"]
|
||||
|
||||
|
@ -93,28 +91,20 @@ def test_delete_tool(api_client: TestClient, tool: TestRecipeTool, unique_user:
|
|||
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
|
|
@ -1,8 +1,14 @@
|
|||
import random
|
||||
from dataclasses import dataclass
|
||||
from uuid import UUID
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from mealie.repos.repository_factory import AllRepositories
|
||||
from mealie.schema.cookbook.cookbook import ReadCookBook, SaveCookBook
|
||||
from tests.utils.assertion_helpers import assert_ignore_keys
|
||||
from tests.utils.factories import random_string
|
||||
from tests.utils.fixture_schemas import TestUser
|
||||
|
||||
|
||||
|
@ -14,27 +20,56 @@ class Routes:
|
|||
|
||||
|
||||
def get_page_data(group_id: UUID):
|
||||
name_and_slug = random_string(10)
|
||||
return {
|
||||
"name": "My New Page",
|
||||
"slug": "my-new-page",
|
||||
"name": name_and_slug,
|
||||
"slug": name_and_slug,
|
||||
"description": "",
|
||||
"position": 0,
|
||||
"categories": [],
|
||||
"group_id": group_id,
|
||||
"group_id": str(group_id),
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class TestCookbook:
|
||||
id: int
|
||||
slug: str
|
||||
name: str
|
||||
data: dict
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def cookbooks(database: AllRepositories, unique_user: TestUser) -> list[TestCookbook]:
|
||||
|
||||
data: list[ReadCookBook] = []
|
||||
yield_data: list[TestCookbook] = []
|
||||
for _ in range(3):
|
||||
cb = database.cookbooks.create(SaveCookBook(**get_page_data(unique_user.group_id)))
|
||||
data.append(cb)
|
||||
yield_data.append(TestCookbook(id=cb.id, slug=cb.slug, name=cb.name, data=cb.dict()))
|
||||
|
||||
yield yield_data
|
||||
|
||||
for cb in yield_data:
|
||||
try:
|
||||
database.cookbooks.delete(cb.id)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def test_create_cookbook(api_client: TestClient, unique_user: TestUser):
|
||||
page_data = get_page_data(unique_user.group_id)
|
||||
response = api_client.post(Routes.base, json=page_data, headers=unique_user.token)
|
||||
assert response.status_code == 201
|
||||
|
||||
|
||||
def test_read_cookbook(api_client: TestClient, unique_user: TestUser):
|
||||
page_data = get_page_data(unique_user.group_id)
|
||||
def test_read_cookbook(api_client: TestClient, unique_user: TestUser, cookbooks: list[TestCookbook]):
|
||||
sample = random.choice(cookbooks)
|
||||
|
||||
response = api_client.get(Routes.item(1), headers=unique_user.token)
|
||||
assert_ignore_keys(response.json(), page_data)
|
||||
response = api_client.get(Routes.item(sample.id), headers=unique_user.token)
|
||||
assert response.status_code == 200
|
||||
assert_ignore_keys(response.json(), sample.data)
|
||||
|
||||
|
||||
def test_update_cookbook(api_client: TestClient, unique_user: TestUser):
|
||||
|
@ -44,14 +79,36 @@ def test_update_cookbook(api_client: TestClient, unique_user: TestUser):
|
|||
page_data["name"] = "My New Name"
|
||||
|
||||
response = api_client.put(Routes.item(1), json=page_data, headers=unique_user.token)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_delete_cookbook(api_client: TestClient, unique_user: TestUser):
|
||||
response = api_client.delete(Routes.item(1), headers=unique_user.token)
|
||||
def test_update_cookbooks_many(api_client: TestClient, unique_user: TestUser, cookbooks: list[TestCookbook]):
|
||||
pages = [x.data for x in cookbooks]
|
||||
|
||||
reverse_order = sorted(pages, key=lambda x: x["position"], reverse=True)
|
||||
for x, page in enumerate(reverse_order):
|
||||
page["position"] = x
|
||||
page["group_id"] = str(unique_user.group_id)
|
||||
|
||||
response = api_client.put(Routes.base, json=reverse_order, headers=unique_user.token)
|
||||
assert response.status_code == 200
|
||||
|
||||
response = api_client.get(Routes.base, headers=unique_user.token)
|
||||
assert response.status_code == 200
|
||||
|
||||
known_ids = [x.id for x in cookbooks]
|
||||
|
||||
server_ids = [x["id"] for x in response.json()]
|
||||
|
||||
for know in known_ids: # Hacky check, because other tests don't cleanup after themselves :(
|
||||
assert know in server_ids
|
||||
|
||||
|
||||
def test_delete_cookbook(api_client: TestClient, unique_user: TestUser, cookbooks: list[TestCookbook]):
|
||||
sample = random.choice(cookbooks)
|
||||
response = api_client.delete(Routes.item(sample.id), headers=unique_user.token)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
response = api_client.get(Routes.item(1), headers=unique_user.token)
|
||||
response = api_client.get(Routes.item(sample.slug), headers=unique_user.token)
|
||||
assert response.status_code == 404
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
from fastapi.testclient import TestClient
|
||||
|
||||
from mealie.repos.all_repositories import AllRepositories
|
||||
from tests.utils.assertion_helpers import assert_ignore_keys
|
||||
from tests.utils.fixture_schemas import TestUser
|
||||
|
||||
|
||||
class Routes:
|
||||
base = "/api/groups/categories"
|
||||
|
||||
@staticmethod
|
||||
def item(item_id: int | str) -> str:
|
||||
return f"{Routes.base}/{item_id}"
|
||||
|
||||
|
||||
def test_group_mealplan_set_preferences(api_client: TestClient, unique_user: TestUser, database: AllRepositories):
|
||||
# Create Categories
|
||||
categories = [{"name": x} for x in ["Breakfast", "Lunch", "Dinner"]]
|
||||
|
||||
created = []
|
||||
for category in categories:
|
||||
create = database.categories.create(category)
|
||||
created.append(create.dict())
|
||||
|
||||
# Set Category Preferences
|
||||
response = api_client.put(Routes.base, json=created, headers=unique_user.token)
|
||||
assert response.status_code == 200
|
||||
|
||||
# Get Category Preferences
|
||||
response = api_client.get(Routes.base, headers=unique_user.token)
|
||||
assert response.status_code == 200
|
||||
|
||||
as_dict = response.json()
|
||||
|
||||
assert len(as_dict) == len(categories)
|
||||
|
||||
for api_data, expected in zip(as_dict, created):
|
||||
assert_ignore_keys(api_data, expected, ["id", "recipes"])
|
|
@ -0,0 +1,100 @@
|
|||
from uuid import uuid4
|
||||
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from mealie.repos.repository_factory import AllRepositories
|
||||
from tests.utils.factories import random_bool
|
||||
from tests.utils.fixture_schemas import TestUser
|
||||
|
||||
|
||||
class Routes:
|
||||
self = "/api/groups/self"
|
||||
memebers = "/api/groups/members"
|
||||
permissions = "/api/groups/permissions"
|
||||
|
||||
|
||||
def get_permissions_payload(user_id: str, can_manage=None) -> dict:
|
||||
return {
|
||||
"user_id": user_id,
|
||||
"can_manage": random_bool() if can_manage is None else can_manage,
|
||||
"can_invite": random_bool(),
|
||||
"can_organize": random_bool(),
|
||||
}
|
||||
|
||||
|
||||
def test_get_group_members(api_client: TestClient, user_tuple: list[TestUser]):
|
||||
usr_1, usr_2 = user_tuple
|
||||
|
||||
response = api_client.get(Routes.memebers, headers=usr_1.token)
|
||||
assert response.status_code == 200
|
||||
|
||||
members = response.json()
|
||||
assert len(members) >= 2
|
||||
|
||||
all_ids = [x["id"] for x in members]
|
||||
|
||||
assert str(usr_1.user_id) in all_ids
|
||||
assert str(usr_2.user_id) in all_ids
|
||||
|
||||
|
||||
def test_set_memeber_permissions(api_client: TestClient, user_tuple: list[TestUser], database: AllRepositories):
|
||||
usr_1, usr_2 = user_tuple
|
||||
|
||||
# Set Acting User
|
||||
acting_user = database.users.get_one(usr_1.user_id)
|
||||
acting_user.can_manage = True
|
||||
database.users.update(acting_user.id, acting_user)
|
||||
|
||||
payload = get_permissions_payload(str(usr_2.user_id))
|
||||
|
||||
# Test
|
||||
response = api_client.put(Routes.permissions, json=payload, headers=usr_1.token)
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_set_memeber_permissions_unauthorized(api_client: TestClient, unique_user: TestUser, database: AllRepositories):
|
||||
# Setup
|
||||
user = database.users.get_one(unique_user.user_id)
|
||||
user.can_manage = False
|
||||
database.users.update(user.id, user)
|
||||
|
||||
payload = get_permissions_payload(str(user.id))
|
||||
payload = {
|
||||
"user_id": str(user.id),
|
||||
"can_manage": True,
|
||||
"can_invite": True,
|
||||
"can_organize": True,
|
||||
}
|
||||
|
||||
# Test
|
||||
response = api_client.put(Routes.permissions, json=payload, headers=unique_user.token)
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_set_memeber_permissions_other_group(
|
||||
api_client: TestClient,
|
||||
unique_user: TestUser,
|
||||
g2_user: TestUser,
|
||||
database: AllRepositories,
|
||||
):
|
||||
user = database.users.get_one(unique_user.user_id)
|
||||
user.can_manage = True
|
||||
database.users.update(user.id, user)
|
||||
|
||||
payload = get_permissions_payload(str(g2_user.user_id))
|
||||
response = api_client.put(Routes.permissions, json=payload, headers=unique_user.token)
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_set_memeber_permissions_no_user(
|
||||
api_client: TestClient,
|
||||
unique_user: TestUser,
|
||||
database: AllRepositories,
|
||||
):
|
||||
user = database.users.get_one(unique_user.user_id)
|
||||
user.can_manage = True
|
||||
database.users.update(user.id, user)
|
||||
|
||||
payload = get_permissions_payload(str(uuid4()))
|
||||
response = api_client.put(Routes.permissions, json=payload, headers=unique_user.token)
|
||||
assert response.status_code == 404
|
|
@ -12,7 +12,6 @@ class Routes:
|
|||
|
||||
def test_get_preferences(api_client: TestClient, unique_user: TestUser) -> None:
|
||||
response = api_client.get(Routes.preferences, headers=unique_user.token)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
preferences = response.json()
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
# from fastapi.testclient import TestClient
|
||||
|
||||
# from tests.utils.fixture_schemas import TestUser
|
||||
|
||||
|
||||
# class Routes:
|
||||
# base = "/api/groups/manage/data" # Not sure if this is a good url?!?!?
|
||||
|
||||
|
||||
# def test_recipe_export(api_client: TestClient, unique_user: TestUser) -> None:
|
||||
# assert False
|
||||
|
||||
|
||||
# def test_recipe_import(api_client: TestClient, unique_user: TestUser) -> None:
|
||||
# assert False
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -3,6 +3,7 @@ import json
|
|||
from fastapi.testclient import TestClient
|
||||
|
||||
from tests.utils.app_routes import AppRoutes
|
||||
from tests.utils.fixture_schemas import TestUser
|
||||
|
||||
|
||||
def test_failed_login(api_client: TestClient, api_routes: AppRoutes):
|
||||
|
@ -23,3 +24,9 @@ def test_superuser_login(api_client: TestClient, api_routes: AppRoutes, admin_to
|
|||
assert response.status_code == 200
|
||||
|
||||
return {"Authorization": f"Bearer {new_token}"}
|
||||
|
||||
|
||||
def test_user_token_refresh(api_client: TestClient, api_routes: AppRoutes, admin_user: TestUser):
|
||||
response = api_client.post(api_routes.auth_refresh, headers=admin_user.token)
|
||||
response = api_client.get(api_routes.users_self, headers=admin_user.token)
|
||||
assert response.status_code == 200
|
||||
|
|
12
tests/unit_tests/test_exceptions.py
Normal file
12
tests/unit_tests/test_exceptions.py
Normal file
|
@ -0,0 +1,12 @@
|
|||
from mealie.core import exceptions
|
||||
from mealie.lang import get_locale_provider
|
||||
|
||||
|
||||
def test_mealie_registered_exceptions() -> None:
|
||||
provider = get_locale_provider()
|
||||
|
||||
lookup = exceptions.mealie_registered_exceptions(provider)
|
||||
|
||||
assert "permission" in lookup[exceptions.PermissionDenied]
|
||||
assert "The requested resource was not found" in lookup[exceptions.NoEntryFound]
|
||||
assert "integrity" in lookup[exceptions.IntegrityError]
|
|
@ -1,3 +1,4 @@
|
|||
import shutil
|
||||
from dataclasses import dataclass
|
||||
from fractions import Fraction
|
||||
|
||||
|
@ -17,7 +18,6 @@ class TestIngredient:
|
|||
|
||||
|
||||
def crf_exists() -> bool:
|
||||
import shutil
|
||||
|
||||
return shutil.which("crf_test") is not None
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ and then use this test case by removing the `@pytest.mark.skip` and than testing
|
|||
"""
|
||||
|
||||
|
||||
@pytest.mark.skip
|
||||
@pytest.mark.skipif(True, reason="Long Running API Test - manually run when updating the parser")
|
||||
@pytest.mark.parametrize("recipe_test_data", test_cases)
|
||||
def test_recipe_parser(recipe_test_data: RecipeSiteTestCase):
|
||||
recipe = scraper.create_from_url(recipe_test_data.url)
|
||||
|
|
22
tests/unit_tests/test_utils.py
Normal file
22
tests/unit_tests/test_utils.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
import pytest
|
||||
|
||||
from mealie.utils.fs_stats import pretty_size
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"size, expected",
|
||||
[
|
||||
(0, "0 bytes"),
|
||||
(1, "1 bytes"),
|
||||
(1024, "1.0 KB"),
|
||||
(1024 ** 2, "1.0 MB"),
|
||||
(1024 ** 2 * 1024, "1.0 GB"),
|
||||
(1024 ** 2 * 1024 * 1024, "1.0 TB"),
|
||||
],
|
||||
)
|
||||
def test_pretty_size(size: int, expected: str) -> None:
|
||||
"""
|
||||
Test pretty size takes in a integer value of a file size and returns the most applicable
|
||||
file unit and the size.
|
||||
"""
|
||||
assert pretty_size(size) == expected
|
Loading…
Add table
Add a link
Reference in a new issue