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

feat: Add Households to Mealie (#3970)

This commit is contained in:
Michael Genson 2024-08-22 10:14:32 -05:00 committed by GitHub
parent 0c29cef17d
commit eb170cc7e5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
315 changed files with 6975 additions and 3577 deletions

View file

@ -16,9 +16,8 @@ from tests.utils.fixture_schemas import TestUser
@pytest.fixture(scope="function")
def ten_slugs(
api_client: TestClient, unique_user: TestUser, database: AllRepositories
) -> Generator[list[str], None, None]:
def ten_slugs(api_client: TestClient, unique_user: TestUser) -> Generator[list[str], None, None]:
database = unique_user.repos
slugs: list[str] = []
for _ in range(10):
@ -38,9 +37,9 @@ def ten_slugs(
pass
def test_bulk_tag_recipes(
api_client: TestClient, unique_user: TestUser, database: AllRepositories, ten_slugs: list[str]
):
def test_bulk_tag_recipes(api_client: TestClient, unique_user: TestUser, ten_slugs: list[str]):
database = unique_user.repos
# Setup Tags
tags = []
for _ in range(3):
@ -66,9 +65,10 @@ def test_bulk_tag_recipes(
def test_bulk_categorize_recipes(
api_client: TestClient,
unique_user: TestUser,
database: AllRepositories,
ten_slugs: list[str],
):
database = unique_user.repos
# Setup Tags
categories = []
for _ in range(3):
@ -94,9 +94,9 @@ def test_bulk_categorize_recipes(
def test_bulk_delete_recipes(
api_client: TestClient,
unique_user: TestUser,
database: AllRepositories,
ten_slugs: list[str],
):
database = unique_user.repos
payload = {"recipes": ten_slugs}
response = api_client.post(api_routes.recipes_bulk_actions_delete, json=payload, headers=unique_user.token)

View file

@ -1,7 +1,10 @@
from uuid import UUID
import pytest
from fastapi.testclient import TestClient
from pydantic import UUID4
from mealie.repos.repository_factory import AllRepositories
from mealie.schema.recipe.recipe import Recipe
from tests.utils import api_routes
from tests.utils.factories import random_string
@ -39,7 +42,7 @@ def test_create_comment(api_client: TestClient, unique_recipe: Recipe, unique_us
assert response_data["recipeId"] == str(unique_recipe.id)
assert response_data["text"] == create_data["text"]
assert response_data["userId"] == unique_user.user_id
assert response_data["userId"] == str(unique_user.user_id)
# Check for Proper Association
response = api_client.get(api_routes.recipes_slug_comments(unique_recipe.slug), headers=unique_user.token)
@ -50,7 +53,7 @@ def test_create_comment(api_client: TestClient, unique_recipe: Recipe, unique_us
assert len(response_data) == 1
assert response_data[0]["recipeId"] == str(unique_recipe.id)
assert response_data[0]["text"] == create_data["text"]
assert response_data[0]["userId"] == unique_user.user_id
assert response_data[0]["userId"] == str(unique_user.user_id)
def test_update_comment(api_client: TestClient, unique_recipe: Recipe, unique_user: TestUser):
@ -73,7 +76,7 @@ def test_update_comment(api_client: TestClient, unique_recipe: Recipe, unique_us
assert response_data["recipeId"] == str(unique_recipe.id)
assert response_data["text"] == update_data["text"]
assert response_data["userId"] == unique_user.user_id
assert response_data["userId"] == str(unique_user.user_id)
def test_delete_comment(api_client: TestClient, unique_recipe: Recipe, unique_user: TestUser):
@ -93,7 +96,20 @@ def test_delete_comment(api_client: TestClient, unique_recipe: Recipe, unique_us
assert response.status_code == 404
def test_admin_can_delete(api_client: TestClient, unique_recipe: Recipe, unique_user: TestUser, admin_user: TestUser):
def test_admin_can_delete(
unfiltered_database: AllRepositories,
api_client: TestClient,
unique_recipe: Recipe,
unique_user: TestUser,
admin_user: TestUser,
):
# Make sure admin belongs to same group/household as user
admin_data = unfiltered_database.users.get_one(admin_user.user_id)
assert admin_data
admin_data.group_id = UUID(unique_user.group_id)
admin_data.household_id = UUID(unique_user.household_id)
unfiltered_database.users.update(admin_user.user_id, admin_data)
# Create Comment
create_data = random_comment(unique_recipe.id)
response = api_client.post(api_routes.comments, json=create_data, headers=unique_user.token)

View file

@ -9,10 +9,10 @@ from pathlib import Path
from uuid import uuid4
from zipfile import ZipFile
from httpx import Response
import pytest
from bs4 import BeautifulSoup
from fastapi.testclient import TestClient
from httpx import Response
from pytest import MonkeyPatch
from recipe_scrapers._abstract import AbstractScraper
from recipe_scrapers._schemaorg import SchemaOrg
@ -20,14 +20,13 @@ from recipe_scrapers.plugins import SchemaOrgFillPlugin
from slugify import slugify
from mealie.pkgs.safehttp.transport import AsyncSafeTransport
from mealie.repos.repository_factory import AllRepositories
from mealie.schema.recipe.recipe import Recipe, RecipeCategory, RecipeSummary, RecipeTag
from mealie.schema.recipe.recipe_category import CategorySave, TagSave
from mealie.schema.recipe.recipe_notes import RecipeNote
from mealie.schema.recipe.recipe_tool import RecipeToolSave
from mealie.services.recipe.recipe_data_service import RecipeDataService
from mealie.services.scraper.recipe_scraper import DEFAULT_SCRAPER_STRATEGIES
from tests import data, utils
from tests import utils
from tests.utils import api_routes
from tests.utils.factories import random_int, random_string
from tests.utils.fixture_schemas import TestUser
@ -82,7 +81,7 @@ def get_init(html_path: Path):
current_method = getattr(self.__class__, name)
current_method = SchemaOrgFillPlugin.run(current_method)
setattr(self.__class__, name, current_method)
setattr(self.__class__, "plugins_initialized", True)
self.__class__.plugins_initialized = True
return init_override
@ -162,7 +161,8 @@ def test_create_by_url(
assert tag["name"] in expected_tags
def test_create_recipe_from_zip(database: AllRepositories, api_client: TestClient, unique_user: TestUser, tempdir: str):
def test_create_recipe_from_zip(api_client: TestClient, unique_user: TestUser, tempdir: str):
database = unique_user.repos
recipe_name = random_string()
recipe = RecipeSummary(
id=uuid4(),
@ -181,9 +181,8 @@ def test_create_recipe_from_zip(database: AllRepositories, api_client: TestClien
assert fetched_recipe
def test_create_recipe_from_zip_invalid_group(
database: AllRepositories, api_client: TestClient, unique_user: TestUser, tempdir: str
):
def test_create_recipe_from_zip_invalid_group(api_client: TestClient, unique_user: TestUser, tempdir: str):
database = unique_user.repos
recipe_name = random_string()
recipe = RecipeSummary(
id=uuid4(),
@ -205,9 +204,8 @@ def test_create_recipe_from_zip_invalid_group(
assert str(fetched_recipe.group_id) == str(unique_user.group_id)
def test_create_recipe_from_zip_invalid_user(
database: AllRepositories, api_client: TestClient, unique_user: TestUser, tempdir: str
):
def test_create_recipe_from_zip_invalid_user(api_client: TestClient, unique_user: TestUser, tempdir: str):
database = unique_user.repos
recipe_name = random_string()
recipe = RecipeSummary(
id=uuid4(),
@ -229,10 +227,9 @@ def test_create_recipe_from_zip_invalid_user(
assert str(fetched_recipe.user_id) == str(unique_user.user_id)
def test_create_recipe_from_zip_existing_category(
database: AllRepositories, api_client: TestClient, unique_user: TestUser, tempdir: str
):
categories = database.categories.by_group(unique_user.group_id).create_many(
def test_create_recipe_from_zip_existing_category(api_client: TestClient, unique_user: TestUser, tempdir: str):
database = unique_user.repos
categories = database.categories.create_many(
[{"name": random_string(), "group_id": unique_user.group_id} for _ in range(random_int(5, 10))]
)
category = random.choice(categories)
@ -259,10 +256,9 @@ def test_create_recipe_from_zip_existing_category(
assert str(fetched_recipe.recipe_category[0].id) == str(category.id)
def test_create_recipe_from_zip_existing_tag(
database: AllRepositories, api_client: TestClient, unique_user: TestUser, tempdir: str
):
tags = database.tags.by_group(unique_user.group_id).create_many(
def test_create_recipe_from_zip_existing_tag(api_client: TestClient, unique_user: TestUser, tempdir: str):
database = unique_user.repos
tags = database.tags.create_many(
[{"name": random_string(), "group_id": unique_user.group_id} for _ in range(random_int(5, 10))]
)
tag = random.choice(tags)
@ -290,9 +286,10 @@ def test_create_recipe_from_zip_existing_tag(
def test_create_recipe_from_zip_existing_category_wrong_ids(
database: AllRepositories, api_client: TestClient, unique_user: TestUser, tempdir: str
api_client: TestClient, unique_user: TestUser, tempdir: str
):
categories = database.categories.by_group(unique_user.group_id).create_many(
database = unique_user.repos
categories = database.categories.create_many(
[{"name": random_string(), "group_id": unique_user.group_id} for _ in range(random_int(5, 10))]
)
category = random.choice(categories)
@ -320,10 +317,9 @@ def test_create_recipe_from_zip_existing_category_wrong_ids(
assert str(fetched_recipe.recipe_category[0].id) == str(category.id)
def test_create_recipe_from_zip_existing_tag_wrong_ids(
database: AllRepositories, api_client: TestClient, unique_user: TestUser, tempdir: str
):
tags = database.tags.by_group(unique_user.group_id).create_many(
def test_create_recipe_from_zip_existing_tag_wrong_ids(api_client: TestClient, unique_user: TestUser, tempdir: str):
database = unique_user.repos
tags = database.tags.create_many(
[{"name": random_string(), "group_id": unique_user.group_id} for _ in range(random_int(5, 10))]
)
tag = random.choice(tags)
@ -351,9 +347,8 @@ def test_create_recipe_from_zip_existing_tag_wrong_ids(
assert str(fetched_recipe.tags[0].id) == str(tag.id)
def test_create_recipe_from_zip_invalid_category(
database: AllRepositories, api_client: TestClient, unique_user: TestUser, tempdir: str
):
def test_create_recipe_from_zip_invalid_category(api_client: TestClient, unique_user: TestUser, tempdir: str):
database = unique_user.repos
invalid_name = random_string()
invalid_category = RecipeCategory(id=uuid4(), name=invalid_name, slug=invalid_name)
@ -382,9 +377,8 @@ def test_create_recipe_from_zip_invalid_category(
assert fetched_recipe.recipe_category[0].slug == invalid_name
def test_create_recipe_from_zip_invalid_tag(
database: AllRepositories, api_client: TestClient, unique_user: TestUser, tempdir: str
):
def test_create_recipe_from_zip_invalid_tag(api_client: TestClient, unique_user: TestUser, tempdir: str):
database = unique_user.repos
invalid_name = random_string()
invalid_tag = RecipeTag(id=uuid4(), name=invalid_name, slug=invalid_name)
@ -713,17 +707,15 @@ def test_get_recipe_by_slug_or_id(api_client: TestClient, unique_user: utils.Tes
@pytest.mark.parametrize("organizer_type", ["tags", "categories", "tools"])
def test_get_recipes_organizer_filter(
api_client: TestClient, unique_user: utils.TestUser, organizer_type: str, database: AllRepositories
):
def test_get_recipes_organizer_filter(api_client: TestClient, unique_user: utils.TestUser, organizer_type: str):
database = unique_user.repos
# create recipes with different organizers
tags = database.tags.by_group(unique_user.group_id).create_many(
[TagSave(name=random_string(), group_id=unique_user.group_id) for _ in range(3)]
)
categories = database.categories.by_group(unique_user.group_id).create_many(
tags = database.tags.create_many([TagSave(name=random_string(), group_id=unique_user.group_id) for _ in range(3)])
categories = database.categories.create_many(
[CategorySave(name=random_string(), group_id=unique_user.group_id) for _ in range(3)]
)
tools = database.tools.by_group(unique_user.group_id).create_many(
tools = database.tools.create_many(
[RecipeToolSave(name=random_string(), group_id=unique_user.group_id) for _ in range(3)]
)
@ -743,7 +735,7 @@ def test_get_recipes_organizer_filter(
)
)
recipes = database.recipes.by_group(unique_user.group_id).create_many(new_recipes_data) # type: ignore
recipes = database.recipes.create_many(new_recipes_data) # type: ignore
# get recipes by organizer
if organizer_type == "tags":

View file

@ -14,8 +14,8 @@ def test_ownership_on_new_with_admin(api_client: TestClient, admin_user: TestUse
recipe = api_client.get(api_routes.recipes + f"/{recipe_name}", headers=admin_user.token).json()
assert recipe["userId"] == admin_user.user_id
assert recipe["groupId"] == admin_user.group_id
assert recipe["userId"] == str(admin_user.user_id)
assert recipe["groupId"] == str(admin_user.group_id)
def test_ownership_on_new_with_user(api_client: TestClient, g2_user: TestUser):
@ -29,8 +29,8 @@ def test_ownership_on_new_with_user(api_client: TestClient, g2_user: TestUser):
recipe = response.json()
assert recipe["userId"] == g2_user.user_id
assert recipe["groupId"] == g2_user.group_id
assert recipe["userId"] == str(g2_user.user_id)
assert recipe["groupId"] == str(g2_user.group_id)
def test_get_all_only_includes_group_recipes(api_client: TestClient, unique_user: TestUser):
@ -47,8 +47,8 @@ def test_get_all_only_includes_group_recipes(api_client: TestClient, unique_user
assert len(recipes) == 5
for recipe in recipes:
assert recipe["groupId"] == unique_user.group_id
assert recipe["userId"] == unique_user.user_id
assert recipe["groupId"] == str(unique_user.group_id)
assert recipe["userId"] == str(unique_user.user_id)
def test_unique_slug_by_group(api_client: TestClient, unique_user: TestUser, g2_user: TestUser) -> None:

View file

@ -1,11 +1,9 @@
import random
from collections.abc import Generator
from uuid import UUID
import pytest
from fastapi.testclient import TestClient
from mealie.repos.repository_factory import AllRepositories
from mealie.schema.recipe.recipe import Recipe
from mealie.schema.user.user import UserRatingUpdate
from tests.utils import api_routes
@ -14,9 +12,10 @@ from tests.utils.fixture_schemas import TestUser
@pytest.fixture(scope="function")
def recipes(database: AllRepositories, user_tuple: tuple[TestUser, TestUser]) -> Generator[list[Recipe], None, None]:
def recipes(user_tuple: tuple[TestUser, TestUser]) -> Generator[list[Recipe], None, None]:
unique_user = random.choice(user_tuple)
recipes_repo = database.recipes.by_group(UUID(unique_user.group_id))
database = unique_user.repos
recipes_repo = database.recipes
recipes: list[Recipe] = []
for _ in range(random_int(10, 20)):
@ -51,7 +50,6 @@ def test_user_recipe_favorites(
unique_user = user_tuple[1]
response = api_client.get(api_routes.users_id_favorites(unique_user.user_id), headers=unique_user.token)
assert response.json()["ratings"] == []
recipes_to_favorite = random.sample(recipes, random_int(5, len(recipes)))
@ -89,7 +87,11 @@ def test_user_recipe_favorites(
assert len(ratings) == len(recipes_to_favorite) - len(recipe_favorites_to_remove)
fetched_recipe_ids = {rating["recipeId"] for rating in ratings}
removed_recipe_ids = {str(recipe.id) for recipe in recipe_favorites_to_remove}
assert fetched_recipe_ids == favorited_recipe_ids - removed_recipe_ids
for recipe_id in removed_recipe_ids:
assert recipe_id not in fetched_recipe_ids
for recipe_id in fetched_recipe_ids:
assert recipe_id in favorited_recipe_ids
@pytest.mark.parametrize("add_favorite", [True, False])
@ -119,8 +121,6 @@ def test_set_user_recipe_ratings(
unique_user = user_tuple[1]
response = api_client.get(api_routes.users_id_ratings(unique_user.user_id), headers=unique_user.token)
assert response.json()["ratings"] == []
recipes_to_rate = random.sample(recipes, random_int(8, len(recipes)))
expected_ratings_by_recipe_id: dict[str, UserRatingUpdate] = {}
@ -144,12 +144,16 @@ def test_set_user_recipe_ratings(
response = api_client.get(get_url, headers=unique_user.token)
ratings = response.json()["ratings"]
assert len(ratings) == len(recipes_to_rate)
for rating in ratings:
recipe_id = rating["recipeId"]
assert rating["rating"] == expected_ratings_by_recipe_id[recipe_id].rating
if recipe_id not in expected_ratings_by_recipe_id:
continue
assert rating["rating"] == expected_ratings_by_recipe_id.pop(recipe_id).rating
assert not rating["isFavorite"]
assert not expected_ratings_by_recipe_id # we should have popped all of them
def test_set_user_rating_invalid_recipe_404(api_client: TestClient, user_tuple: tuple[TestUser, TestUser]):
unique_user = random.choice(user_tuple)
@ -289,9 +293,10 @@ def test_set_rating_to_zero(api_client: TestClient, user_tuple: tuple[TestUser,
def test_delete_recipe_deletes_ratings(
database: AllRepositories, api_client: TestClient, user_tuple: tuple[TestUser, TestUser], recipes: list[Recipe]
api_client: TestClient, user_tuple: tuple[TestUser, TestUser], recipes: list[Recipe]
):
unique_user = random.choice(user_tuple)
database = unique_user.repos
recipe = random.choice(recipes)
rating = UserRatingUpdate(rating=random.uniform(1, 5), is_favorite=random.choice([True, False, None]))
response = api_client.post(
@ -306,6 +311,7 @@ def test_delete_recipe_deletes_ratings(
assert response.json()
database.recipes.delete(recipe.id, match_key="id")
database.session.commit()
response = api_client.get(api_routes.users_self_ratings_recipe_id(recipe.id), headers=unique_user.token)
assert response.status_code == 404

View file

@ -4,15 +4,15 @@ 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 mealie.schema.recipe.recipe_share_token import RecipeShareToken, RecipeShareTokenSave
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 slug(api_client: TestClient, unique_user: TestUser, database: AllRepositories) -> Generator[str, None, None]:
def slug(api_client: TestClient, unique_user: TestUser) -> Generator[str, None, None]:
database = unique_user.repos
payload = {"name": random_string(length=20)}
response = api_client.post(api_routes.recipes, json=payload, headers=unique_user.token)
assert response.status_code == 201
@ -27,14 +27,13 @@ def slug(api_client: TestClient, unique_user: TestUser, database: AllRepositorie
pass
def test_recipe_share_tokens_get_all(
api_client: TestClient,
unique_user: TestUser,
database: AllRepositories,
slug: str,
):
def test_recipe_share_tokens_get_all(api_client: TestClient, unique_user: TestUser, slug: str):
database = unique_user.repos
# Create 5 Tokens
recipe = database.recipes.get_one(slug)
assert recipe
tokens = []
for _ in range(5):
token = database.recipe_share_tokens.create(
@ -50,14 +49,13 @@ def test_recipe_share_tokens_get_all(
assert len(response_data) == 5
def test_recipe_share_tokens_get_all_with_id(
api_client: TestClient,
unique_user: TestUser,
database: AllRepositories,
slug: str,
):
def test_recipe_share_tokens_get_all_with_id(api_client: TestClient, unique_user: TestUser, slug: str):
database = unique_user.repos
# Create 5 Tokens
recipe = database.recipes.get_one(slug)
assert recipe
tokens = []
for _ in range(3):
token = database.recipe_share_tokens.create(
@ -73,13 +71,10 @@ def test_recipe_share_tokens_get_all_with_id(
assert len(response_data) == 3
def test_recipe_share_tokens_create_and_get_one(
api_client: TestClient,
unique_user: TestUser,
database: AllRepositories,
slug: str,
):
def test_recipe_share_tokens_create_and_get_one(api_client: TestClient, unique_user: TestUser, slug: str):
database = unique_user.repos
recipe = database.recipes.get_one(slug)
assert recipe
payload = {
"recipeId": str(recipe.id),
@ -95,14 +90,13 @@ def test_recipe_share_tokens_create_and_get_one(
assert response_data["recipe"]["id"] == str(recipe.id)
def test_recipe_share_tokens_delete_one(
api_client: TestClient,
unique_user: TestUser,
database: AllRepositories,
slug: str,
):
def test_recipe_share_tokens_delete_one(api_client: TestClient, unique_user: TestUser, slug: str):
database = unique_user.repos
# Create Token
token: RecipeShareToken | None = None
recipe = database.recipes.get_one(slug)
assert recipe
token = database.recipe_share_tokens.create(
RecipeShareTokenSave(recipe_id=recipe.id, group_id=unique_user.group_id)

View file

@ -15,7 +15,7 @@ def test_associate_ingredient_with_step(api_client: TestClient, unique_user: Tes
# Associate an ingredient with a step
steps = {} # key=step_id, value=ingredient_id
for idx, step in enumerate(recipe.recipe_instructions):
for idx, step in enumerate(recipe.recipe_instructions or []):
ingredients = random.choices(recipe.recipe_ingredient, k=2)
step.ingredient_references = [
@ -39,7 +39,7 @@ def test_associate_ingredient_with_step(api_client: TestClient, unique_user: Tes
data: dict = json.loads(response.text)
for idx, stp in enumerate(data.get("recipeInstructions")):
for idx, stp in enumerate(data.get("recipeInstructions") or []):
all_refs = [ref["referenceId"] for ref in stp.get("ingredientReferences")]
assert len(all_refs) == 2

View file

@ -39,7 +39,7 @@ def test_create_timeline_event(api_client: TestClient, unique_user: TestUser, re
recipe = recipes[0]
new_event = {
"recipe_id": str(recipe.id),
"user_id": unique_user.user_id,
"user_id": str(unique_user.user_id),
"subject": random_string(),
"event_type": "info",
"message": random_string(),
@ -63,7 +63,7 @@ def test_get_all_timeline_events(api_client: TestClient, unique_user: TestUser,
events_data = [
{
"recipe_id": str(recipe.id),
"user_id": unique_user.user_id,
"user_id": str(unique_user.user_id),
"subject": random_string(),
"event_type": "info",
"message": random_string(),
@ -98,7 +98,7 @@ def test_get_timeline_event(api_client: TestClient, unique_user: TestUser, recip
recipe = recipes[0]
new_event_data = {
"recipe_id": str(recipe.id),
"user_id": unique_user.user_id,
"user_id": str(unique_user.user_id),
"subject": random_string(),
"event_type": "info",
"message": random_string(),
@ -127,7 +127,7 @@ def test_update_timeline_event(api_client: TestClient, unique_user: TestUser, re
recipe = recipes[0]
new_event_data = {
"recipe_id": str(recipe.id),
"user_id": unique_user.user_id,
"user_id": str(unique_user.user_id),
"subject": old_subject,
"event_type": "info",
}
@ -157,7 +157,7 @@ def test_delete_timeline_event(api_client: TestClient, unique_user: TestUser, re
recipe = recipes[0]
new_event_data = {
"recipe_id": str(recipe.id),
"user_id": unique_user.user_id,
"user_id": str(unique_user.user_id),
"subject": random_string(),
"event_type": "info",
"message": random_string(),
@ -187,7 +187,7 @@ def test_timeline_event_message_alias(api_client: TestClient, unique_user: TestU
recipe = recipes[0]
new_event_data = {
"recipeId": str(recipe.id),
"userId": unique_user.user_id,
"userId": str(unique_user.user_id),
"subject": random_string(),
"eventType": "info",
"eventMessage": random_string(), # eventMessage is the correct alias for the message
@ -234,7 +234,7 @@ def test_timeline_event_update_image(
recipe = recipes[0]
new_event_data = {
"recipe_id": str(recipe.id),
"user_id": unique_user.user_id,
"user_id": str(unique_user.user_id),
"subject": random_string(),
"message": random_string(),
"event_type": "info",
@ -281,7 +281,7 @@ def test_create_recipe_with_timeline_event(api_client: TestClient, unique_user:
def test_invalid_recipe_id(api_client: TestClient, unique_user: TestUser):
new_event_data = {
"recipe_id": str(uuid4()),
"user_id": unique_user.user_id,
"user_id": str(unique_user.user_id),
"subject": random_string(),
"event_type": "info",
"message": random_string(),