mirror of
				https://github.com/mealie-recipes/mealie.git
				synced 2025-10-31 02:03:35 -04:00 
			
		
		
		
	fix: Make Sure Test Webhook Always Fires (#5816)
Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
This commit is contained in:
		| @@ -38,7 +38,7 @@ export const useGroupWebhooks = function () { | ||||
|       loading.value = true; | ||||
|  | ||||
|       const payload = { | ||||
|         enabled: false, | ||||
|         enabled: true, | ||||
|         name: "New Webhook", | ||||
|         url: "", | ||||
|         scheduledTime: "00:00", | ||||
|   | ||||
| @@ -10,7 +10,7 @@ from mealie.routes._base.mixins import HttpRepo | ||||
| from mealie.schema import mapper | ||||
| from mealie.schema.household.webhook import CreateWebhook, ReadWebhook, SaveWebhook, WebhookPagination | ||||
| from mealie.schema.response.pagination import PaginationQuery | ||||
| from mealie.services.scheduler.tasks.post_webhooks import post_group_webhooks, post_single_webhook | ||||
| from mealie.services.scheduler.tasks.post_webhooks import post_group_webhooks, post_test_webhook | ||||
|  | ||||
| router = APIRouter(prefix="/households/webhooks", tags=["Households: Webhooks"]) | ||||
|  | ||||
| @@ -55,7 +55,7 @@ class ReadWebhookController(BaseUserController): | ||||
|     @router.post("/{item_id}/test") | ||||
|     def test_one(self, item_id: UUID4, bg_tasks: BackgroundTasks): | ||||
|         webhook = self.mixins.get_one(item_id) | ||||
|         bg_tasks.add_task(post_single_webhook, webhook, "Test Webhook") | ||||
|         bg_tasks.add_task(post_test_webhook, webhook, "Test Webhook") | ||||
|  | ||||
|     @router.put("/{item_id}", response_model=ReadWebhook) | ||||
|     def update_one(self, item_id: UUID4, data: CreateWebhook): | ||||
|   | ||||
| @@ -3,7 +3,6 @@ import json | ||||
| from abc import ABC, abstractmethod | ||||
| from collections.abc import Generator | ||||
| from datetime import UTC, datetime | ||||
| from typing import cast | ||||
| from urllib.parse import parse_qs, urlencode, urlsplit, urlunsplit | ||||
|  | ||||
| from fastapi.encoders import jsonable_encoder | ||||
| @@ -148,15 +147,24 @@ class WebhookEventListener(EventListenerBase): | ||||
|  | ||||
|     def publish_to_subscribers(self, event: Event, subscribers: list[ReadWebhook]) -> None: | ||||
|         with self.ensure_repos(self.group_id, self.household_id) as repos: | ||||
|             if event.document_data.document_type == EventDocumentType.mealplan: | ||||
|                 webhook_data = cast(EventWebhookData, event.document_data) | ||||
|                 meal_repo = repos.meals | ||||
|                 meal_data = meal_repo.get_meals_by_date_range( | ||||
|                     webhook_data.webhook_start_dt, webhook_data.webhook_end_dt | ||||
|                 ) | ||||
|                 if meal_data: | ||||
|                     webhook_data.webhook_body = meal_data | ||||
|                     self.publisher.publish(event, [webhook.url for webhook in subscribers]) | ||||
|             if not isinstance(event.document_data, EventWebhookData): | ||||
|                 return | ||||
|  | ||||
|             match event.document_data.document_type: | ||||
|                 case EventDocumentType.mealplan: | ||||
|                     meal_repo = repos.meals | ||||
|                     meal_data = meal_repo.get_meals_by_date_range( | ||||
|                         event.document_data.webhook_start_dt, event.document_data.webhook_end_dt | ||||
|                     ) | ||||
|                     event.document_data.webhook_body = meal_data or None | ||||
|                 case _: | ||||
|                     if event.event_type is EventTypes.test_message: | ||||
|                         # make sure the webhook has a valid body so it gets sent | ||||
|                         event.document_data.webhook_body = event.document_data.webhook_body or [] | ||||
|  | ||||
|             # Only publish to subscribers if we have a webhook body to send | ||||
|             if event.document_data.webhook_body is not None: | ||||
|                 self.publisher.publish(event, [webhook.url for webhook in subscribers]) | ||||
|  | ||||
|     def get_scheduled_webhooks(self, start_dt: datetime, end_dt: datetime) -> list[ReadWebhook]: | ||||
|         """Fetches all scheduled webhooks from the database""" | ||||
|   | ||||
| @@ -79,12 +79,12 @@ def post_group_webhooks( | ||||
|             ) | ||||
|  | ||||
|  | ||||
| def post_single_webhook(webhook: ReadWebhook, message: str = "") -> None: | ||||
| def post_test_webhook(webhook: ReadWebhook, message: str = "") -> None: | ||||
|     dt = datetime.min.replace(tzinfo=UTC) | ||||
|     event_type = EventTypes.webhook_task | ||||
|     event_type = EventTypes.test_message | ||||
|  | ||||
|     event_document_data = EventWebhookData( | ||||
|         document_type=EventDocumentType.mealplan, | ||||
|         document_type=EventDocumentType.generic, | ||||
|         operation=EventOperation.info, | ||||
|         webhook_start_dt=dt, | ||||
|         webhook_end_dt=dt, | ||||
|   | ||||
| @@ -3,6 +3,8 @@ from datetime import UTC, datetime | ||||
| import pytest | ||||
| from fastapi.testclient import TestClient | ||||
|  | ||||
| from mealie.schema.household.webhook import ReadWebhook | ||||
| from mealie.services.scheduler.tasks.post_webhooks import post_test_webhook | ||||
| from tests.utils import api_routes, assert_deserialize, jsonify | ||||
| from tests.utils.fixture_schemas import TestUser | ||||
|  | ||||
| @@ -84,3 +86,48 @@ def test_delete_webhook(api_client: TestClient, webhook_data, unique_user: TestU | ||||
|  | ||||
|     response = api_client.get(api_routes.households_webhooks_item_id(item_id), headers=unique_user.token) | ||||
|     assert response.status_code == 404 | ||||
|  | ||||
|  | ||||
| def test_post_test_webhook( | ||||
|     monkeypatch: pytest.MonkeyPatch, api_client: TestClient, unique_user: TestUser, webhook_data | ||||
| ): | ||||
|     # Mock the requests.post to avoid actual HTTP calls | ||||
|     class MockResponse: | ||||
|         status_code = 200 | ||||
|  | ||||
|     mock_calls = [] | ||||
|  | ||||
|     def mock_post(*args, **kwargs): | ||||
|         mock_calls.append((args, kwargs)) | ||||
|         return MockResponse() | ||||
|  | ||||
|     monkeypatch.setattr("mealie.services.event_bus_service.publisher.requests.post", mock_post) | ||||
|  | ||||
|     # Create a webhook and post it | ||||
|     response = api_client.post( | ||||
|         api_routes.households_webhooks, | ||||
|         json=jsonify(webhook_data), | ||||
|         headers=unique_user.token, | ||||
|     ) | ||||
|     webhook_dict = assert_deserialize(response, 201) | ||||
|  | ||||
|     webhook = ReadWebhook( | ||||
|         id=webhook_dict["id"], | ||||
|         name=webhook_dict["name"], | ||||
|         url=webhook_dict["url"], | ||||
|         scheduled_time=webhook_dict["scheduledTime"], | ||||
|         enabled=webhook_dict["enabled"], | ||||
|         group_id=webhook_dict["groupId"], | ||||
|         household_id=webhook_dict["householdId"], | ||||
|     ) | ||||
|  | ||||
|     test_message = "This is a test webhook message" | ||||
|     post_test_webhook(webhook, test_message) | ||||
|  | ||||
|     # Verify that requests.post was called with the correct parameters | ||||
|     assert len(mock_calls) == 1 | ||||
|     args, kwargs = mock_calls[0] | ||||
|  | ||||
|     assert kwargs["json"]["message"]["body"] == test_message | ||||
|     assert kwargs["timeout"] == 15 | ||||
|     assert args[0] == webhook.url | ||||
|   | ||||
		Reference in New Issue
	
	Block a user