mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-08-05 13:35:23 +02:00
feat: mealplan-webhooks (#1403)
* fix type errors on event bus * webhooks fields required for new implementation * db migration * wip: webhook query + tests and stub function * ignore type checker error * type and method cleanup * datetime and time utc validator * update testing code for utc scheduled time * fix file cmp function call * update version_number * add support for translating "time" objects when restoring backup * bump recipe-scrapers * use specific import syntax * generate frontend types * utilize names exports * use utc times * add task to scheduler * implement new scheduler functionality * stub for type annotation * implement meal-plan data getter * add experimental banner
This commit is contained in:
parent
b1256f4ad2
commit
5a053cdcd6
22 changed files with 428 additions and 93 deletions
|
@ -1,10 +1,11 @@
|
|||
import contextlib
|
||||
from collections.abc import Generator
|
||||
|
||||
from pytest import MonkeyPatch, fixture
|
||||
|
||||
mp = MonkeyPatch()
|
||||
mp.setenv("PRODUCTION", "True")
|
||||
mp.setenv("TESTING", "True")
|
||||
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi.testclient import TestClient
|
||||
|
@ -34,11 +35,9 @@ def api_client():
|
|||
|
||||
yield TestClient(app)
|
||||
|
||||
try:
|
||||
with contextlib.suppress(Exception):
|
||||
settings = config.get_app_settings()
|
||||
settings.DB_PROVIDER.db_path.unlink() # Handle SQLite Provider
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
@fixture(scope="session")
|
||||
|
@ -52,16 +51,13 @@ def test_image_png():
|
|||
|
||||
|
||||
@fixture(scope="session", autouse=True)
|
||||
def global_cleanup() -> None:
|
||||
def global_cleanup() -> Generator[None, None, None]:
|
||||
"""Purges the .temp directory used for testing"""
|
||||
yield None
|
||||
try:
|
||||
with contextlib.suppress(Exception):
|
||||
temp_dir = Path(__file__).parent / ".temp"
|
||||
|
||||
if temp_dir.exists():
|
||||
import shutil
|
||||
|
||||
shutil.rmtree(temp_dir, ignore_errors=True)
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
|
|
|
@ -1,71 +1,74 @@
|
|||
from datetime import datetime, timezone
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from tests.utils import assert_derserialize, jsonify
|
||||
from tests.utils.fixture_schemas import TestUser
|
||||
|
||||
|
||||
class Routes:
|
||||
base = "/api/groups/webhooks"
|
||||
|
||||
@staticmethod
|
||||
def item(item_id: int) -> str:
|
||||
return f"{Routes.base}/{item_id}"
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def webhook_data():
|
||||
return {"enabled": True, "name": "Test-Name", "url": "https://my-fake-url.com", "time": "00:00"}
|
||||
return {
|
||||
"enabled": True,
|
||||
"name": "Test-Name",
|
||||
"url": "https://my-fake-url.com",
|
||||
"time": "00:00",
|
||||
"scheduledTime": datetime.now(),
|
||||
}
|
||||
|
||||
|
||||
def test_create_webhook(api_client: TestClient, unique_user: TestUser, webhook_data):
|
||||
response = api_client.post(Routes.base, json=webhook_data, headers=unique_user.token)
|
||||
|
||||
response = api_client.post(Routes.base, json=jsonify(webhook_data), headers=unique_user.token)
|
||||
assert response.status_code == 201
|
||||
|
||||
|
||||
def test_read_webhook(api_client: TestClient, unique_user: TestUser, webhook_data):
|
||||
response = api_client.post(Routes.base, json=webhook_data, headers=unique_user.token)
|
||||
id = response.json()["id"]
|
||||
response = api_client.post(Routes.base, json=jsonify(webhook_data), headers=unique_user.token)
|
||||
item_id = response.json()["id"]
|
||||
|
||||
response = api_client.get(Routes.item(id), headers=unique_user.token)
|
||||
response = api_client.get(Routes.item(item_id), headers=unique_user.token)
|
||||
webhook = assert_derserialize(response, 200)
|
||||
|
||||
webhook = response.json()
|
||||
|
||||
assert webhook["id"]
|
||||
assert webhook["id"] == item_id
|
||||
assert webhook["name"] == webhook_data["name"]
|
||||
assert webhook["url"] == webhook_data["url"]
|
||||
assert webhook["time"] == webhook_data["time"]
|
||||
assert webhook["scheduledTime"] == str(webhook_data["scheduledTime"].astimezone(timezone.utc).time())
|
||||
assert webhook["enabled"] == webhook_data["enabled"]
|
||||
|
||||
|
||||
def test_update_webhook(api_client: TestClient, webhook_data, unique_user: TestUser):
|
||||
response = api_client.post(Routes.base, json=webhook_data, headers=unique_user.token)
|
||||
id = response.json()["id"]
|
||||
response = api_client.post(Routes.base, json=jsonify(webhook_data), headers=unique_user.token)
|
||||
item_dict = assert_derserialize(response, 201)
|
||||
item_id = item_dict["id"]
|
||||
|
||||
webhook_data["name"] = "My New Name"
|
||||
webhook_data["url"] = "https://my-new-fake-url.com"
|
||||
webhook_data["time"] = "01:00"
|
||||
webhook_data["enabled"] = False
|
||||
|
||||
response = api_client.put(Routes.item(id), json=webhook_data, headers=unique_user.token)
|
||||
response = api_client.put(Routes.item(item_id), json=jsonify(webhook_data), headers=unique_user.token)
|
||||
updated_webhook = assert_derserialize(response, 200)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
updated_webhook = response.json()
|
||||
assert updated_webhook["name"] == webhook_data["name"]
|
||||
assert updated_webhook["url"] == webhook_data["url"]
|
||||
assert updated_webhook["time"] == webhook_data["time"]
|
||||
assert updated_webhook["enabled"] == webhook_data["enabled"]
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_delete_webhook(api_client: TestClient, webhook_data, unique_user: TestUser):
|
||||
response = api_client.post(Routes.base, json=webhook_data, headers=unique_user.token)
|
||||
id = response.json()["id"]
|
||||
|
||||
response = api_client.delete(Routes.item(id), headers=unique_user.token)
|
||||
response = api_client.post(Routes.base, json=jsonify(webhook_data), headers=unique_user.token)
|
||||
item_dict = assert_derserialize(response, 201)
|
||||
item_id = item_dict["id"]
|
||||
|
||||
response = api_client.delete(Routes.item(item_id), headers=unique_user.token)
|
||||
assert response.status_code == 200
|
||||
|
||||
response = api_client.get(Routes.item(id), headers=unique_user.token)
|
||||
response = api_client.get(Routes.item(item_id), headers=unique_user.token)
|
||||
assert response.status_code == 404
|
||||
|
|
|
@ -4,7 +4,7 @@ from mealie.core.config import get_app_settings
|
|||
from mealie.services.backups_v2.alchemy_exporter import AlchemyExporter
|
||||
|
||||
ALEMBIC_VERSIONS = [
|
||||
{"version_num": "ab0bae02578f"},
|
||||
{"version_num": "f30cf048c228"},
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ def match_file_tree(path_a: Path, path_b: Path):
|
|||
assert b_file.exists()
|
||||
match_file_tree(a_file, b_file)
|
||||
else:
|
||||
assert filecmp(path_a, path_b)
|
||||
assert filecmp.cmp(path_a, path_b)
|
||||
|
||||
|
||||
def test_database_backup():
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
from datetime import datetime, timedelta
|
||||
|
||||
from pydantic import UUID4
|
||||
|
||||
from mealie.repos.repository_factory import AllRepositories
|
||||
from mealie.schema.group.webhook import SaveWebhook, WebhookType
|
||||
from mealie.services.scheduler.tasks.post_webhooks import get_scheduled_webhooks
|
||||
from tests.utils import random_string
|
||||
from tests.utils.factories import random_bool
|
||||
from tests.utils.fixture_schemas import TestUser
|
||||
|
||||
|
||||
def webhook_factory(
|
||||
group_id: str | UUID4,
|
||||
enabled: bool = True,
|
||||
name: str = "",
|
||||
url: str = "",
|
||||
scheduled_time: datetime | None = None,
|
||||
webhook_type: str = WebhookType.mealplan,
|
||||
) -> SaveWebhook:
|
||||
return SaveWebhook(
|
||||
enabled=enabled,
|
||||
name=name or random_string(),
|
||||
url=url or random_string(),
|
||||
webhook_type=webhook_type,
|
||||
scheduled_time=scheduled_time.time() if scheduled_time else datetime.now().time(),
|
||||
group_id=group_id,
|
||||
)
|
||||
|
||||
|
||||
def test_get_scheduled_webhooks_filter_query(database: AllRepositories, unique_user: TestUser):
|
||||
"""
|
||||
get_scheduled_webhooks_test tests the get_scheduled_webhooks function.
|
||||
"""
|
||||
|
||||
expected: list[SaveWebhook] = []
|
||||
|
||||
start = datetime.now()
|
||||
|
||||
for _ in range(5):
|
||||
new_item = webhook_factory(group_id=unique_user.group_id, enabled=random_bool())
|
||||
out_of_range_item = webhook_factory(
|
||||
group_id=unique_user.group_id,
|
||||
enabled=random_bool(),
|
||||
scheduled_time=(start - timedelta(minutes=20)),
|
||||
)
|
||||
|
||||
database.webhooks.create(new_item)
|
||||
database.webhooks.create(out_of_range_item)
|
||||
|
||||
if new_item.enabled:
|
||||
expected.append(new_item)
|
||||
|
||||
results = get_scheduled_webhooks(database.session, start, datetime.now() + timedelta(minutes=5))
|
||||
|
||||
assert len(results) == len(expected)
|
||||
|
||||
for result in results:
|
||||
assert result.enabled
|
||||
|
||||
for expected_item in expected:
|
||||
|
||||
if result.name == expected_item.name: # Names are uniquely generated so we can use this to compare
|
||||
assert result.enabled == expected_item.enabled
|
||||
break
|
Loading…
Add table
Add a link
Reference in a new issue