Add Mealie integration (#119678)

This commit is contained in:
Joost Lekkerkerker
2024-06-21 11:04:55 +02:00
committed by GitHub
parent dc6c1f4e87
commit 5138c3de0a
22 changed files with 1895 additions and 0 deletions

View File

@ -826,6 +826,8 @@ build.json @home-assistant/supervisor
/tests/components/matrix/ @PaarthShah
/homeassistant/components/matter/ @home-assistant/matter
/tests/components/matter/ @home-assistant/matter
/homeassistant/components/mealie/ @joostlek
/tests/components/mealie/ @joostlek
/homeassistant/components/meater/ @Sotolotl @emontnemery
/tests/components/meater/ @Sotolotl @emontnemery
/homeassistant/components/medcom_ble/ @elafargue

View File

@ -0,0 +1,33 @@
"""The Mealie integration."""
from __future__ import annotations
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from .coordinator import MealieCoordinator
PLATFORMS: list[Platform] = [Platform.CALENDAR]
type MealieConfigEntry = ConfigEntry[MealieCoordinator]
async def async_setup_entry(hass: HomeAssistant, entry: MealieConfigEntry) -> bool:
"""Set up Mealie from a config entry."""
coordinator = MealieCoordinator(hass)
await coordinator.async_config_entry_first_refresh()
entry.runtime_data = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, entry: MealieConfigEntry) -> bool:
"""Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

View File

@ -0,0 +1,81 @@
"""Calendar platform for Mealie."""
from __future__ import annotations
from datetime import datetime
from aiomealie import Mealplan, MealplanEntryType
from homeassistant.components.calendar import CalendarEntity, CalendarEvent
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import MealieConfigEntry, MealieCoordinator
from .entity import MealieEntity
async def async_setup_entry(
hass: HomeAssistant,
entry: MealieConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the calendar platform for entity."""
coordinator = entry.runtime_data
async_add_entities(
MealieMealplanCalendarEntity(coordinator, entry_type)
for entry_type in MealplanEntryType
)
def _get_event_from_mealplan(mealplan: Mealplan) -> CalendarEvent:
"""Create a CalendarEvent from a Mealplan."""
description: str | None = None
name = "No recipe"
if mealplan.recipe:
name = mealplan.recipe.name
description = mealplan.recipe.description
return CalendarEvent(
start=mealplan.mealplan_date,
end=mealplan.mealplan_date,
summary=name,
description=description,
)
class MealieMealplanCalendarEntity(MealieEntity, CalendarEntity):
"""A calendar entity."""
def __init__(
self, coordinator: MealieCoordinator, entry_type: MealplanEntryType
) -> None:
"""Create the Calendar entity."""
super().__init__(coordinator)
self._entry_type = entry_type
self._attr_translation_key = entry_type.name.lower()
self._attr_unique_id = (
f"{self.coordinator.config_entry.entry_id}_{entry_type.name.lower()}"
)
@property
def event(self) -> CalendarEvent | None:
"""Return the next upcoming event."""
mealplans = self.coordinator.data[self._entry_type]
if not mealplans:
return None
return _get_event_from_mealplan(mealplans[0])
async def async_get_events(
self, hass: HomeAssistant, start_date: datetime, end_date: datetime
) -> list[CalendarEvent]:
"""Get all events in a specific time frame."""
mealplans = (
await self.coordinator.client.get_mealplans(
start_date.date(), end_date.date()
)
).items
return [
_get_event_from_mealplan(mealplan)
for mealplan in mealplans
if mealplan.entry_type is self._entry_type
]

View File

@ -0,0 +1,55 @@
"""Config flow for Mealie."""
from typing import Any
from aiomealie import MealieAuthenticationError, MealieClient, MealieConnectionError
import voluptuous as vol
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_API_TOKEN, CONF_HOST
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import DOMAIN, LOGGER
SCHEMA = vol.Schema(
{
vol.Required(CONF_HOST): str,
vol.Required(CONF_API_TOKEN): str,
}
)
class MealieConfigFlow(ConfigFlow, domain=DOMAIN):
"""Mealie config flow."""
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle a flow initialized by the user."""
errors: dict[str, str] = {}
if user_input:
self._async_abort_entries_match({CONF_HOST: user_input[CONF_HOST]})
client = MealieClient(
user_input[CONF_HOST],
token=user_input[CONF_API_TOKEN],
session=async_get_clientsession(self.hass),
)
try:
await client.get_mealplan_today()
except MealieConnectionError:
errors["base"] = "cannot_connect"
except MealieAuthenticationError:
errors["base"] = "invalid_auth"
except Exception: # noqa: BLE001
LOGGER.exception("Unexpected error")
errors["base"] = "unknown"
else:
return self.async_create_entry(
title="Mealie",
data=user_input,
)
return self.async_show_form(
step_id="user",
data_schema=SCHEMA,
errors=errors,
)

View File

@ -0,0 +1,7 @@
"""Constants for the Mealie integration."""
import logging
DOMAIN = "mealie"
LOGGER = logging.getLogger(__package__)

View File

@ -0,0 +1,65 @@
"""Define an object to manage fetching Mealie data."""
from __future__ import annotations
from datetime import timedelta
from typing import TYPE_CHECKING
from aiomealie import (
MealieAuthenticationError,
MealieClient,
MealieConnectionError,
Mealplan,
MealplanEntryType,
)
from homeassistant.const import CONF_API_TOKEN, CONF_HOST
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryError
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
import homeassistant.util.dt as dt_util
from .const import LOGGER
if TYPE_CHECKING:
from . import MealieConfigEntry
WEEK = timedelta(days=7)
class MealieCoordinator(DataUpdateCoordinator[dict[MealplanEntryType, list[Mealplan]]]):
"""Class to manage fetching Mealie data."""
config_entry: MealieConfigEntry
def __init__(self, hass: HomeAssistant) -> None:
"""Initialize coordinator."""
super().__init__(
hass, logger=LOGGER, name="Mealie", update_interval=timedelta(hours=1)
)
self.client = MealieClient(
self.config_entry.data[CONF_HOST],
token=self.config_entry.data[CONF_API_TOKEN],
session=async_get_clientsession(hass),
)
async def _async_update_data(self) -> dict[MealplanEntryType, list[Mealplan]]:
next_week = dt_util.now() + WEEK
try:
data = (
await self.client.get_mealplans(dt_util.now().date(), next_week.date())
).items
except MealieAuthenticationError as error:
raise ConfigEntryError("Authentication failed") from error
except MealieConnectionError as error:
raise UpdateFailed(error) from error
res: dict[MealplanEntryType, list[Mealplan]] = {
MealplanEntryType.BREAKFAST: [],
MealplanEntryType.LUNCH: [],
MealplanEntryType.DINNER: [],
MealplanEntryType.SIDE: [],
}
for meal in data:
res[meal.entry_type].append(meal)
return res

View File

@ -0,0 +1,21 @@
"""Base class for Mealie entities."""
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN
from .coordinator import MealieCoordinator
class MealieEntity(CoordinatorEntity[MealieCoordinator]):
"""Defines a base Mealie entity."""
_attr_has_entity_name = True
def __init__(self, coordinator: MealieCoordinator) -> None:
"""Initialize Mealie entity."""
super().__init__(coordinator)
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, coordinator.config_entry.entry_id)},
entry_type=DeviceEntryType.SERVICE,
)

View File

@ -0,0 +1,10 @@
{
"domain": "mealie",
"name": "Mealie",
"codeowners": ["@joostlek"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/mealie",
"integration_type": "service",
"iot_class": "local_polling",
"requirements": ["aiomealie==0.3.1"]
}

View File

@ -0,0 +1,36 @@
{
"config": {
"step": {
"user": {
"data": {
"host": "[%key:common::config_flow::data::host%]",
"api_token": "[%key:common::config_flow::data::api_token%]"
}
}
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]"
}
},
"entity": {
"calendar": {
"breakfast": {
"name": "Breakfast"
},
"dinner": {
"name": "Dinner"
},
"lunch": {
"name": "Lunch"
},
"side": {
"name": "Side"
}
}
}
}

View File

@ -319,6 +319,7 @@ FLOWS = {
"lyric",
"mailgun",
"matter",
"mealie",
"meater",
"medcom_ble",
"media_extractor",

View File

@ -3505,6 +3505,12 @@
"config_flow": true,
"iot_class": "local_push"
},
"mealie": {
"name": "Mealie",
"integration_type": "service",
"config_flow": true,
"iot_class": "local_polling"
},
"meater": {
"name": "Meater",
"integration_type": "hub",

View File

@ -293,6 +293,9 @@ aiolookin==1.0.0
# homeassistant.components.lyric
aiolyric==1.1.0
# homeassistant.components.mealie
aiomealie==0.3.1
# homeassistant.components.modern_forms
aiomodernforms==0.1.8

View File

@ -266,6 +266,9 @@ aiolookin==1.0.0
# homeassistant.components.lyric
aiolyric==1.1.0
# homeassistant.components.mealie
aiomealie==0.3.1
# homeassistant.components.modern_forms
aiomodernforms==0.1.8

View File

@ -0,0 +1,13 @@
"""Tests for the Mealie integration."""
from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry
async def setup_integration(hass: HomeAssistant, config_entry: MockConfigEntry) -> None:
"""Fixture for setting up the component."""
config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()

View File

@ -0,0 +1,58 @@
"""Mealie tests configuration."""
from unittest.mock import patch
from aiomealie import Mealplan, MealplanResponse
from mashumaro.codecs.orjson import ORJSONDecoder
import pytest
from typing_extensions import Generator
from homeassistant.components.mealie.const import DOMAIN
from homeassistant.const import CONF_API_TOKEN, CONF_HOST
from tests.common import MockConfigEntry, load_fixture
from tests.components.smhi.common import AsyncMock
@pytest.fixture
def mock_setup_entry() -> Generator[AsyncMock]:
"""Override async_setup_entry."""
with patch(
"homeassistant.components.mealie.async_setup_entry",
return_value=True,
) as mock_setup_entry:
yield mock_setup_entry
@pytest.fixture
def mock_mealie_client() -> Generator[AsyncMock]:
"""Mock a Mealie client."""
with (
patch(
"homeassistant.components.mealie.coordinator.MealieClient",
autospec=True,
) as mock_client,
patch(
"homeassistant.components.mealie.config_flow.MealieClient",
new=mock_client,
),
):
client = mock_client.return_value
client.get_mealplans.return_value = MealplanResponse.from_json(
load_fixture("get_mealplans.json", DOMAIN)
)
client.get_mealplan_today.return_value = ORJSONDecoder(list[Mealplan]).decode(
load_fixture("get_mealplan_today.json", DOMAIN)
)
yield client
@pytest.fixture
def mock_config_entry() -> MockConfigEntry:
"""Mock a config entry."""
return MockConfigEntry(
domain=DOMAIN,
title="Mealie",
data={CONF_HOST: "demo.mealie.io", CONF_API_TOKEN: "token"},
entry_id="01J0BC4QM2YBRP6H5G933CETT7",
)

View File

@ -0,0 +1,253 @@
[
{
"date": "2024-01-21",
"entryType": "dinner",
"title": "",
"text": "",
"recipeId": "40393996-417e-4487-a081-28608a668826",
"id": 192,
"groupId": "0bf60b2e-ca89-42a9-94d4-8f67ca72b157",
"userId": "1ce8b5fe-04e8-4b80-aab1-d92c94685c6d",
"recipe": {
"id": "40393996-417e-4487-a081-28608a668826",
"userId": "1ce8b5fe-04e8-4b80-aab1-d92c94685c6d",
"groupId": "0bf60b2e-ca89-42a9-94d4-8f67ca72b157",
"name": "Cauliflower Salad",
"slug": "cauliflower-salad",
"image": "qLdv",
"recipeYield": "6 servings",
"totalTime": "2 Hours 35 Minutes",
"prepTime": "25 Minutes",
"cookTime": null,
"performTime": "10 Minutes",
"description": "This is a wonderful option for picnics and grill outs when you are looking for a new take on potato salad. This simple side salad made with cauliflower, peas, and hard boiled eggs can be made the day ahead and chilled until party time!",
"recipeCategory": [],
"tags": [],
"tools": [
{
"id": "6e199f62-8356-46cf-8f6f-ea923780a1e3",
"name": "Stove",
"slug": "stove",
"onHand": false
}
],
"rating": null,
"orgURL": "https://www.allrecipes.com/recipe/142152/cauliflower-salad/",
"dateAdded": "2023-12-29",
"dateUpdated": "2024-01-06T13:38:55.116185",
"createdAt": "2023-12-29T00:46:50.138612",
"updateAt": "2024-01-06T13:38:55.119029",
"lastMade": "2024-01-06T22:59:59"
}
},
{
"date": "2024-01-21",
"entryType": "dinner",
"title": "",
"text": "",
"recipeId": "872bb477-8d90-4025-98b0-07a9d0d9ce3a",
"id": 206,
"groupId": "0bf60b2e-ca89-42a9-94d4-8f67ca72b157",
"userId": "1ce8b5fe-04e8-4b80-aab1-d92c94685c6d",
"recipe": {
"id": "872bb477-8d90-4025-98b0-07a9d0d9ce3a",
"userId": "1ce8b5fe-04e8-4b80-aab1-d92c94685c6d",
"groupId": "0bf60b2e-ca89-42a9-94d4-8f67ca72b157",
"name": "15 Minute Cheesy Sausage & Veg Pasta",
"slug": "15-minute-cheesy-sausage-veg-pasta",
"image": "BeNc",
"recipeYield": "",
"totalTime": null,
"prepTime": null,
"cookTime": null,
"performTime": null,
"description": "Easy, cheesy, sausage pasta! In the whirlwind of mid-week mayhem, dinner doesnt have to be a chore this 15-minute pasta, featuring HECKs Chicken Italia Chipolatas is your ticket to a delicious and hassle-free mid-week meal.",
"recipeCategory": [],
"tags": [],
"tools": [],
"rating": null,
"orgURL": "https://www.annabelkarmel.com/recipes/15-minute-cheesy-sausage-veg-pasta/",
"dateAdded": "2024-01-01",
"dateUpdated": "2024-01-01T20:40:40.441381",
"createdAt": "2024-01-01T20:40:40.443048",
"updateAt": "2024-01-01T20:40:40.443050",
"lastMade": null
}
},
{
"date": "2024-01-21",
"entryType": "lunch",
"title": "",
"text": "",
"recipeId": "744a9831-fa56-4f61-9e12-fc5ebce58ed9",
"id": 207,
"groupId": "0bf60b2e-ca89-42a9-94d4-8f67ca72b157",
"userId": "1ce8b5fe-04e8-4b80-aab1-d92c94685c6d",
"recipe": {
"id": "744a9831-fa56-4f61-9e12-fc5ebce58ed9",
"userId": "1ce8b5fe-04e8-4b80-aab1-d92c94685c6d",
"groupId": "0bf60b2e-ca89-42a9-94d4-8f67ca72b157",
"name": "cake",
"slug": "cake",
"image": null,
"recipeYield": null,
"totalTime": null,
"prepTime": null,
"cookTime": null,
"performTime": null,
"description": "",
"recipeCategory": [],
"tags": [],
"tools": [],
"rating": null,
"orgURL": null,
"dateAdded": "2024-01-01",
"dateUpdated": "2024-01-01T14:39:11.214806",
"createdAt": "2024-01-01T14:39:11.216709",
"updateAt": "2024-01-01T14:39:11.216711",
"lastMade": null
}
},
{
"date": "2024-01-21",
"entryType": "lunch",
"title": "",
"text": "",
"recipeId": "27455eb2-31d3-4682-84ff-02a114bf293a",
"id": 208,
"groupId": "0bf60b2e-ca89-42a9-94d4-8f67ca72b157",
"userId": "1ce8b5fe-04e8-4b80-aab1-d92c94685c6d",
"recipe": {
"id": "27455eb2-31d3-4682-84ff-02a114bf293a",
"userId": "1ce8b5fe-04e8-4b80-aab1-d92c94685c6d",
"groupId": "0bf60b2e-ca89-42a9-94d4-8f67ca72b157",
"name": "Pomegranate chicken with almond couscous",
"slug": "pomegranate-chicken-with-almond-couscous",
"image": "lF4p",
"recipeYield": "4 servings",
"totalTime": "20 Minutes",
"prepTime": "5 Minutes",
"cookTime": null,
"performTime": "15 Minutes",
"description": "Jazz up chicken breasts in this fruity, sweetly spiced sauce with pomegranate seeds, toasted almonds and tagine paste",
"recipeCategory": [],
"tags": [],
"tools": [],
"rating": null,
"orgURL": "https://www.bbcgoodfood.com/recipes/pomegranate-chicken-almond-couscous",
"dateAdded": "2023-12-29",
"dateUpdated": "2023-12-29T08:29:03.178355",
"createdAt": "2023-12-29T08:29:03.180819",
"updateAt": "2023-12-29T08:29:03.180820",
"lastMade": null
}
},
{
"date": "2024-01-21",
"entryType": "dinner",
"title": "",
"text": "",
"recipeId": "4233330e-6947-4042-90b7-44c405b70714",
"id": 209,
"groupId": "0bf60b2e-ca89-42a9-94d4-8f67ca72b157",
"userId": "1ce8b5fe-04e8-4b80-aab1-d92c94685c6d",
"recipe": {
"id": "4233330e-6947-4042-90b7-44c405b70714",
"userId": "1ce8b5fe-04e8-4b80-aab1-d92c94685c6d",
"groupId": "0bf60b2e-ca89-42a9-94d4-8f67ca72b157",
"name": "Csirkés és tofus empanadas",
"slug": "csirkes-es-tofus-empanadas",
"image": "ALqz",
"recipeYield": "16 servings",
"totalTime": "95",
"prepTime": "40",
"cookTime": null,
"performTime": "15",
"description": "",
"recipeCategory": [],
"tags": [],
"tools": [],
"rating": null,
"orgURL": "https://streetkitchen.hu/street-kitchen/csirkes-es-tofus-empanadas/",
"dateAdded": "2023-12-29",
"dateUpdated": "2023-12-29T07:56:20.087496",
"createdAt": "2023-12-29T07:53:47.765573",
"updateAt": "2023-12-29T07:56:20.090890",
"lastMade": null
}
},
{
"date": "2024-01-21",
"entryType": "dinner",
"title": "",
"text": "",
"recipeId": "48f39d27-4b8e-4c14-bf36-4e1e6497e75e",
"id": 210,
"groupId": "0bf60b2e-ca89-42a9-94d4-8f67ca72b157",
"userId": "1ce8b5fe-04e8-4b80-aab1-d92c94685c6d",
"recipe": {
"id": "48f39d27-4b8e-4c14-bf36-4e1e6497e75e",
"userId": "1ce8b5fe-04e8-4b80-aab1-d92c94685c6d",
"groupId": "0bf60b2e-ca89-42a9-94d4-8f67ca72b157",
"name": "All-American Beef Stew Recipe",
"slug": "all-american-beef-stew-recipe",
"image": "356X",
"recipeYield": "6 servings",
"totalTime": "3 Hours 15 Minutes",
"prepTime": "5 Minutes",
"cookTime": null,
"performTime": "3 Hours 10 Minutes",
"description": "This All-American beef stew recipe includes tender beef coated in a rich, intense sauce and vegetables that bring complementary texture and flavor.",
"recipeCategory": [],
"tags": [
{
"id": "78318c97-75c7-4d06-95b6-51ef8f4a0257",
"name": "< 4 Hours",
"slug": "4-hours"
}
],
"tools": [],
"rating": null,
"orgURL": "https://www.seriouseats.com/all-american-beef-stew-recipe",
"dateAdded": "2024-01-20",
"dateUpdated": "2024-01-21T03:04:45.606075",
"createdAt": "2024-01-20T20:41:29.266390",
"updateAt": "2024-01-21T03:04:45.609563",
"lastMade": null
}
},
{
"date": "2024-01-21",
"entryType": "dinner",
"title": "",
"text": "",
"recipeId": "27455eb2-31d3-4682-84ff-02a114bf293a",
"id": 223,
"groupId": "0bf60b2e-ca89-42a9-94d4-8f67ca72b157",
"userId": "1ce8b5fe-04e8-4b80-aab1-d92c94685c6d",
"recipe": {
"id": "27455eb2-31d3-4682-84ff-02a114bf293a",
"userId": "1ce8b5fe-04e8-4b80-aab1-d92c94685c6d",
"groupId": "0bf60b2e-ca89-42a9-94d4-8f67ca72b157",
"name": "Pomegranate chicken with almond couscous",
"slug": "pomegranate-chicken-with-almond-couscous",
"image": "lF4p",
"recipeYield": "4 servings",
"totalTime": "20 Minutes",
"prepTime": "5 Minutes",
"cookTime": null,
"performTime": "15 Minutes",
"description": "Jazz up chicken breasts in this fruity, sweetly spiced sauce with pomegranate seeds, toasted almonds and tagine paste",
"recipeCategory": [],
"tags": [],
"tools": [],
"rating": null,
"orgURL": "https://www.bbcgoodfood.com/recipes/pomegranate-chicken-almond-couscous",
"dateAdded": "2023-12-29",
"dateUpdated": "2023-12-29T08:29:03.178355",
"createdAt": "2023-12-29T08:29:03.180819",
"updateAt": "2023-12-29T08:29:03.180820",
"lastMade": null
}
}
]

View File

@ -0,0 +1,612 @@
{
"page": 1,
"per_page": 50,
"total": 14,
"total_pages": 1,
"items": [
{
"date": "2024-01-22",
"entryType": "dinner",
"title": "",
"text": "",
"recipeId": "c5f00a93-71a2-4e48-900f-d9ad0bb9de93",
"id": 230,
"groupId": "0bf60b2e-ca89-42a9-94d4-8f67ca72b157",
"userId": "1ce8b5fe-04e8-4b80-aab1-d92c94685c6d",
"recipe": {
"id": "c5f00a93-71a2-4e48-900f-d9ad0bb9de93",
"userId": "1ce8b5fe-04e8-4b80-aab1-d92c94685c6d",
"groupId": "0bf60b2e-ca89-42a9-94d4-8f67ca72b157",
"name": "Zoete aardappel curry traybake",
"slug": "zoete-aardappel-curry-traybake",
"image": "AiIo",
"recipeYield": "2 servings",
"totalTime": "40 Minutes",
"prepTime": null,
"cookTime": null,
"performTime": null,
"description": "Een traybake is eigenlijk altijd een goed idee. Deze zoete aardappel curry traybake dus ook. Waarom? Omdat je alleen maar wat groenten - en in dit geval kip - op een bakplaat (traybake dus) legt, hier wat kruiden aan toevoegt en deze in de oven schuift. Ideaal dus als je geen zin hebt om lang in de keuken te staan. Maar gewoon lekker op de bank wil ploffen om te wachten tot de oven klaar is. Joe! That\\'s what we like. Deze zoete aardappel curry traybake bevat behalve zoete aardappel en curry ook kikkererwten, kippendijfilet en bloemkoolroosjes. Je gebruikt yoghurt en limoen als een soort dressing. En je serveert deze heerlijke traybake met naanbrood. Je kunt natuurljk ook voor deze traybake met chipolataworstjes gaan. Wil je graag meer ovengerechten? Dan moet je eigenlijk even kijken naar onze Ovenbijbel. Onmisbaar in je keuken! We willen je deze zoete aardappelstamppot met prei ook niet onthouden. Megalekker bordje comfortfood als je \\'t ons vraagt.",
"recipeCategory": [],
"tags": [],
"tools": [],
"rating": null,
"orgURL": "https://chickslovefood.com/recept/zoete-aardappel-curry-traybake/",
"dateAdded": "2024-01-22",
"dateUpdated": "2024-01-22T00:27:46.324512",
"createdAt": "2024-01-22T00:27:46.327546",
"updateAt": "2024-01-22T00:27:46.327548",
"lastMade": null
}
},
{
"date": "2024-01-23",
"entryType": "breakfast",
"title": "",
"text": "",
"recipeId": "5b055066-d57d-4fd0-8dfd-a2c2f07b36f1",
"id": 229,
"groupId": "0bf60b2e-ca89-42a9-94d4-8f67ca72b157",
"userId": "1ce8b5fe-04e8-4b80-aab1-d92c94685c6d",
"recipe": {
"id": "5b055066-d57d-4fd0-8dfd-a2c2f07b36f1",
"userId": "1ce8b5fe-04e8-4b80-aab1-d92c94685c6d",
"groupId": "0bf60b2e-ca89-42a9-94d4-8f67ca72b157",
"name": "Roast Chicken",
"slug": "roast-chicken",
"image": "JeQ2",
"recipeYield": "6 servings",
"totalTime": "1 Hour 35 Minutes",
"prepTime": "15 Minutes",
"cookTime": null,
"performTime": "1 Hour 20 Minutes",
"description": "The BEST Roast Chicken recipe is simple, budget friendly, and gives you a tender, mouth-watering chicken full of flavor! Served with roasted vegetables, this recipe is simple enough for any cook!",
"recipeCategory": [],
"tags": [],
"tools": [],
"rating": null,
"orgURL": "https://tastesbetterfromscratch.com/roast-chicken/",
"dateAdded": "2024-01-21",
"dateUpdated": "2024-01-21T15:29:25.664540",
"createdAt": "2024-01-21T15:29:25.667450",
"updateAt": "2024-01-21T15:29:25.667452",
"lastMade": null
}
},
{
"date": "2024-01-23",
"entryType": "lunch",
"title": "",
"text": "",
"recipeId": "e360a0cc-18b0-4a84-a91b-8aa59e2451c9",
"id": 226,
"groupId": "0bf60b2e-ca89-42a9-94d4-8f67ca72b157",
"userId": "1ce8b5fe-04e8-4b80-aab1-d92c94685c6d",
"recipe": {
"id": "e360a0cc-18b0-4a84-a91b-8aa59e2451c9",
"userId": "1ce8b5fe-04e8-4b80-aab1-d92c94685c6d",
"groupId": "0bf60b2e-ca89-42a9-94d4-8f67ca72b157",
"name": "Receta de pollo al curry en 10 minutos (con vídeo incluido)",
"slug": "receta-de-pollo-al-curry-en-10-minutos-con-video-incluido",
"image": "INQz",
"recipeYield": "2 servings",
"totalTime": "10 Minutes",
"prepTime": "3 Minutes",
"cookTime": null,
"performTime": "7 Minutes",
"description": "Te explicamos paso a paso, de manera sencilla, la elaboración de la receta de pollo al curry con leche de coco en 10 minutos. Ingredientes, tiempo de...",
"recipeCategory": [],
"tags": [],
"tools": [
{
"id": "1170e609-20d3-45b8-b0c7-3a4cfa614e88",
"name": "Backofen",
"slug": "backofen",
"onHand": false
},
{
"id": "9ab522ad-c3be-4dad-8b4f-d9d53817f4d0",
"name": "Magimix blender",
"slug": "magimix-blender",
"onHand": false
},
{
"id": "b4ca27dc-9bf6-48be-ad10-3e7056cb24bc",
"name": "Alluminio",
"slug": "alluminio",
"onHand": false
}
],
"rating": null,
"orgURL": "https://www.directoalpaladar.com/recetas-de-carnes-y-aves/receta-de-pollo-al-curry-en-10-minutos",
"dateAdded": "2024-01-21",
"dateUpdated": "2024-01-21T12:56:31.483701",
"createdAt": "2024-01-21T12:45:28.589669",
"updateAt": "2024-01-21T12:56:31.487406",
"lastMade": null
}
},
{
"date": "2024-01-23",
"entryType": "lunch",
"title": "",
"text": "",
"recipeId": "9c7b8aee-c93c-4b1b-ab48-2625d444743a",
"id": 224,
"groupId": "0bf60b2e-ca89-42a9-94d4-8f67ca72b157",
"userId": "1ce8b5fe-04e8-4b80-aab1-d92c94685c6d",
"recipe": {
"id": "9c7b8aee-c93c-4b1b-ab48-2625d444743a",
"userId": "1ce8b5fe-04e8-4b80-aab1-d92c94685c6d",
"groupId": "0bf60b2e-ca89-42a9-94d4-8f67ca72b157",
"name": "Boeuf bourguignon : la vraie recette (2)",
"slug": "boeuf-bourguignon-la-vraie-recette-2",
"image": "nj5M",
"recipeYield": "4 servings",
"totalTime": "5 Hours",
"prepTime": "1 Hour",
"cookTime": null,
"performTime": "4 Hours",
"description": "bourguignon, oignon, carotte, bouquet garni, vin rouge, beurre, sel, poivre",
"recipeCategory": [],
"tags": [
{
"id": "01c2f4ac-54ce-49bc-9bd7-8a49f353a3a4",
"name": "Poivre",
"slug": "poivre"
},
{
"id": "90a26cea-a8a1-41a1-9e8c-e94e3c40f7a7",
"name": "Sel",
"slug": "sel"
},
{
"id": "d7b01a4b-5206-4bd2-b9c4-d13b95ca0edb",
"name": "Beurre",
"slug": "beurre"
},
{
"id": "304faaf8-13ec-4537-91f3-9f39a3585545",
"name": "Facile",
"slug": "facile"
},
{
"id": "6508fb05-fb60-4bed-90c4-584bd6d74cb5",
"name": "Daube",
"slug": "daube"
},
{
"id": "18ff59b6-b599-456a-896b-4b76448b08ca",
"name": "Bourguignon",
"slug": "bourguignon"
},
{
"id": "685a0d90-8de4-494e-8eb8-68e7f5d5ffbe",
"name": "Vin Rouge",
"slug": "vin-rouge"
},
{
"id": "5dedc8b5-30f5-4d6e-875f-34deefd01883",
"name": "Oignon",
"slug": "oignon"
},
{
"id": "065b79e0-6276-4ebb-9428-7018b40c55bb",
"name": "Bouquet Garni",
"slug": "bouquet-garni"
},
{
"id": "d858b1d9-2ca1-46d4-acc2-3d03f991f03f",
"name": "Moyen",
"slug": "moyen"
},
{
"id": "bded0bd8-8d41-4ec5-ad73-e0107fb60908",
"name": "Boeuf Bourguignon : La Vraie Recette",
"slug": "boeuf-bourguignon-la-vraie-recette"
},
{
"id": "7f99b04f-914a-408b-a057-511ca1125734",
"name": "Carotte",
"slug": "carotte"
}
],
"tools": [],
"rating": null,
"orgURL": "https://www.marmiton.org/recettes/recette_boeuf-bourguignon_18889.aspx",
"dateAdded": "2024-01-21",
"dateUpdated": "2024-01-21T08:45:28.780361",
"createdAt": "2024-01-21T08:45:28.782322",
"updateAt": "2024-01-21T08:45:28.782324",
"lastMade": null
}
},
{
"date": "2024-01-23",
"entryType": "dinner",
"title": "",
"text": "",
"recipeId": "f79f7e9d-4b58-4930-a586-2b127f16ee34",
"id": 222,
"groupId": "0bf60b2e-ca89-42a9-94d4-8f67ca72b157",
"userId": "1ce8b5fe-04e8-4b80-aab1-d92c94685c6d",
"recipe": {
"id": "f79f7e9d-4b58-4930-a586-2b127f16ee34",
"userId": "1ce8b5fe-04e8-4b80-aab1-d92c94685c6d",
"groupId": "0bf60b2e-ca89-42a9-94d4-8f67ca72b157",
"name": "Εύκολη μακαρονάδα με κεφτεδάκια στον φούρνο (1)",
"slug": "eukole-makaronada-me-kephtedakia-ston-phourno-1",
"image": "En9o",
"recipeYield": "6 servings",
"totalTime": null,
"prepTime": "15 Minutes",
"cookTime": null,
"performTime": "50 Minutes",
"description": "Εύκολη μακαρονάδα με κεφτεδάκια στον φούρνο από τον Άκη Πετρετζίκη. Φτιάξτε την πιο εύκολη μακαρονάδα με κεφτεδάκια σε μόνο ένα σκεύος.",
"recipeCategory": [],
"tags": [],
"tools": [],
"rating": null,
"orgURL": "https://akispetretzikis.com/recipe/7959/efkolh-makaronada-me-keftedakia-ston-fourno",
"dateAdded": "2024-01-21",
"dateUpdated": "2024-01-21T09:08:58.056854",
"createdAt": "2024-01-21T09:08:58.059401",
"updateAt": "2024-01-21T09:08:58.059403",
"lastMade": null
}
},
{
"date": "2024-01-23",
"entryType": "dinner",
"title": "",
"text": "",
"recipeId": "47595e4c-52bc-441d-b273-3edf4258806d",
"id": 221,
"groupId": "0bf60b2e-ca89-42a9-94d4-8f67ca72b157",
"userId": "1ce8b5fe-04e8-4b80-aab1-d92c94685c6d",
"recipe": {
"id": "47595e4c-52bc-441d-b273-3edf4258806d",
"userId": "1ce8b5fe-04e8-4b80-aab1-d92c94685c6d",
"groupId": "0bf60b2e-ca89-42a9-94d4-8f67ca72b157",
"name": "Greek Turkey Meatballs with Lemon Orzo & Creamy Feta Yogurt Sauce",
"slug": "greek-turkey-meatballs-with-lemon-orzo-creamy-feta-yogurt-sauce",
"image": "Kn62",
"recipeYield": "4 servings",
"totalTime": "1 Hour",
"prepTime": "40 Minutes",
"cookTime": null,
"performTime": "20 Minutes",
"description": "Delicious Greek turkey meatballs with lemon orzo, tender veggies, and a creamy feta yogurt sauce. These healthy baked Greek turkey meatballs are filled with tons of wonderful herbs and make the perfect protein-packed weeknight meal!",
"recipeCategory": [],
"tags": [],
"tools": [],
"rating": null,
"orgURL": "https://www.ambitiouskitchen.com/greek-turkey-meatballs/",
"dateAdded": "2024-01-04",
"dateUpdated": "2024-01-04T11:51:00.843570",
"createdAt": "2024-01-04T11:51:00.847033",
"updateAt": "2024-01-04T11:51:00.847035",
"lastMade": null
}
},
{
"date": "2024-01-23",
"entryType": "side",
"title": "",
"text": "",
"recipeId": "9d553779-607e-471b-acf3-84e6be27b159",
"id": 220,
"groupId": "0bf60b2e-ca89-42a9-94d4-8f67ca72b157",
"userId": "1ce8b5fe-04e8-4b80-aab1-d92c94685c6d",
"recipe": {
"id": "9d553779-607e-471b-acf3-84e6be27b159",
"userId": "1ce8b5fe-04e8-4b80-aab1-d92c94685c6d",
"groupId": "0bf60b2e-ca89-42a9-94d4-8f67ca72b157",
"name": "Einfacher Nudelauflauf mit Brokkoli",
"slug": "einfacher-nudelauflauf-mit-brokkoli",
"image": "nOPT",
"recipeYield": "4 servings",
"totalTime": "35 Minutes",
"prepTime": "15 Minutes",
"cookTime": null,
"performTime": "20 Minutes",
"description": "Einfacher Nudelauflauf mit Brokkoli, Sahnesauce und extra Käse. Dieses vegetarische 5 Zutaten Rezept ist super schnell gemacht und SO gut!",
"recipeCategory": [],
"tags": [
{
"id": "78318c97-75c7-4d06-95b6-51ef8f4a0257",
"name": "< 4 Hours",
"slug": "4-hours"
}
],
"tools": [],
"rating": null,
"orgURL": "https://kochkarussell.com/einfacher-nudelauflauf-brokkoli/",
"dateAdded": "2024-01-21",
"dateUpdated": "2024-01-21T03:04:25.718367",
"createdAt": "2024-01-21T02:13:11.323363",
"updateAt": "2024-01-21T03:04:25.721489",
"lastMade": null
}
},
{
"date": "2024-01-23",
"entryType": "dinner",
"title": "",
"text": "",
"recipeId": "92635fd0-f2dc-4e78-a6e4-ecd556ad361f",
"id": 219,
"groupId": "0bf60b2e-ca89-42a9-94d4-8f67ca72b157",
"userId": "1ce8b5fe-04e8-4b80-aab1-d92c94685c6d",
"recipe": {
"id": "92635fd0-f2dc-4e78-a6e4-ecd556ad361f",
"userId": "1ce8b5fe-04e8-4b80-aab1-d92c94685c6d",
"groupId": "0bf60b2e-ca89-42a9-94d4-8f67ca72b157",
"name": "Pampered Chef Double Chocolate Mocha Trifle",
"slug": "pampered-chef-double-chocolate-mocha-trifle",
"image": "ibL6",
"recipeYield": "12 servings",
"totalTime": "1 Hour 15 Minutes",
"prepTime": "15 Minutes",
"cookTime": null,
"performTime": "1 Hour",
"description": "This is a modified Pampered Chef recipe. You can use a trifle bowl or large glass punch/salad bowl to show it off. It is really easy to make and I never have any leftovers. Cook time includes chill time.",
"recipeCategory": [],
"tags": [
{
"id": "0248c21d-c85a-47b2-aaf6-fb6caf1b7726",
"name": "Weeknight",
"slug": "weeknight"
},
{
"id": "78318c97-75c7-4d06-95b6-51ef8f4a0257",
"name": "< 4 Hours",
"slug": "4-hours"
}
],
"tools": [],
"rating": 3,
"orgURL": "https://www.food.com/recipe/pampered-chef-double-chocolate-mocha-trifle-74963",
"dateAdded": "2024-01-06",
"dateUpdated": "2024-01-06T08:11:21.427447",
"createdAt": "2024-01-06T06:29:24.966994",
"updateAt": "2024-01-06T08:11:21.430079",
"lastMade": null
}
},
{
"date": "2024-01-22",
"entryType": "dinner",
"title": "",
"text": "",
"recipeId": "8bdd3656-5e7e-45d3-a3c4-557390846a22",
"id": 217,
"groupId": "0bf60b2e-ca89-42a9-94d4-8f67ca72b157",
"userId": "1ce8b5fe-04e8-4b80-aab1-d92c94685c6d",
"recipe": {
"id": "8bdd3656-5e7e-45d3-a3c4-557390846a22",
"userId": "1ce8b5fe-04e8-4b80-aab1-d92c94685c6d",
"groupId": "0bf60b2e-ca89-42a9-94d4-8f67ca72b157",
"name": "Cheeseburger Sliders (Easy, 30-min Recipe)",
"slug": "cheeseburger-sliders-easy-30-min-recipe",
"image": "beGq",
"recipeYield": "24 servings",
"totalTime": "30 Minutes",
"prepTime": "8 Minutes",
"cookTime": null,
"performTime": "22 Minutes",
"description": "Cheeseburger Sliders are juicy, cheesy and beefy - everything we love about classic burgers! These sliders are quick and easy plus they are make-ahead and reheat really well.",
"recipeCategory": [],
"tags": [
{
"id": "7a4ca427-642f-4428-8dc7-557ea9c8d1b4",
"name": "Cheeseburger Sliders",
"slug": "cheeseburger-sliders"
},
{
"id": "941558d2-50d5-4c9d-8890-a0258f18d493",
"name": "Sliders",
"slug": "sliders"
}
],
"tools": [],
"rating": 5,
"orgURL": "https://natashaskitchen.com/cheeseburger-sliders/",
"dateAdded": "2024-01-21",
"dateUpdated": "2024-01-21T07:43:24.261010",
"createdAt": "2024-01-21T06:49:35.466777",
"updateAt": "2024-01-21T06:49:35.466778",
"lastMade": "2024-01-22T04:59:59"
}
},
{
"date": "2024-01-22",
"entryType": "lunch",
"title": "",
"text": "",
"recipeId": "48f39d27-4b8e-4c14-bf36-4e1e6497e75e",
"id": 216,
"groupId": "0bf60b2e-ca89-42a9-94d4-8f67ca72b157",
"userId": "1ce8b5fe-04e8-4b80-aab1-d92c94685c6d",
"recipe": {
"id": "48f39d27-4b8e-4c14-bf36-4e1e6497e75e",
"userId": "1ce8b5fe-04e8-4b80-aab1-d92c94685c6d",
"groupId": "0bf60b2e-ca89-42a9-94d4-8f67ca72b157",
"name": "All-American Beef Stew Recipe",
"slug": "all-american-beef-stew-recipe",
"image": "356X",
"recipeYield": "6 servings",
"totalTime": "3 Hours 15 Minutes",
"prepTime": "5 Minutes",
"cookTime": null,
"performTime": "3 Hours 10 Minutes",
"description": "This All-American beef stew recipe includes tender beef coated in a rich, intense sauce and vegetables that bring complementary texture and flavor.",
"recipeCategory": [],
"tags": [
{
"id": "78318c97-75c7-4d06-95b6-51ef8f4a0257",
"name": "< 4 Hours",
"slug": "4-hours"
}
],
"tools": [],
"rating": null,
"orgURL": "https://www.seriouseats.com/all-american-beef-stew-recipe",
"dateAdded": "2024-01-20",
"dateUpdated": "2024-01-21T03:04:45.606075",
"createdAt": "2024-01-20T20:41:29.266390",
"updateAt": "2024-01-21T03:04:45.609563",
"lastMade": null
}
},
{
"date": "2024-01-23",
"entryType": "dinner",
"title": "",
"text": "",
"recipeId": "48f39d27-4b8e-4c14-bf36-4e1e6497e75e",
"id": 212,
"groupId": "0bf60b2e-ca89-42a9-94d4-8f67ca72b157",
"userId": "1ce8b5fe-04e8-4b80-aab1-d92c94685c6d",
"recipe": {
"id": "48f39d27-4b8e-4c14-bf36-4e1e6497e75e",
"userId": "1ce8b5fe-04e8-4b80-aab1-d92c94685c6d",
"groupId": "0bf60b2e-ca89-42a9-94d4-8f67ca72b157",
"name": "All-American Beef Stew Recipe",
"slug": "all-american-beef-stew-recipe",
"image": "356X",
"recipeYield": "6 servings",
"totalTime": "3 Hours 15 Minutes",
"prepTime": "5 Minutes",
"cookTime": null,
"performTime": "3 Hours 10 Minutes",
"description": "This All-American beef stew recipe includes tender beef coated in a rich, intense sauce and vegetables that bring complementary texture and flavor.",
"recipeCategory": [],
"tags": [
{
"id": "78318c97-75c7-4d06-95b6-51ef8f4a0257",
"name": "< 4 Hours",
"slug": "4-hours"
}
],
"tools": [],
"rating": null,
"orgURL": "https://www.seriouseats.com/all-american-beef-stew-recipe",
"dateAdded": "2024-01-20",
"dateUpdated": "2024-01-21T03:04:45.606075",
"createdAt": "2024-01-20T20:41:29.266390",
"updateAt": "2024-01-21T03:04:45.609563",
"lastMade": null
}
},
{
"date": "2024-01-22",
"entryType": "dinner",
"title": "",
"text": "",
"recipeId": "9d553779-607e-471b-acf3-84e6be27b159",
"id": 211,
"groupId": "0bf60b2e-ca89-42a9-94d4-8f67ca72b157",
"userId": "1ce8b5fe-04e8-4b80-aab1-d92c94685c6d",
"recipe": {
"id": "9d553779-607e-471b-acf3-84e6be27b159",
"userId": "1ce8b5fe-04e8-4b80-aab1-d92c94685c6d",
"groupId": "0bf60b2e-ca89-42a9-94d4-8f67ca72b157",
"name": "Einfacher Nudelauflauf mit Brokkoli",
"slug": "einfacher-nudelauflauf-mit-brokkoli",
"image": "nOPT",
"recipeYield": "4 servings",
"totalTime": "35 Minutes",
"prepTime": "15 Minutes",
"cookTime": null,
"performTime": "20 Minutes",
"description": "Einfacher Nudelauflauf mit Brokkoli, Sahnesauce und extra Käse. Dieses vegetarische 5 Zutaten Rezept ist super schnell gemacht und SO gut!",
"recipeCategory": [],
"tags": [
{
"id": "78318c97-75c7-4d06-95b6-51ef8f4a0257",
"name": "< 4 Hours",
"slug": "4-hours"
}
],
"tools": [],
"rating": null,
"orgURL": "https://kochkarussell.com/einfacher-nudelauflauf-brokkoli/",
"dateAdded": "2024-01-21",
"dateUpdated": "2024-01-21T03:04:25.718367",
"createdAt": "2024-01-21T02:13:11.323363",
"updateAt": "2024-01-21T03:04:25.721489",
"lastMade": null
}
},
{
"date": "2024-01-23",
"entryType": "dinner",
"title": "",
"text": "",
"recipeId": "25b814f2-d9bf-4df0-b40d-d2f2457b4317",
"id": 196,
"groupId": "0bf60b2e-ca89-42a9-94d4-8f67ca72b157",
"userId": "1ce8b5fe-04e8-4b80-aab1-d92c94685c6d",
"recipe": {
"id": "25b814f2-d9bf-4df0-b40d-d2f2457b4317",
"userId": "1ce8b5fe-04e8-4b80-aab1-d92c94685c6d",
"groupId": "0bf60b2e-ca89-42a9-94d4-8f67ca72b157",
"name": "Miso Udon Noodles with Spinach and Tofu",
"slug": "miso-udon-noodles-with-spinach-and-tofu",
"image": "5G1v",
"recipeYield": "2 servings",
"totalTime": "25 Minutes",
"prepTime": "10 Minutes",
"cookTime": null,
"performTime": "15 Minutes",
"description": "Simple to prepare and ready in 25 minutes, this vegetarian miso noodle recipe can be eaten on its own or served as a side.",
"recipeCategory": [],
"tags": [],
"tools": [],
"rating": null,
"orgURL": "https://www.allrecipes.com/recipe/284039/miso-udon-noodles-with-spinach-and-tofu/",
"dateAdded": "2024-01-05",
"dateUpdated": "2024-01-05T16:35:00.264511",
"createdAt": "2024-01-05T16:00:45.090493",
"updateAt": "2024-01-05T16:35:00.267508",
"lastMade": null
}
},
{
"date": "2024-01-22",
"entryType": "dinner",
"title": "",
"text": "",
"recipeId": "55c88810-4cf1-4d86-ae50-63b15fd173fb",
"id": 195,
"groupId": "0bf60b2e-ca89-42a9-94d4-8f67ca72b157",
"userId": "1ce8b5fe-04e8-4b80-aab1-d92c94685c6d",
"recipe": {
"id": "55c88810-4cf1-4d86-ae50-63b15fd173fb",
"userId": "1ce8b5fe-04e8-4b80-aab1-d92c94685c6d",
"groupId": "0bf60b2e-ca89-42a9-94d4-8f67ca72b157",
"name": "Mousse de saumon",
"slug": "mousse-de-saumon",
"image": "rrNL",
"recipeYield": "12 servings",
"totalTime": "17 Minutes",
"prepTime": "15 Minutes",
"cookTime": null,
"performTime": "2 Minutes",
"description": "Avis aux nostalgiques des années 1980, la mousse de saumon est de retour dans une présentation adaptée au goût du jour. On utilise une technique sans faille : un saumon frais cuit au micro-ondes et mélangé au robot avec du fromage à la crème et de la crème sure. On obtient ainsi une texture onctueuse à tartiner, qui na rien à envier aux préparations gélatineuses dantan !",
"recipeCategory": [],
"tags": [],
"tools": [],
"rating": null,
"orgURL": "https://www.ricardocuisine.com/recettes/8919-mousse-de-saumon",
"dateAdded": "2024-01-02",
"dateUpdated": "2024-01-02T06:35:05.206948",
"createdAt": "2024-01-02T06:33:15.329794",
"updateAt": "2024-01-02T06:35:05.209189",
"lastMade": "2024-01-02T22:59:59"
}
}
],
"next": null,
"previous": null
}

View File

@ -0,0 +1,359 @@
# serializer version: 1
# name: test_api_calendar
list([
dict({
'entity_id': 'calendar.mealie_breakfast',
'name': 'Mealie Breakfast',
}),
dict({
'entity_id': 'calendar.mealie_dinner',
'name': 'Mealie Dinner',
}),
dict({
'entity_id': 'calendar.mealie_lunch',
'name': 'Mealie Lunch',
}),
dict({
'entity_id': 'calendar.mealie_side',
'name': 'Mealie Side',
}),
])
# ---
# name: test_api_events
list([
dict({
'description': "Een traybake is eigenlijk altijd een goed idee. Deze zoete aardappel curry traybake dus ook. Waarom? Omdat je alleen maar wat groenten - en in dit geval kip - op een bakplaat (traybake dus) legt, hier wat kruiden aan toevoegt en deze in de oven schuift. Ideaal dus als je geen zin hebt om lang in de keuken te staan. Maar gewoon lekker op de bank wil ploffen om te wachten tot de oven klaar is. Joe! That\\'s what we like. Deze zoete aardappel curry traybake bevat behalve zoete aardappel en curry ook kikkererwten, kippendijfilet en bloemkoolroosjes. Je gebruikt yoghurt en limoen als een soort dressing. En je serveert deze heerlijke traybake met naanbrood. Je kunt natuurljk ook voor deze traybake met chipolataworstjes gaan. Wil je graag meer ovengerechten? Dan moet je eigenlijk even kijken naar onze Ovenbijbel. Onmisbaar in je keuken! We willen je deze zoete aardappelstamppot met prei ook niet onthouden. Megalekker bordje comfortfood als je \\'t ons vraagt.",
'end': dict({
'date': '2024-01-23',
}),
'location': None,
'recurrence_id': None,
'rrule': None,
'start': dict({
'date': '2024-01-22',
}),
'summary': 'Zoete aardappel curry traybake',
'uid': None,
}),
dict({
'description': 'Εύκολη μακαρονάδα με κεφτεδάκια στον φούρνο από τον Άκη Πετρετζίκη. Φτιάξτε την πιο εύκολη μακαρονάδα με κεφτεδάκια σε μόνο ένα σκεύος.',
'end': dict({
'date': '2024-01-24',
}),
'location': None,
'recurrence_id': None,
'rrule': None,
'start': dict({
'date': '2024-01-23',
}),
'summary': 'Εύκολη μακαρονάδα με κεφτεδάκια στον φούρνο (1)',
'uid': None,
}),
dict({
'description': 'Delicious Greek turkey meatballs with lemon orzo, tender veggies, and a creamy feta yogurt sauce. These healthy baked Greek turkey meatballs are filled with tons of wonderful herbs and make the perfect protein-packed weeknight meal!',
'end': dict({
'date': '2024-01-24',
}),
'location': None,
'recurrence_id': None,
'rrule': None,
'start': dict({
'date': '2024-01-23',
}),
'summary': 'Greek Turkey Meatballs with Lemon Orzo & Creamy Feta Yogurt Sauce',
'uid': None,
}),
dict({
'description': 'This is a modified Pampered Chef recipe. You can use a trifle bowl or large glass punch/salad bowl to show it off. It is really easy to make and I never have any leftovers. Cook time includes chill time.',
'end': dict({
'date': '2024-01-24',
}),
'location': None,
'recurrence_id': None,
'rrule': None,
'start': dict({
'date': '2024-01-23',
}),
'summary': 'Pampered Chef Double Chocolate Mocha Trifle',
'uid': None,
}),
dict({
'description': 'Cheeseburger Sliders are juicy, cheesy and beefy - everything we love about classic burgers! These sliders are quick and easy plus they are make-ahead and reheat really well.',
'end': dict({
'date': '2024-01-23',
}),
'location': None,
'recurrence_id': None,
'rrule': None,
'start': dict({
'date': '2024-01-22',
}),
'summary': 'Cheeseburger Sliders (Easy, 30-min Recipe)',
'uid': None,
}),
dict({
'description': 'This All-American beef stew recipe includes tender beef coated in a rich, intense sauce and vegetables that bring complementary texture and flavor.',
'end': dict({
'date': '2024-01-24',
}),
'location': None,
'recurrence_id': None,
'rrule': None,
'start': dict({
'date': '2024-01-23',
}),
'summary': 'All-American Beef Stew Recipe',
'uid': None,
}),
dict({
'description': 'Einfacher Nudelauflauf mit Brokkoli, Sahnesauce und extra Käse. Dieses vegetarische 5 Zutaten Rezept ist super schnell gemacht und SO gut!',
'end': dict({
'date': '2024-01-23',
}),
'location': None,
'recurrence_id': None,
'rrule': None,
'start': dict({
'date': '2024-01-22',
}),
'summary': 'Einfacher Nudelauflauf mit Brokkoli',
'uid': None,
}),
dict({
'description': 'Simple to prepare and ready in 25 minutes, this vegetarian miso noodle recipe can be eaten on its own or served as a side.',
'end': dict({
'date': '2024-01-24',
}),
'location': None,
'recurrence_id': None,
'rrule': None,
'start': dict({
'date': '2024-01-23',
}),
'summary': 'Miso Udon Noodles with Spinach and Tofu',
'uid': None,
}),
dict({
'description': 'Avis aux nostalgiques des années 1980, la mousse de saumon est de retour dans une présentation adaptée au goût du jour. On utilise une technique sans faille : un saumon frais cuit au micro-ondes et mélangé au robot avec du fromage à la crème et de la crème sure. On obtient ainsi une texture onctueuse à tartiner, qui na rien à envier aux préparations gélatineuses dantan !',
'end': dict({
'date': '2024-01-23',
}),
'location': None,
'recurrence_id': None,
'rrule': None,
'start': dict({
'date': '2024-01-22',
}),
'summary': 'Mousse de saumon',
'uid': None,
}),
])
# ---
# name: test_entities[calendar.mealie_breakfast-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'calendar',
'entity_category': None,
'entity_id': 'calendar.mealie_breakfast',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Breakfast',
'platform': 'mealie',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'breakfast',
'unique_id': '01J0BC4QM2YBRP6H5G933CETT7_breakfast',
'unit_of_measurement': None,
})
# ---
# name: test_entities[calendar.mealie_breakfast-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'all_day': True,
'description': 'The BEST Roast Chicken recipe is simple, budget friendly, and gives you a tender, mouth-watering chicken full of flavor! Served with roasted vegetables, this recipe is simple enough for any cook!',
'end_time': '2024-01-24 00:00:00',
'friendly_name': 'Mealie Breakfast',
'location': '',
'message': 'Roast Chicken',
'start_time': '2024-01-23 00:00:00',
}),
'context': <ANY>,
'entity_id': 'calendar.mealie_breakfast',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_entities[calendar.mealie_dinner-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'calendar',
'entity_category': None,
'entity_id': 'calendar.mealie_dinner',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Dinner',
'platform': 'mealie',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'dinner',
'unique_id': '01J0BC4QM2YBRP6H5G933CETT7_dinner',
'unit_of_measurement': None,
})
# ---
# name: test_entities[calendar.mealie_dinner-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'all_day': True,
'description': "Een traybake is eigenlijk altijd een goed idee. Deze zoete aardappel curry traybake dus ook. Waarom? Omdat je alleen maar wat groenten - en in dit geval kip - op een bakplaat (traybake dus) legt, hier wat kruiden aan toevoegt en deze in de oven schuift. Ideaal dus als je geen zin hebt om lang in de keuken te staan. Maar gewoon lekker op de bank wil ploffen om te wachten tot de oven klaar is. Joe! That\\'s what we like. Deze zoete aardappel curry traybake bevat behalve zoete aardappel en curry ook kikkererwten, kippendijfilet en bloemkoolroosjes. Je gebruikt yoghurt en limoen als een soort dressing. En je serveert deze heerlijke traybake met naanbrood. Je kunt natuurljk ook voor deze traybake met chipolataworstjes gaan. Wil je graag meer ovengerechten? Dan moet je eigenlijk even kijken naar onze Ovenbijbel. Onmisbaar in je keuken! We willen je deze zoete aardappelstamppot met prei ook niet onthouden. Megalekker bordje comfortfood als je \\'t ons vraagt.",
'end_time': '2024-01-23 00:00:00',
'friendly_name': 'Mealie Dinner',
'location': '',
'message': 'Zoete aardappel curry traybake',
'start_time': '2024-01-22 00:00:00',
}),
'context': <ANY>,
'entity_id': 'calendar.mealie_dinner',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_entities[calendar.mealie_lunch-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'calendar',
'entity_category': None,
'entity_id': 'calendar.mealie_lunch',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Lunch',
'platform': 'mealie',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'lunch',
'unique_id': '01J0BC4QM2YBRP6H5G933CETT7_lunch',
'unit_of_measurement': None,
})
# ---
# name: test_entities[calendar.mealie_lunch-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'all_day': True,
'description': 'Te explicamos paso a paso, de manera sencilla, la elaboración de la receta de pollo al curry con leche de coco en 10 minutos. Ingredientes, tiempo de...',
'end_time': '2024-01-24 00:00:00',
'friendly_name': 'Mealie Lunch',
'location': '',
'message': 'Receta de pollo al curry en 10 minutos (con vídeo incluido)',
'start_time': '2024-01-23 00:00:00',
}),
'context': <ANY>,
'entity_id': 'calendar.mealie_lunch',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_entities[calendar.mealie_side-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'calendar',
'entity_category': None,
'entity_id': 'calendar.mealie_side',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Side',
'platform': 'mealie',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'side',
'unique_id': '01J0BC4QM2YBRP6H5G933CETT7_side',
'unit_of_measurement': None,
})
# ---
# name: test_entities[calendar.mealie_side-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'all_day': True,
'description': 'Einfacher Nudelauflauf mit Brokkoli, Sahnesauce und extra Käse. Dieses vegetarische 5 Zutaten Rezept ist super schnell gemacht und SO gut!',
'end_time': '2024-01-24 00:00:00',
'friendly_name': 'Mealie Side',
'location': '',
'message': 'Einfacher Nudelauflauf mit Brokkoli',
'start_time': '2024-01-23 00:00:00',
}),
'context': <ANY>,
'entity_id': 'calendar.mealie_side',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---

View File

@ -0,0 +1,31 @@
# serializer version: 1
# name: test_device_info
DeviceRegistryEntrySnapshot({
'area_id': None,
'config_entries': <ANY>,
'configuration_url': None,
'connections': set({
}),
'disabled_by': None,
'entry_type': <DeviceEntryType.SERVICE: 'service'>,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'mealie',
'01J0BC4QM2YBRP6H5G933CETT7',
),
}),
'is_new': False,
'labels': set({
}),
'manufacturer': None,
'model': None,
'name': 'Mealie',
'name_by_user': None,
'serial_number': None,
'suggested_area': None,
'sw_version': None,
'via_device_id': None,
})
# ---

View File

@ -0,0 +1,69 @@
"""Tests for the Mealie calendar."""
from datetime import date
from http import HTTPStatus
from unittest.mock import AsyncMock
from syrupy.assertion import SnapshotAssertion
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from . import setup_integration
from tests.common import MockConfigEntry, snapshot_platform
from tests.typing import ClientSessionGenerator
async def test_api_calendar(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
mock_mealie_client: AsyncMock,
mock_config_entry: MockConfigEntry,
hass_client: ClientSessionGenerator,
) -> None:
"""Test the API returns the calendar."""
await setup_integration(hass, mock_config_entry)
client = await hass_client()
response = await client.get("/api/calendars")
assert response.status == HTTPStatus.OK
data = await response.json()
assert data == snapshot
async def test_entities(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
mock_mealie_client: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test the API returns the calendar."""
await setup_integration(hass, mock_config_entry)
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
async def test_api_events(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
mock_mealie_client: AsyncMock,
mock_config_entry: MockConfigEntry,
hass_client: ClientSessionGenerator,
) -> None:
"""Test the Mealie calendar view."""
await setup_integration(hass, mock_config_entry)
client = await hass_client()
response = await client.get(
"/api/calendars/calendar.mealie_dinner?start=2023-08-01&end=2023-11-01"
)
assert mock_mealie_client.get_mealplans.called == 1
assert mock_mealie_client.get_mealplans.call_args_list[1].args == (
date(2023, 8, 1),
date(2023, 11, 1),
)
assert response.status == HTTPStatus.OK
events = await response.json()
assert events == snapshot

View File

@ -0,0 +1,107 @@
"""Tests for the Mealie config flow."""
from unittest.mock import AsyncMock
from aiomealie import MealieAuthenticationError, MealieConnectionError
import pytest
from homeassistant.components.mealie.const import DOMAIN
from homeassistant.config_entries import SOURCE_USER
from homeassistant.const import CONF_API_TOKEN, CONF_HOST
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from tests.common import MockConfigEntry
async def test_full_flow(
hass: HomeAssistant,
mock_mealie_client: AsyncMock,
mock_setup_entry: AsyncMock,
) -> None:
"""Test full flow."""
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_USER},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user"
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_HOST: "demo.mealie.io", CONF_API_TOKEN: "token"},
)
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["title"] == "Mealie"
assert result["data"] == {
CONF_HOST: "demo.mealie.io",
CONF_API_TOKEN: "token",
}
@pytest.mark.parametrize(
("exception", "error"),
[
(MealieConnectionError, "cannot_connect"),
(MealieAuthenticationError, "invalid_auth"),
(Exception, "unknown"),
],
)
async def test_flow_errors(
hass: HomeAssistant,
mock_mealie_client: AsyncMock,
mock_setup_entry: AsyncMock,
exception: Exception,
error: str,
) -> None:
"""Test flow errors."""
mock_mealie_client.get_mealplan_today.side_effect = exception
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_USER},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user"
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_HOST: "demo.mealie.io", CONF_API_TOKEN: "token"},
)
assert result["type"] is FlowResultType.FORM
assert result["errors"] == {"base": error}
mock_mealie_client.get_mealplan_today.side_effect = None
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_HOST: "demo.mealie.io", CONF_API_TOKEN: "token"},
)
await hass.async_block_till_done()
assert result["type"] is FlowResultType.CREATE_ENTRY
async def test_duplicate(
hass: HomeAssistant,
mock_mealie_client: AsyncMock,
mock_setup_entry: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test duplicate flow."""
mock_config_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_USER},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user"
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_HOST: "demo.mealie.io", CONF_API_TOKEN: "token"},
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "already_configured"

View File

@ -0,0 +1,70 @@
"""Tests for the Mealie integration."""
from unittest.mock import AsyncMock
from aiomealie import MealieAuthenticationError, MealieConnectionError
import pytest
from syrupy import SnapshotAssertion
from homeassistant.components.mealie.const import DOMAIN
from homeassistant.config_entries import ConfigEntryState
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr
from . import setup_integration
from tests.common import MockConfigEntry
async def test_device_info(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
mock_mealie_client: AsyncMock,
mock_config_entry: MockConfigEntry,
device_registry: dr.DeviceRegistry,
) -> None:
"""Test device registry integration."""
await setup_integration(hass, mock_config_entry)
device_entry = device_registry.async_get_device(
identifiers={(DOMAIN, mock_config_entry.entry_id)}
)
assert device_entry is not None
assert device_entry == snapshot
async def test_load_unload_entry(
hass: HomeAssistant,
mock_mealie_client: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test load and unload entry."""
await setup_integration(hass, mock_config_entry)
assert mock_config_entry.state is ConfigEntryState.LOADED
await hass.config_entries.async_remove(mock_config_entry.entry_id)
await hass.async_block_till_done()
assert mock_config_entry.state is ConfigEntryState.NOT_LOADED
@pytest.mark.parametrize(
("exc", "state"),
[
(MealieConnectionError, ConfigEntryState.SETUP_RETRY),
(MealieAuthenticationError, ConfigEntryState.SETUP_ERROR),
],
)
async def test_initialization_failure(
hass: HomeAssistant,
mock_mealie_client: AsyncMock,
mock_config_entry: MockConfigEntry,
exc: Exception,
state: ConfigEntryState,
) -> None:
"""Test initialization failure."""
mock_mealie_client.get_mealplans.side_effect = exc
await setup_integration(hass, mock_config_entry)
assert mock_config_entry.state is state