mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-08-05 13:35:23 +02:00
feat: recipe timeline backend api (#1685)
* added recipe_timeline_events table to db * added schema and routes for recipe timeline events * added missing mixin and fixed update schema * added tests * adjusted migration revision tree * updated alembic revision test * added initial timeline event for new recipes * added additional tests * added event bus support * renamed event_dt to timestamp * add timeline_events to ignore list * run code-gen * use new test routes implementation * use doc string syntax * moved event type enum from db to schema Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com>
This commit is contained in:
parent
714a080ecb
commit
6ee64535df
19 changed files with 639 additions and 6 deletions
|
@ -0,0 +1,252 @@
|
|||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from mealie.schema.recipe.recipe import Recipe
|
||||
from mealie.schema.recipe.recipe_timeline_events import RecipeTimelineEventOut, RecipeTimelineEventPagination
|
||||
from tests.utils import api_routes
|
||||
from tests.utils.factories import random_string
|
||||
from tests.utils.fixture_schemas import TestUser
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def recipes(api_client: TestClient, unique_user: TestUser):
|
||||
recipes = []
|
||||
for _ in range(3):
|
||||
data = {"name": random_string(10)}
|
||||
response = api_client.post(api_routes.recipes, json=data, headers=unique_user.token)
|
||||
|
||||
assert response.status_code == 201
|
||||
slug = response.json()
|
||||
|
||||
response = api_client.get(f"{api_routes.recipes}/{slug}", headers=unique_user.token)
|
||||
assert response.status_code == 200
|
||||
|
||||
recipe = Recipe.parse_obj(response.json())
|
||||
recipes.append(recipe)
|
||||
|
||||
yield recipes
|
||||
response = api_client.delete(f"{api_routes.recipes}/{slug}", headers=unique_user.token)
|
||||
|
||||
|
||||
def test_create_timeline_event(api_client: TestClient, unique_user: TestUser, recipes: list[Recipe]):
|
||||
recipe = recipes[0]
|
||||
new_event = {
|
||||
"user_id": unique_user.user_id,
|
||||
"subject": random_string(),
|
||||
"event_type": "info",
|
||||
"message": random_string(),
|
||||
}
|
||||
|
||||
event_response = api_client.post(
|
||||
api_routes.recipes_slug_timeline_events(recipe.slug),
|
||||
json=new_event,
|
||||
headers=unique_user.token,
|
||||
)
|
||||
assert event_response.status_code == 201
|
||||
|
||||
event = RecipeTimelineEventOut.parse_obj(event_response.json())
|
||||
assert event.recipe_id == recipe.id
|
||||
assert str(event.user_id) == str(unique_user.user_id)
|
||||
|
||||
|
||||
def test_get_all_timeline_events(api_client: TestClient, unique_user: TestUser, recipes: list[Recipe]):
|
||||
# create some events
|
||||
recipe = recipes[0]
|
||||
events_data = [
|
||||
{
|
||||
"user_id": unique_user.user_id,
|
||||
"subject": random_string(),
|
||||
"event_type": "info",
|
||||
"message": random_string(),
|
||||
}
|
||||
for _ in range(10)
|
||||
]
|
||||
|
||||
events: list[RecipeTimelineEventOut] = []
|
||||
for event_data in events_data:
|
||||
event_response = api_client.post(
|
||||
api_routes.recipes_slug_timeline_events(recipe.slug), json=event_data, headers=unique_user.token
|
||||
)
|
||||
events.append(RecipeTimelineEventOut.parse_obj(event_response.json()))
|
||||
|
||||
# check that we see them all
|
||||
params = {"page": 1, "perPage": -1}
|
||||
|
||||
events_response = api_client.get(
|
||||
api_routes.recipes_slug_timeline_events(recipe.slug), params=params, headers=unique_user.token
|
||||
)
|
||||
events_pagination = RecipeTimelineEventPagination.parse_obj(events_response.json())
|
||||
|
||||
event_ids = [event.id for event in events]
|
||||
paginated_event_ids = [event.id for event in events_pagination.items]
|
||||
|
||||
assert len(event_ids) <= len(paginated_event_ids)
|
||||
for event_id in event_ids:
|
||||
assert event_id in paginated_event_ids
|
||||
|
||||
|
||||
def test_get_timeline_event(api_client: TestClient, unique_user: TestUser, recipes: list[Recipe]):
|
||||
# create an event
|
||||
recipe = recipes[0]
|
||||
new_event_data = {
|
||||
"user_id": unique_user.user_id,
|
||||
"subject": random_string(),
|
||||
"event_type": "info",
|
||||
"message": random_string(),
|
||||
}
|
||||
|
||||
event_response = api_client.post(
|
||||
api_routes.recipes_slug_timeline_events(recipe.slug),
|
||||
json=new_event_data,
|
||||
headers=unique_user.token,
|
||||
)
|
||||
new_event = RecipeTimelineEventOut.parse_obj(event_response.json())
|
||||
|
||||
# fetch the new event
|
||||
event_response = api_client.get(
|
||||
api_routes.recipes_slug_timeline_events_item_id(recipe.slug, new_event.id), headers=unique_user.token
|
||||
)
|
||||
assert event_response.status_code == 200
|
||||
|
||||
event = RecipeTimelineEventOut.parse_obj(event_response.json())
|
||||
assert event == new_event
|
||||
|
||||
|
||||
def test_update_timeline_event(api_client: TestClient, unique_user: TestUser, recipes: list[Recipe]):
|
||||
old_subject = random_string()
|
||||
new_subject = random_string()
|
||||
|
||||
# create an event
|
||||
recipe = recipes[0]
|
||||
new_event_data = {
|
||||
"user_id": unique_user.user_id,
|
||||
"subject": old_subject,
|
||||
"event_type": "info",
|
||||
}
|
||||
|
||||
event_response = api_client.post(
|
||||
api_routes.recipes_slug_timeline_events(recipe.slug), json=new_event_data, headers=unique_user.token
|
||||
)
|
||||
new_event = RecipeTimelineEventOut.parse_obj(event_response.json())
|
||||
assert new_event.subject == old_subject
|
||||
|
||||
# update the event
|
||||
updated_event_data = {"subject": new_subject}
|
||||
|
||||
event_response = api_client.put(
|
||||
api_routes.recipes_slug_timeline_events_item_id(recipe.slug, new_event.id),
|
||||
json=updated_event_data,
|
||||
headers=unique_user.token,
|
||||
)
|
||||
assert event_response.status_code == 200
|
||||
|
||||
updated_event = RecipeTimelineEventOut.parse_obj(event_response.json())
|
||||
assert new_event.id == updated_event.id
|
||||
assert updated_event.subject == new_subject
|
||||
|
||||
|
||||
def test_delete_timeline_event(api_client: TestClient, unique_user: TestUser, recipes: list[Recipe]):
|
||||
# create an event
|
||||
recipe = recipes[0]
|
||||
new_event_data = {
|
||||
"user_id": unique_user.user_id,
|
||||
"subject": random_string(),
|
||||
"event_type": "info",
|
||||
"message": random_string(),
|
||||
}
|
||||
|
||||
event_response = api_client.post(
|
||||
api_routes.recipes_slug_timeline_events(recipe.slug), json=new_event_data, headers=unique_user.token
|
||||
)
|
||||
new_event = RecipeTimelineEventOut.parse_obj(event_response.json())
|
||||
|
||||
# delete the event
|
||||
event_response = api_client.delete(
|
||||
api_routes.recipes_slug_timeline_events_item_id(recipe.slug, new_event.id), headers=unique_user.token
|
||||
)
|
||||
assert event_response.status_code == 200
|
||||
|
||||
deleted_event = RecipeTimelineEventOut.parse_obj(event_response.json())
|
||||
assert deleted_event.id == new_event.id
|
||||
|
||||
# try to get the event
|
||||
event_response = api_client.get(
|
||||
api_routes.recipes_slug_timeline_events_item_id(recipe.slug, deleted_event.id), headers=unique_user.token
|
||||
)
|
||||
assert event_response.status_code == 404
|
||||
|
||||
|
||||
def test_create_recipe_with_timeline_event(api_client: TestClient, unique_user: TestUser, recipes: list[Recipe]):
|
||||
# make sure when the recipes fixture was created that all recipes have at least one event
|
||||
for recipe in recipes:
|
||||
events_response = api_client.get(
|
||||
api_routes.recipes_slug_timeline_events(recipe.slug), headers=unique_user.token
|
||||
)
|
||||
events_pagination = RecipeTimelineEventPagination.parse_obj(events_response.json())
|
||||
assert events_pagination.items
|
||||
|
||||
|
||||
def test_invalid_recipe_slug(api_client: TestClient, unique_user: TestUser):
|
||||
new_event_data = {
|
||||
"user_id": unique_user.user_id,
|
||||
"subject": random_string(),
|
||||
"event_type": "info",
|
||||
"message": random_string(),
|
||||
}
|
||||
|
||||
event_response = api_client.post(
|
||||
api_routes.recipes_slug_timeline_events(random_string()), json=new_event_data, headers=unique_user.token
|
||||
)
|
||||
assert event_response.status_code == 404
|
||||
|
||||
|
||||
def test_recipe_slug_mismatch(api_client: TestClient, unique_user: TestUser, recipes: list[Recipe]):
|
||||
# get new recipes
|
||||
recipe = recipes[0]
|
||||
invalid_recipe = recipes[1]
|
||||
|
||||
# create a new event
|
||||
new_event_data = {
|
||||
"user_id": unique_user.user_id,
|
||||
"subject": random_string(),
|
||||
"event_type": "info",
|
||||
"message": random_string(),
|
||||
}
|
||||
|
||||
event_response = api_client.post(
|
||||
api_routes.recipes_slug_timeline_events(recipe.slug), json=new_event_data, headers=unique_user.token
|
||||
)
|
||||
event = RecipeTimelineEventOut.parse_obj(event_response.json())
|
||||
|
||||
# try to perform operations on the event using the wrong recipe
|
||||
event_response = api_client.get(
|
||||
api_routes.recipes_slug_timeline_events_item_id(invalid_recipe.slug, event.id),
|
||||
json=new_event_data,
|
||||
headers=unique_user.token,
|
||||
)
|
||||
assert event_response.status_code == 404
|
||||
|
||||
event_response = api_client.put(
|
||||
api_routes.recipes_slug_timeline_events_item_id(invalid_recipe.slug, event.id),
|
||||
json=new_event_data,
|
||||
headers=unique_user.token,
|
||||
)
|
||||
assert event_response.status_code == 404
|
||||
|
||||
event_response = api_client.delete(
|
||||
api_routes.recipes_slug_timeline_events_item_id(invalid_recipe.slug, event.id),
|
||||
json=new_event_data,
|
||||
headers=unique_user.token,
|
||||
)
|
||||
assert event_response.status_code == 404
|
||||
|
||||
# make sure the event still exists and is unmodified
|
||||
event_response = api_client.get(
|
||||
api_routes.recipes_slug_timeline_events_item_id(recipe.slug, event.id),
|
||||
json=new_event_data,
|
||||
headers=unique_user.token,
|
||||
)
|
||||
assert event_response.status_code == 200
|
||||
|
||||
existing_event = RecipeTimelineEventOut.parse_obj(event_response.json())
|
||||
assert existing_event == event
|
Loading…
Add table
Add a link
Reference in a new issue