mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-06-03 21:55:22 -04:00
* fix #1144 * fix type checks * refactor test routes package * fix #1208 * unify test routes into module
This commit is contained in:
parent
07f6446526
commit
68f7efc177
@ -1,9 +1,9 @@
|
|||||||
preserve_hierarchy: false
|
preserve_hierarchy: false
|
||||||
files:
|
files:
|
||||||
- source: /frontend/src/locales/messages/en-US.json
|
- source: /frontend/lang/messages/en-US.json
|
||||||
translation: /frontend/src/locales/messages/%locale%.json
|
translation: /frontend/lang/messages/%locale%.json
|
||||||
- source: /frontend/src/locales/dateTimeFormats/en-US.json
|
- source: /frontend/lang/dateTimeFormats/en-US.json
|
||||||
translation: /frontend/src/locales/dateTimeFormats/%locale%.json
|
translation: /frontend/lang/dateTimeFormats/%locale%.json
|
||||||
|
|
||||||
# Backend General Messages
|
# Backend General Messages
|
||||||
- source: /mealie/lang/messages/en-US.json
|
- source: /mealie/lang/messages/en-US.json
|
||||||
|
@ -149,8 +149,8 @@ class RepositoryGeneric(Generic[T, D]):
|
|||||||
if match_key is None:
|
if match_key is None:
|
||||||
match_key = self.primary_key
|
match_key = self.primary_key
|
||||||
|
|
||||||
filter = self._filter_builder(**{match_key: match_value})
|
fltr = self._filter_builder(**{match_key: match_value})
|
||||||
return self.session.query(self.sql_model).filter_by(**filter).one()
|
return self.session.query(self.sql_model).filter_by(**fltr).one()
|
||||||
|
|
||||||
def get_one(self, value: str | int | UUID4, key: str = None, any_case=False, override_schema=None) -> T | None:
|
def get_one(self, value: str | int | UUID4, key: str = None, any_case=False, override_schema=None) -> T | None:
|
||||||
key = key or self.primary_key
|
key = key or self.primary_key
|
||||||
@ -210,7 +210,7 @@ class RepositoryGeneric(Generic[T, D]):
|
|||||||
|
|
||||||
return [eff_schema.from_orm(x) for x in result]
|
return [eff_schema.from_orm(x) for x in result]
|
||||||
|
|
||||||
def create(self, document: T | BaseModel) -> T:
|
def create(self, document: T | BaseModel | dict) -> T:
|
||||||
"""Creates a new database entry for the given SQL Alchemy Model.
|
"""Creates a new database entry for the given SQL Alchemy Model.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -266,8 +266,10 @@ class RepositoryGeneric(Generic[T, D]):
|
|||||||
|
|
||||||
return self.update(match_value, entry_as_dict)
|
return self.update(match_value, entry_as_dict)
|
||||||
|
|
||||||
def delete(self, primary_key_value) -> D:
|
def delete(self, value, match_key: str | None = None) -> T:
|
||||||
result = self.session.query(self.sql_model).filter_by(**{self.primary_key: primary_key_value}).one()
|
match_key = match_key or self.primary_key
|
||||||
|
|
||||||
|
result = self.session.query(self.sql_model).filter_by(**{match_key: value}).one()
|
||||||
results_as_model = self.schema.from_orm(result)
|
results_as_model = self.schema.from_orm(result)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -313,7 +315,7 @@ class RepositoryGeneric(Generic[T, D]):
|
|||||||
for x in self.session.query(self.sql_model).filter(attribute_name == attr_match).all() # noqa: 711
|
for x in self.session.query(self.sql_model).filter(attribute_name == attr_match).all() # noqa: 711
|
||||||
]
|
]
|
||||||
|
|
||||||
def create_many(self, documents: list[T]) -> list[T]:
|
def create_many(self, documents: list[T | dict]) -> list[T]:
|
||||||
new_documents = []
|
new_documents = []
|
||||||
for document in documents:
|
for document in documents:
|
||||||
document = document if isinstance(document, dict) else document.dict()
|
document = document if isinstance(document, dict) else document.dict()
|
||||||
|
@ -2,6 +2,8 @@ import random
|
|||||||
import shutil
|
import shutil
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
from pydantic import UUID4
|
||||||
|
|
||||||
from mealie.assets import users as users_assets
|
from mealie.assets import users as users_assets
|
||||||
from mealie.schema.user.user import PrivateUser, User
|
from mealie.schema.user.user import PrivateUser, User
|
||||||
|
|
||||||
@ -30,11 +32,11 @@ class RepositoryUsers(RepositoryGeneric[PrivateUser, User]):
|
|||||||
|
|
||||||
return new_user
|
return new_user
|
||||||
|
|
||||||
def delete(self, id: str) -> User:
|
def delete(self, value: str | UUID4, match_key: str | None = None) -> User:
|
||||||
entry = super().delete(id)
|
entry = super().delete(value, match_key)
|
||||||
# Delete the user's directory
|
# Delete the user's directory
|
||||||
shutil.rmtree(PrivateUser.get_directory(id))
|
shutil.rmtree(PrivateUser.get_directory(value))
|
||||||
return entry
|
return entry # type: ignore
|
||||||
|
|
||||||
def get_by_username(self, username: str, limit=1) -> Optional[User]:
|
def get_by_username(self, username: str, limit=1) -> Optional[User]:
|
||||||
dbuser = self.session.query(User).filter(User.username == username).one_or_none()
|
dbuser = self.session.query(User).filter(User.username == username).one_or_none()
|
||||||
|
@ -128,7 +128,7 @@ class PrivateUser(UserOut):
|
|||||||
orm_mode = True
|
orm_mode = True
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_directory(user_id: UUID4) -> Path:
|
def get_directory(user_id: UUID4 | str) -> Path:
|
||||||
user_dir = get_app_dirs().USER_DIR / str(user_id)
|
user_dir = get_app_dirs().USER_DIR / str(user_id)
|
||||||
user_dir.mkdir(parents=True, exist_ok=True)
|
user_dir.mkdir(parents=True, exist_ok=True)
|
||||||
return user_dir
|
return user_dir
|
||||||
|
@ -178,7 +178,7 @@ class RecipeService(BaseService):
|
|||||||
def delete_one(self, slug) -> Recipe:
|
def delete_one(self, slug) -> Recipe:
|
||||||
recipe = self._get_recipe(slug)
|
recipe = self._get_recipe(slug)
|
||||||
self.can_update(recipe)
|
self.can_update(recipe)
|
||||||
data = self.repos.recipes.delete(slug)
|
data = self.repos.recipes.delete(recipe.id, "id")
|
||||||
self.delete_assets(data)
|
self.delete_assets(data)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ def generate_create_data() -> dict:
|
|||||||
def test_init_superuser(api_client: TestClient, admin_user: TestUser):
|
def test_init_superuser(api_client: TestClient, admin_user: TestUser):
|
||||||
settings = get_app_settings()
|
settings = get_app_settings()
|
||||||
|
|
||||||
response = api_client.get(routes.RoutesAdminUsers.item(admin_user.user_id), headers=admin_user.token)
|
response = api_client.get(routes.admin.AdminUsers.item(admin_user.user_id), headers=admin_user.token)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
admin_data = response.json()
|
admin_data = response.json()
|
||||||
@ -41,13 +41,13 @@ def test_init_superuser(api_client: TestClient, admin_user: TestUser):
|
|||||||
|
|
||||||
def test_create_user(api_client: TestClient, api_routes: AppRoutes, admin_token):
|
def test_create_user(api_client: TestClient, api_routes: AppRoutes, admin_token):
|
||||||
create_data = generate_create_data()
|
create_data = generate_create_data()
|
||||||
response = api_client.post(routes.RoutesAdminUsers.base, json=create_data, headers=admin_token)
|
response = api_client.post(routes.admin.AdminUsers.base, json=create_data, headers=admin_token)
|
||||||
assert response.status_code == 201
|
assert response.status_code == 201
|
||||||
|
|
||||||
form_data = {"username": create_data["email"], "password": create_data["password"]}
|
form_data = {"username": create_data["email"], "password": create_data["password"]}
|
||||||
header = utils.login(form_data=form_data, api_client=api_client, api_routes=api_routes)
|
header = utils.login(form_data=form_data, api_client=api_client, api_routes=api_routes)
|
||||||
|
|
||||||
response = api_client.get(routes.RoutesUsers.self, headers=header)
|
response = api_client.get(routes.user.Users.self, headers=header)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
user_data = response.json()
|
user_data = response.json()
|
||||||
@ -60,14 +60,14 @@ def test_create_user(api_client: TestClient, api_routes: AppRoutes, admin_token)
|
|||||||
|
|
||||||
def test_create_user_as_non_admin(api_client: TestClient, user_token):
|
def test_create_user_as_non_admin(api_client: TestClient, user_token):
|
||||||
create_data = generate_create_data()
|
create_data = generate_create_data()
|
||||||
response = api_client.post(routes.RoutesAdminUsers.base, json=create_data, headers=user_token)
|
response = api_client.post(routes.admin.AdminUsers.base, json=create_data, headers=user_token)
|
||||||
assert response.status_code == 403
|
assert response.status_code == 403
|
||||||
|
|
||||||
|
|
||||||
def test_update_user(api_client: TestClient, admin_user: TestUser):
|
def test_update_user(api_client: TestClient, admin_user: TestUser):
|
||||||
# Create a new user
|
# Create a new user
|
||||||
create_data = generate_create_data()
|
create_data = generate_create_data()
|
||||||
response = api_client.post(routes.RoutesAdminUsers.base, json=create_data, headers=admin_user.token)
|
response = api_client.post(routes.admin.AdminUsers.base, json=create_data, headers=admin_user.token)
|
||||||
assert response.status_code == 201
|
assert response.status_code == 201
|
||||||
update_data = response.json()
|
update_data = response.json()
|
||||||
|
|
||||||
@ -76,7 +76,7 @@ def test_update_user(api_client: TestClient, admin_user: TestUser):
|
|||||||
update_data["email"] = random_email()
|
update_data["email"] = random_email()
|
||||||
|
|
||||||
response = api_client.put(
|
response = api_client.put(
|
||||||
routes.RoutesAdminUsers.item(update_data["id"]), headers=admin_user.token, json=update_data
|
routes.admin.AdminUsers.item(update_data["id"]), headers=admin_user.token, json=update_data
|
||||||
)
|
)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
@ -93,21 +93,21 @@ def test_update_other_user_as_not_admin(api_client: TestClient, unique_user: Tes
|
|||||||
"admin": True,
|
"admin": True,
|
||||||
}
|
}
|
||||||
response = api_client.put(
|
response = api_client.put(
|
||||||
routes.RoutesAdminUsers.item(g2_user.user_id), headers=unique_user.token, json=update_data
|
routes.admin.AdminUsers.item(g2_user.user_id), headers=unique_user.token, json=update_data
|
||||||
)
|
)
|
||||||
|
|
||||||
assert response.status_code == 403
|
assert response.status_code == 403
|
||||||
|
|
||||||
|
|
||||||
def test_self_demote_admin(api_client: TestClient, admin_user: TestUser):
|
def test_self_demote_admin(api_client: TestClient, admin_user: TestUser):
|
||||||
response = api_client.get(routes.RoutesUsers.self, headers=admin_user.token)
|
response = api_client.get(routes.user.Users.self, headers=admin_user.token)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
user_data = response.json()
|
user_data = response.json()
|
||||||
user_data["admin"] = False
|
user_data["admin"] = False
|
||||||
|
|
||||||
response = api_client.put(
|
response = api_client.put(
|
||||||
routes.RoutesAdminUsers.item(admin_user.user_id), headers=admin_user.token, json=user_data
|
routes.admin.AdminUsers.item(admin_user.user_id), headers=admin_user.token, json=user_data
|
||||||
)
|
)
|
||||||
|
|
||||||
assert response.status_code == 403
|
assert response.status_code == 403
|
||||||
@ -122,12 +122,12 @@ def test_self_promote_admin(api_client: TestClient, unique_user: TestUser):
|
|||||||
"admin": True,
|
"admin": True,
|
||||||
}
|
}
|
||||||
response = api_client.put(
|
response = api_client.put(
|
||||||
routes.RoutesAdminUsers.item(unique_user.user_id), headers=unique_user.token, json=update_data
|
routes.admin.AdminUsers.item(unique_user.user_id), headers=unique_user.token, json=update_data
|
||||||
)
|
)
|
||||||
|
|
||||||
assert response.status_code == 403
|
assert response.status_code == 403
|
||||||
|
|
||||||
|
|
||||||
def test_delete_user(api_client: TestClient, admin_token, unique_user: TestUser):
|
def test_delete_user(api_client: TestClient, admin_token, unique_user: TestUser):
|
||||||
response = api_client.delete(routes.RoutesAdminUsers.item(unique_user.user_id), headers=admin_token)
|
response = api_client.delete(routes.admin.AdminUsers.item(unique_user.user_id), headers=admin_token)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
@ -14,9 +14,9 @@ test_ids = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
organizer_routes = [
|
organizer_routes = [
|
||||||
(routes.RoutesCategory),
|
(routes.organizers.Categories),
|
||||||
(routes.RoutesTags),
|
(routes.organizers.Tags),
|
||||||
(routes.RoutesTools),
|
(routes.organizers.Tools),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -43,9 +43,9 @@ def test_organizers_create_read(api_client: TestClient, unique_user: TestUser, r
|
|||||||
|
|
||||||
|
|
||||||
update_data = [
|
update_data = [
|
||||||
(routes.RoutesCategory, {"name": random_string(10)}),
|
(routes.organizers.Categories, {"name": random_string(10)}),
|
||||||
(routes.RoutesTags, {"name": random_string(10)}),
|
(routes.organizers.Tags, {"name": random_string(10)}),
|
||||||
(routes.RoutesTools, {"name": random_string(10), "onHand": random_bool()}),
|
(routes.organizers.Tools, {"name": random_string(10), "onHand": random_bool()}),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -101,9 +101,9 @@ def test_organizer_delete(
|
|||||||
|
|
||||||
|
|
||||||
association_data = [
|
association_data = [
|
||||||
(routes.RoutesCategory, recipe_keys.recipe_category),
|
(routes.organizers.Categories, recipe_keys.recipe_category),
|
||||||
(routes.RoutesTags, "tags"),
|
(routes.organizers.Tags, "tags"),
|
||||||
(routes.RoutesTools, "tools"),
|
(routes.organizers.Tools, "tools"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -123,28 +123,28 @@ def test_organizer_association(
|
|||||||
|
|
||||||
# Setup Recipe
|
# Setup Recipe
|
||||||
recipe_data = {"name": random_string(10)}
|
recipe_data = {"name": random_string(10)}
|
||||||
response = api_client.post(routes.RoutesRecipe.base, json=recipe_data, headers=unique_user.token)
|
response = api_client.post(routes.recipes.Recipe.base, json=recipe_data, headers=unique_user.token)
|
||||||
slug = response.json()
|
slug = response.json()
|
||||||
assert response.status_code == 201
|
assert response.status_code == 201
|
||||||
|
|
||||||
# Get Recipe Data
|
# Get Recipe Data
|
||||||
response = api_client.get(routes.RoutesRecipe.item(slug), headers=unique_user.token)
|
response = api_client.get(routes.recipes.Recipe.item(slug), headers=unique_user.token)
|
||||||
as_json = response.json()
|
as_json = response.json()
|
||||||
as_json[recipe_key] = [
|
as_json[recipe_key] = [
|
||||||
{"id": item["id"], "group_id": unique_user.group_id, "name": item["name"], "slug": item["slug"]}
|
{"id": item["id"], "group_id": unique_user.group_id, "name": item["name"], "slug": item["slug"]}
|
||||||
]
|
]
|
||||||
|
|
||||||
# Update Recipe
|
# Update Recipe
|
||||||
response = api_client.put(routes.RoutesRecipe.item(slug), json=as_json, headers=unique_user.token)
|
response = api_client.put(routes.recipes.Recipe.item(slug), json=as_json, headers=unique_user.token)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
# Get Recipe Data
|
# Get Recipe Data
|
||||||
response = api_client.get(routes.RoutesRecipe.item(slug), headers=unique_user.token)
|
response = api_client.get(routes.recipes.Recipe.item(slug), headers=unique_user.token)
|
||||||
as_json = response.json()
|
as_json = response.json()
|
||||||
assert as_json[recipe_key][0]["slug"] == item["slug"]
|
assert as_json[recipe_key][0]["slug"] == item["slug"]
|
||||||
|
|
||||||
# Cleanup
|
# Cleanup
|
||||||
response = api_client.delete(routes.RoutesRecipe.item(slug), headers=unique_user.token)
|
response = api_client.delete(routes.recipes.Recipe.item(slug), headers=unique_user.token)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
response = api_client.delete(route.item(item["id"]), headers=unique_user.token)
|
response = api_client.delete(route.item(item["id"]), headers=unique_user.token)
|
||||||
@ -155,7 +155,7 @@ def test_organizer_association(
|
|||||||
def test_organizer_get_by_slug(
|
def test_organizer_get_by_slug(
|
||||||
api_client: TestClient,
|
api_client: TestClient,
|
||||||
unique_user: TestUser,
|
unique_user: TestUser,
|
||||||
route: routes.RoutesOrganizerBase,
|
route: routes.organizers.RoutesOrganizerBase,
|
||||||
recipe_key: str,
|
recipe_key: str,
|
||||||
):
|
):
|
||||||
# Create Organizer
|
# Create Organizer
|
||||||
@ -170,20 +170,20 @@ def test_organizer_get_by_slug(
|
|||||||
for _ in range(10):
|
for _ in range(10):
|
||||||
# Setup Recipe
|
# Setup Recipe
|
||||||
recipe_data = {"name": random_string(10)}
|
recipe_data = {"name": random_string(10)}
|
||||||
response = api_client.post(routes.RoutesRecipe.base, json=recipe_data, headers=unique_user.token)
|
response = api_client.post(routes.recipes.Recipe.base, json=recipe_data, headers=unique_user.token)
|
||||||
assert response.status_code == 201
|
assert response.status_code == 201
|
||||||
slug = response.json()
|
slug = response.json()
|
||||||
recipe_slugs.append(slug)
|
recipe_slugs.append(slug)
|
||||||
|
|
||||||
# Associate 10 Recipes to Organizer
|
# Associate 10 Recipes to Organizer
|
||||||
for slug in recipe_slugs:
|
for slug in recipe_slugs:
|
||||||
response = api_client.get(routes.RoutesRecipe.item(slug), headers=unique_user.token)
|
response = api_client.get(routes.recipes.Recipe.item(slug), headers=unique_user.token)
|
||||||
as_json = response.json()
|
as_json = response.json()
|
||||||
as_json[recipe_key] = [
|
as_json[recipe_key] = [
|
||||||
{"id": item["id"], "group_id": unique_user.group_id, "name": item["name"], "slug": item["slug"]}
|
{"id": item["id"], "group_id": unique_user.group_id, "name": item["name"], "slug": item["slug"]}
|
||||||
]
|
]
|
||||||
|
|
||||||
response = api_client.put(routes.RoutesRecipe.item(slug), json=as_json, headers=unique_user.token)
|
response = api_client.put(routes.recipes.Recipe.item(slug), json=as_json, headers=unique_user.token)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
# Get Organizer by Slug
|
# Get Organizer by Slug
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
from fastapi.testclient import TestClient
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
from mealie.repos.repository_factory import AllRepositories
|
from mealie.repos.repository_factory import AllRepositories
|
||||||
|
from tests.utils import routes
|
||||||
from tests.utils.fixture_schemas import TestUser
|
from tests.utils.fixture_schemas import TestUser
|
||||||
from tests.utils.routes import RoutesSeeders
|
|
||||||
|
|
||||||
|
|
||||||
def test_seed_invalid_locale(api_client: TestClient, unique_user: TestUser):
|
def test_seed_invalid_locale(api_client: TestClient, unique_user: TestUser):
|
||||||
for route in [RoutesSeeders.foods, RoutesSeeders.labels, RoutesSeeders.units]:
|
for route in [routes.seeders.Seeders.foods, routes.seeders.Seeders.labels, routes.seeders.Seeders.units]:
|
||||||
resp = api_client.post(route, json={"locale": "invalid"}, headers=unique_user.token)
|
resp = api_client.post(route, json={"locale": "invalid"}, headers=unique_user.token)
|
||||||
assert resp.status_code == 422
|
assert resp.status_code == 422
|
||||||
|
|
||||||
@ -18,7 +18,7 @@ def test_seed_foods(api_client: TestClient, unique_user: TestUser, database: All
|
|||||||
foods = database.ingredient_foods.by_group(unique_user.group_id).get_all()
|
foods = database.ingredient_foods.by_group(unique_user.group_id).get_all()
|
||||||
assert len(foods) == 0
|
assert len(foods) == 0
|
||||||
|
|
||||||
resp = api_client.post(RoutesSeeders.foods, json={"locale": "en-US"}, headers=unique_user.token)
|
resp = api_client.post(routes.seeders.Seeders.foods, json={"locale": "en-US"}, headers=unique_user.token)
|
||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
|
|
||||||
# Check that the foods was created
|
# Check that the foods was created
|
||||||
@ -33,7 +33,7 @@ def test_seed_units(api_client: TestClient, unique_user: TestUser, database: All
|
|||||||
units = database.ingredient_units.by_group(unique_user.group_id).get_all()
|
units = database.ingredient_units.by_group(unique_user.group_id).get_all()
|
||||||
assert len(units) == 0
|
assert len(units) == 0
|
||||||
|
|
||||||
resp = api_client.post(RoutesSeeders.units, json={"locale": "en-US"}, headers=unique_user.token)
|
resp = api_client.post(routes.seeders.Seeders.units, json={"locale": "en-US"}, headers=unique_user.token)
|
||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
|
|
||||||
# Check that the foods was created
|
# Check that the foods was created
|
||||||
@ -48,7 +48,7 @@ def test_seed_labels(api_client: TestClient, unique_user: TestUser, database: Al
|
|||||||
labels = database.group_multi_purpose_labels.by_group(unique_user.group_id).get_all()
|
labels = database.group_multi_purpose_labels.by_group(unique_user.group_id).get_all()
|
||||||
assert len(labels) == 0
|
assert len(labels) == 0
|
||||||
|
|
||||||
resp = api_client.post(RoutesSeeders.labels, json={"locale": "en-US"}, headers=unique_user.token)
|
resp = api_client.post(routes.seeders.Seeders.labels, json={"locale": "en-US"}, headers=unique_user.token)
|
||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
|
|
||||||
# Check that the foods was created
|
# Check that the foods was created
|
||||||
|
@ -14,6 +14,7 @@ from mealie.schema.recipe.recipe import RecipeCategory
|
|||||||
from mealie.services.recipe.recipe_data_service import RecipeDataService
|
from mealie.services.recipe.recipe_data_service import RecipeDataService
|
||||||
from mealie.services.scraper.scraper_strategies import RecipeScraperOpenGraph
|
from mealie.services.scraper.scraper_strategies import RecipeScraperOpenGraph
|
||||||
from tests import data, utils
|
from tests import data, utils
|
||||||
|
from tests.utils import routes
|
||||||
from tests.utils.app_routes import AppRoutes
|
from tests.utils.app_routes import AppRoutes
|
||||||
from tests.utils.factories import random_string
|
from tests.utils.factories import random_string
|
||||||
from tests.utils.fixture_schemas import TestUser
|
from tests.utils.fixture_schemas import TestUser
|
||||||
@ -157,12 +158,11 @@ def test_create_by_url_with_tags(
|
|||||||
@pytest.mark.parametrize("recipe_data", recipe_test_data)
|
@pytest.mark.parametrize("recipe_data", recipe_test_data)
|
||||||
def test_read_update(
|
def test_read_update(
|
||||||
api_client: TestClient,
|
api_client: TestClient,
|
||||||
api_routes: AppRoutes,
|
|
||||||
recipe_data: RecipeSiteTestCase,
|
recipe_data: RecipeSiteTestCase,
|
||||||
unique_user: TestUser,
|
unique_user: TestUser,
|
||||||
recipe_categories: list[RecipeCategory],
|
recipe_categories: list[RecipeCategory],
|
||||||
):
|
):
|
||||||
recipe_url = api_routes.recipes_recipe_slug(recipe_data.expected_slug)
|
recipe_url = routes.recipes.Recipe.item(recipe_data.expected_slug)
|
||||||
response = api_client.get(recipe_url, headers=unique_user.token)
|
response = api_client.get(recipe_url, headers=unique_user.token)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
@ -196,8 +196,8 @@ def test_read_update(
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("recipe_data", recipe_test_data)
|
@pytest.mark.parametrize("recipe_data", recipe_test_data)
|
||||||
def test_rename(api_client: TestClient, api_routes: AppRoutes, recipe_data: RecipeSiteTestCase, unique_user: TestUser):
|
def test_rename(api_client: TestClient, recipe_data: RecipeSiteTestCase, unique_user: TestUser):
|
||||||
recipe_url = api_routes.recipes_recipe_slug(recipe_data.expected_slug)
|
recipe_url = routes.recipes.Recipe.item(recipe_data.expected_slug)
|
||||||
response = api_client.get(recipe_url, headers=unique_user.token)
|
response = api_client.get(recipe_url, headers=unique_user.token)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
@ -215,44 +215,66 @@ def test_rename(api_client: TestClient, api_routes: AppRoutes, recipe_data: Reci
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("recipe_data", recipe_test_data)
|
@pytest.mark.parametrize("recipe_data", recipe_test_data)
|
||||||
def test_delete(api_client: TestClient, api_routes: AppRoutes, recipe_data: RecipeSiteTestCase, unique_user: TestUser):
|
def test_delete(api_client: TestClient, recipe_data: RecipeSiteTestCase, unique_user: TestUser):
|
||||||
recipe_url = api_routes.recipes_recipe_slug(recipe_data.expected_slug)
|
response = api_client.delete(routes.recipes.Recipe.item(recipe_data.expected_slug), headers=unique_user.token)
|
||||||
response = api_client.delete(recipe_url, headers=unique_user.token)
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
def test_recipe_crud_404(api_client: TestClient, api_routes: AppRoutes, unique_user: TestUser):
|
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)
|
response = api_client.put(routes.recipes.Recipe.item("test"), json={"test": "stest"}, headers=unique_user.token)
|
||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
|
|
||||||
response = api_client.get(api_routes.recipes_recipe_slug("test"), headers=unique_user.token)
|
response = api_client.get(routes.recipes.Recipe.item("test"), headers=unique_user.token)
|
||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
|
|
||||||
response = api_client.delete(api_routes.recipes_recipe_slug("test"), headers=unique_user.token)
|
response = api_client.delete(routes.recipes.Recipe.item("test"), headers=unique_user.token)
|
||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
|
|
||||||
response = api_client.patch(api_routes.recipes_create_url, json={"test": "stest"}, headers=unique_user.token)
|
response = api_client.patch(api_routes.recipes_create_url, json={"test": "stest"}, headers=unique_user.token)
|
||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
def test_create_recipe_same_name(api_client: TestClient, api_routes: AppRoutes, unique_user: TestUser):
|
def test_create_recipe_same_name(api_client: TestClient, unique_user: TestUser):
|
||||||
slug = random_string(10)
|
slug = random_string(10)
|
||||||
|
|
||||||
response = api_client.post("/api/recipes", json={"name": slug}, headers=unique_user.token)
|
response = api_client.post(routes.recipes.Recipe.base, json={"name": slug}, headers=unique_user.token)
|
||||||
assert response.status_code == 201
|
assert response.status_code == 201
|
||||||
assert json.loads(response.text) == slug
|
assert json.loads(response.text) == slug
|
||||||
|
|
||||||
response = api_client.post("/api/recipes", json={"name": slug}, headers=unique_user.token)
|
response = api_client.post(routes.recipes.Recipe.base, json={"name": slug}, headers=unique_user.token)
|
||||||
assert response.status_code == 201
|
assert response.status_code == 201
|
||||||
assert json.loads(response.text) == f"{slug}-1"
|
assert json.loads(response.text) == f"{slug}-1"
|
||||||
|
|
||||||
|
|
||||||
def test_create_recipe_too_many_time(api_client: TestClient, api_routes: AppRoutes, unique_user: TestUser):
|
def test_create_recipe_too_many_time(api_client: TestClient, unique_user: TestUser):
|
||||||
slug = random_string(10)
|
slug = random_string(10)
|
||||||
|
|
||||||
for _ in range(10):
|
for _ in range(10):
|
||||||
response = api_client.post("/api/recipes", json={"name": slug}, headers=unique_user.token)
|
response = api_client.post(routes.recipes.Recipe.base, json={"name": slug}, headers=unique_user.token)
|
||||||
assert response.status_code == 201
|
assert response.status_code == 201
|
||||||
|
|
||||||
response = api_client.post("/api/recipes", json={"name": slug}, headers=unique_user.token)
|
response = api_client.post(routes.recipes.Recipe.base, json={"name": slug}, headers=unique_user.token)
|
||||||
assert response.status_code == 400
|
assert response.status_code == 400
|
||||||
|
|
||||||
|
|
||||||
|
def test_delete_recipe_same_name(api_client: TestClient, unique_user: utils.TestUser, g2_user: utils.TestUser):
|
||||||
|
slug = random_string(10)
|
||||||
|
|
||||||
|
# Create recipe for both users
|
||||||
|
for user in (unique_user, g2_user):
|
||||||
|
response = api_client.post(routes.recipes.Recipe.base, json={"name": slug}, headers=user.token)
|
||||||
|
assert response.status_code == 201
|
||||||
|
assert json.loads(response.text) == slug
|
||||||
|
|
||||||
|
# Delete recipe for user 1
|
||||||
|
response = api_client.delete(routes.recipes.Recipe.item(slug), headers=unique_user.token)
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
# Ensure recipe for user 2 still exists
|
||||||
|
response = api_client.get(routes.recipes.Recipe.item(slug), headers=g2_user.token)
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
# Make sure recipe for user 1 doesn't exist
|
||||||
|
response = api_client.get(routes.recipes.Recipe.item(slug), headers=unique_user.token)
|
||||||
|
response = api_client.get(routes.recipes.Recipe.item(slug), headers=unique_user.token)
|
||||||
|
assert response.status_code == 404
|
||||||
|
@ -26,7 +26,7 @@ def test_associate_ingredient_with_step(api_client: TestClient, unique_user: Tes
|
|||||||
steps[idx] = [str(ingredient.reference_id) for ingredient in ingredients]
|
steps[idx] = [str(ingredient.reference_id) for ingredient in ingredients]
|
||||||
|
|
||||||
response = api_client.put(
|
response = api_client.put(
|
||||||
routes.RoutesRecipe.item(recipe.slug),
|
routes.recipes.Recipe.item(recipe.slug),
|
||||||
json=jsonify(recipe.dict()),
|
json=jsonify(recipe.dict()),
|
||||||
headers=unique_user.token,
|
headers=unique_user.token,
|
||||||
)
|
)
|
||||||
@ -35,14 +35,14 @@ def test_associate_ingredient_with_step(api_client: TestClient, unique_user: Tes
|
|||||||
|
|
||||||
# Get Recipe and check that the ingredient is associated with the step
|
# Get Recipe and check that the ingredient is associated with the step
|
||||||
|
|
||||||
response = api_client.get(routes.RoutesRecipe.item(recipe.slug), headers=unique_user.token)
|
response = api_client.get(routes.recipes.Recipe.item(recipe.slug), headers=unique_user.token)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
recipe = json.loads(response.text)
|
data: dict = json.loads(response.text)
|
||||||
|
|
||||||
for idx, step in enumerate(recipe.get("recipeInstructions")):
|
for idx, stp in enumerate(data.get("recipeInstructions")):
|
||||||
all_refs = [ref["referenceId"] for ref in step.get("ingredientReferences")]
|
all_refs = [ref["referenceId"] for ref in stp.get("ingredientReferences")]
|
||||||
|
|
||||||
assert len(all_refs) == 2
|
assert len(all_refs) == 2
|
||||||
|
|
||||||
|
@ -10,8 +10,8 @@ from tests.utils import routes
|
|||||||
class CategoryTestCase(ABCMultiTenantTestCase):
|
class CategoryTestCase(ABCMultiTenantTestCase):
|
||||||
items: list[RecipeCategory]
|
items: list[RecipeCategory]
|
||||||
|
|
||||||
def seed_action(self, group_id: str) -> set[int]:
|
def seed_action(self, group_id: str) -> set[str]:
|
||||||
category_ids: set[int] = set()
|
category_ids: set[str] = set()
|
||||||
for _ in range(10):
|
for _ in range(10):
|
||||||
category = self.database.categories.create(
|
category = self.database.categories.create(
|
||||||
CategorySave(
|
CategorySave(
|
||||||
@ -26,8 +26,8 @@ class CategoryTestCase(ABCMultiTenantTestCase):
|
|||||||
return category_ids
|
return category_ids
|
||||||
|
|
||||||
def seed_multi(self, group1_id: str, group2_id: str) -> tuple[set[str], set[str]]:
|
def seed_multi(self, group1_id: str, group2_id: str) -> tuple[set[str], set[str]]:
|
||||||
g1_item_ids = set()
|
g1_item_ids: set[str] = set()
|
||||||
g2_item_ids = set()
|
g2_item_ids: set[str] = set()
|
||||||
|
|
||||||
for group_id, item_ids in [(group1_id, g1_item_ids), (group2_id, g2_item_ids)]:
|
for group_id, item_ids in [(group1_id, g1_item_ids), (group2_id, g2_item_ids)]:
|
||||||
for _ in range(10):
|
for _ in range(10):
|
||||||
@ -44,7 +44,7 @@ class CategoryTestCase(ABCMultiTenantTestCase):
|
|||||||
return g1_item_ids, g2_item_ids
|
return g1_item_ids, g2_item_ids
|
||||||
|
|
||||||
def get_all(self, token: str) -> Response:
|
def get_all(self, token: str) -> Response:
|
||||||
return self.client.get(routes.RoutesCategory.base, headers=token)
|
return self.client.get(routes.organizers.Categories.base, headers=token)
|
||||||
|
|
||||||
def cleanup(self) -> None:
|
def cleanup(self) -> None:
|
||||||
for item in self.items:
|
for item in self.items:
|
||||||
|
@ -43,7 +43,7 @@ class FoodsTestCase(ABCMultiTenantTestCase):
|
|||||||
return g1_item_ids, g2_item_ids
|
return g1_item_ids, g2_item_ids
|
||||||
|
|
||||||
def get_all(self, token: str) -> Response:
|
def get_all(self, token: str) -> Response:
|
||||||
return self.client.get(routes.RoutesFoods.base, headers=token)
|
return self.client.get(routes.recipes.Foods.base, headers=token)
|
||||||
|
|
||||||
def cleanup(self) -> None:
|
def cleanup(self) -> None:
|
||||||
for item in self.items:
|
for item in self.items:
|
||||||
|
@ -10,8 +10,8 @@ from tests.utils import routes
|
|||||||
class TagsTestCase(ABCMultiTenantTestCase):
|
class TagsTestCase(ABCMultiTenantTestCase):
|
||||||
items: list[RecipeTag]
|
items: list[RecipeTag]
|
||||||
|
|
||||||
def seed_action(self, group_id: str) -> set[int]:
|
def seed_action(self, group_id: str) -> set[str]:
|
||||||
tag_ids: set[int] = set()
|
tag_ids: set[str] = set()
|
||||||
for _ in range(10):
|
for _ in range(10):
|
||||||
tag = self.database.tags.create(
|
tag = self.database.tags.create(
|
||||||
TagSave(
|
TagSave(
|
||||||
@ -26,8 +26,8 @@ class TagsTestCase(ABCMultiTenantTestCase):
|
|||||||
return tag_ids
|
return tag_ids
|
||||||
|
|
||||||
def seed_multi(self, group1_id: str, group2_id: str) -> tuple[set[str], set[str]]:
|
def seed_multi(self, group1_id: str, group2_id: str) -> tuple[set[str], set[str]]:
|
||||||
g1_item_ids = set()
|
g1_item_ids: set[str] = set()
|
||||||
g2_item_ids = set()
|
g2_item_ids: set[str] = set()
|
||||||
|
|
||||||
for group_id, item_ids in [(group1_id, g1_item_ids), (group2_id, g2_item_ids)]:
|
for group_id, item_ids in [(group1_id, g1_item_ids), (group2_id, g2_item_ids)]:
|
||||||
for _ in range(10):
|
for _ in range(10):
|
||||||
@ -44,7 +44,7 @@ class TagsTestCase(ABCMultiTenantTestCase):
|
|||||||
return g1_item_ids, g2_item_ids
|
return g1_item_ids, g2_item_ids
|
||||||
|
|
||||||
def get_all(self, token: str) -> Response:
|
def get_all(self, token: str) -> Response:
|
||||||
return self.client.get(routes.RoutesTags.base, headers=token)
|
return self.client.get(routes.organizers.Tags.base, headers=token)
|
||||||
|
|
||||||
def cleanup(self) -> None:
|
def cleanup(self) -> None:
|
||||||
for item in self.items:
|
for item in self.items:
|
||||||
|
@ -44,7 +44,7 @@ class ToolsTestCase(ABCMultiTenantTestCase):
|
|||||||
return g1_item_ids, g2_item_ids
|
return g1_item_ids, g2_item_ids
|
||||||
|
|
||||||
def get_all(self, token: str) -> Response:
|
def get_all(self, token: str) -> Response:
|
||||||
return self.client.get(routes.RoutesTools.base, headers=token)
|
return self.client.get(routes.organizers.Tools.base, headers=token)
|
||||||
|
|
||||||
def cleanup(self) -> None:
|
def cleanup(self) -> None:
|
||||||
for item in self.items:
|
for item in self.items:
|
||||||
|
@ -9,8 +9,8 @@ from tests.utils import routes
|
|||||||
class UnitsTestCase(ABCMultiTenantTestCase):
|
class UnitsTestCase(ABCMultiTenantTestCase):
|
||||||
items: list[IngredientUnit]
|
items: list[IngredientUnit]
|
||||||
|
|
||||||
def seed_action(self, group_id: str) -> set[int]:
|
def seed_action(self, group_id: str) -> set[str]:
|
||||||
unit_ids: set[int] = set()
|
unit_ids: set[str] = set()
|
||||||
for _ in range(10):
|
for _ in range(10):
|
||||||
unit = self.database.ingredient_units.create(
|
unit = self.database.ingredient_units.create(
|
||||||
SaveIngredientUnit(
|
SaveIngredientUnit(
|
||||||
@ -25,8 +25,8 @@ class UnitsTestCase(ABCMultiTenantTestCase):
|
|||||||
return unit_ids
|
return unit_ids
|
||||||
|
|
||||||
def seed_multi(self, group1_id: str, group2_id: str) -> tuple[set[str], set[str]]:
|
def seed_multi(self, group1_id: str, group2_id: str) -> tuple[set[str], set[str]]:
|
||||||
g1_item_ids = set()
|
g1_item_ids: set[str] = set()
|
||||||
g2_item_ids = set()
|
g2_item_ids: set[str] = set()
|
||||||
|
|
||||||
for group_id, item_ids in [(group1_id, g1_item_ids), (group2_id, g2_item_ids)]:
|
for group_id, item_ids in [(group1_id, g1_item_ids), (group2_id, g2_item_ids)]:
|
||||||
for _ in range(10):
|
for _ in range(10):
|
||||||
@ -43,7 +43,7 @@ class UnitsTestCase(ABCMultiTenantTestCase):
|
|||||||
return g1_item_ids, g2_item_ids
|
return g1_item_ids, g2_item_ids
|
||||||
|
|
||||||
def get_all(self, token: str) -> Response:
|
def get_all(self, token: str) -> Response:
|
||||||
return self.client.get(routes.RoutesUnits.base, headers=token)
|
return self.client.get(routes.recipes.Units.base, headers=token)
|
||||||
|
|
||||||
def cleanup(self) -> None:
|
def cleanup(self) -> None:
|
||||||
for item in self.items:
|
for item in self.items:
|
||||||
|
@ -1,60 +0,0 @@
|
|||||||
from pydantic import UUID4
|
|
||||||
|
|
||||||
|
|
||||||
class RoutesBase:
|
|
||||||
prefix = "/api"
|
|
||||||
base = f"{prefix}/"
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
|
||||||
raise Exception("This class is not meant to be instantiated.")
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def item(cls, item_id: int | str | UUID4) -> str:
|
|
||||||
return f"{cls.base}/{item_id}"
|
|
||||||
|
|
||||||
|
|
||||||
class RoutesFoods(RoutesBase):
|
|
||||||
base = "/api/foods"
|
|
||||||
|
|
||||||
|
|
||||||
class RoutesUnits(RoutesBase):
|
|
||||||
base = "/api/units"
|
|
||||||
|
|
||||||
|
|
||||||
class RoutesOrganizerBase(RoutesBase):
|
|
||||||
@classmethod
|
|
||||||
def slug(cls, slug: str) -> str:
|
|
||||||
return f"{cls.base}/slug/{slug}"
|
|
||||||
|
|
||||||
|
|
||||||
class RoutesTools(RoutesOrganizerBase):
|
|
||||||
base = "/api/organizers/tools"
|
|
||||||
|
|
||||||
|
|
||||||
class RoutesTags(RoutesOrganizerBase):
|
|
||||||
base = "/api/organizers/tags"
|
|
||||||
|
|
||||||
|
|
||||||
class RoutesCategory(RoutesOrganizerBase):
|
|
||||||
base = "/api/organizers/categories"
|
|
||||||
|
|
||||||
|
|
||||||
class RoutesRecipe(RoutesBase):
|
|
||||||
base = "/api/recipes"
|
|
||||||
|
|
||||||
|
|
||||||
class RoutesAdminUsers(RoutesBase):
|
|
||||||
base = "/api/admin/users"
|
|
||||||
|
|
||||||
|
|
||||||
class RoutesUsers(RoutesBase):
|
|
||||||
base = "/api/users"
|
|
||||||
self = f"{base}/self"
|
|
||||||
|
|
||||||
|
|
||||||
class RoutesSeeders(RoutesBase):
|
|
||||||
base = "/api/groups/seeders"
|
|
||||||
|
|
||||||
foods = f"{base}/foods"
|
|
||||||
units = f"{base}/units"
|
|
||||||
labels = f"{base}/labels"
|
|
6
tests/utils/routes/__init__.py
Normal file
6
tests/utils/routes/__init__.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from . import routes_admin as admin
|
||||||
|
from . import routes_organizers as organizers
|
||||||
|
from . import routes_recipes as recipes
|
||||||
|
from . import routes_seeders as seeders
|
||||||
|
from . import routes_user as user
|
||||||
|
from ._base import RoutesBase
|
17
tests/utils/routes/_base.py
Normal file
17
tests/utils/routes/_base.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
from pydantic import UUID4
|
||||||
|
|
||||||
|
|
||||||
|
def v1(route: str) -> str:
|
||||||
|
return f"/api{route}"
|
||||||
|
|
||||||
|
|
||||||
|
class RoutesBase:
|
||||||
|
prefix = "/api"
|
||||||
|
base = f"{prefix}/"
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
raise NotImplementedError("This class is not meant to be instantiated.")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def item(cls, item_id: int | str | UUID4) -> str:
|
||||||
|
return f"{cls.base}/{item_id}"
|
5
tests/utils/routes/routes_admin.py
Normal file
5
tests/utils/routes/routes_admin.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from ._base import RoutesBase, v1
|
||||||
|
|
||||||
|
|
||||||
|
class AdminUsers(RoutesBase):
|
||||||
|
base = v1("/admin/users")
|
19
tests/utils/routes/routes_organizers.py
Normal file
19
tests/utils/routes/routes_organizers.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
from ._base import RoutesBase, v1
|
||||||
|
|
||||||
|
|
||||||
|
class RoutesOrganizerBase(RoutesBase):
|
||||||
|
@classmethod
|
||||||
|
def slug(cls, slug: str) -> str:
|
||||||
|
return f"{cls.base}/slug/{slug}"
|
||||||
|
|
||||||
|
|
||||||
|
class Tools(RoutesOrganizerBase):
|
||||||
|
base = v1("/organizers/tools")
|
||||||
|
|
||||||
|
|
||||||
|
class Tags(RoutesOrganizerBase):
|
||||||
|
base = v1("/organizers/tags")
|
||||||
|
|
||||||
|
|
||||||
|
class Categories(RoutesOrganizerBase):
|
||||||
|
base = v1("/organizers/categories")
|
13
tests/utils/routes/routes_recipes.py
Normal file
13
tests/utils/routes/routes_recipes.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
from ._base import RoutesBase, v1
|
||||||
|
|
||||||
|
|
||||||
|
class Foods(RoutesBase):
|
||||||
|
base = v1("/foods")
|
||||||
|
|
||||||
|
|
||||||
|
class Units(RoutesBase):
|
||||||
|
base = v1("/units")
|
||||||
|
|
||||||
|
|
||||||
|
class Recipe(RoutesBase):
|
||||||
|
base = v1("/recipes")
|
9
tests/utils/routes/routes_seeders.py
Normal file
9
tests/utils/routes/routes_seeders.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
from ._base import RoutesBase, v1
|
||||||
|
|
||||||
|
|
||||||
|
class Seeders(RoutesBase):
|
||||||
|
base = v1("/groups/seeders")
|
||||||
|
|
||||||
|
foods = f"{base}/foods"
|
||||||
|
units = f"{base}/units"
|
||||||
|
labels = f"{base}/labels"
|
6
tests/utils/routes/routes_user.py
Normal file
6
tests/utils/routes/routes_user.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from ._base import RoutesBase, v1
|
||||||
|
|
||||||
|
|
||||||
|
class Users(RoutesBase):
|
||||||
|
base = v1("/users")
|
||||||
|
self = f"{base}/self"
|
Loading…
x
Reference in New Issue
Block a user