fix(trigger): surface webhook trigger quota exceeded as 429 response (#38656)

This commit is contained in:
林玮 (Jade Lin) 2026-07-10 16:50:25 +08:00 committed by GitHub
parent 775ed70bac
commit 44fb074359
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 55 additions and 5 deletions

View File

@ -7,6 +7,7 @@ from werkzeug.exceptions import NotFound, RequestEntityTooLarge
from controllers.trigger import bp
from core.trigger.debug.event_bus import TriggerDebugEventBus
from core.trigger.debug.events import WebhookDebugEvent, build_webhook_pool_key
from services.errors.app import QuotaExceededError
from services.trigger.webhook_service import RawWebhookDataDict, WebhookService
logger = logging.getLogger(__name__)
@ -60,8 +61,15 @@ def handle_webhook(webhook_id: str):
response_data, status_code = WebhookService.generate_webhook_response(node_config)
return jsonify(response_data), status_code
except ValueError as e:
raise NotFound(str(e))
except QuotaExceededError:
return jsonify(
{
"error": "Too Many Requests",
"message": "Trigger event quota exceeded. Please upgrade your plan.",
}
), 429
except ValueError as error:
raise NotFound(str(error))
except RequestEntityTooLarge:
raise
except Exception as e:

View File

@ -101,6 +101,7 @@ class WebhookService:
- Mapping[str, Any]: The node configuration data
Raises:
QuotaExceededError: If the app trigger is rate limited
ValueError: If webhook not found, app trigger not found, trigger disabled, or workflow not found
"""
with Session(db.engine) as session:
@ -141,8 +142,10 @@ class WebhookService:
# Only check enabled status if not in debug mode
if app_trigger.status == AppTriggerStatus.RATE_LIMITED:
raise ValueError(
f"Webhook trigger is rate limited for webhook {webhook_id}, please upgrade your plan."
raise QuotaExceededError(
feature=QuotaType.TRIGGER.value,
tenant_id=webhook_trigger.tenant_id,
required=1,
)
if app_trigger.status != AppTriggerStatus.ENABLED:
@ -799,6 +802,7 @@ class WebhookService:
workflow: The workflow to execute
Raises:
QuotaExceededError: If the tenant has exhausted its trigger quota
ValueError: If tenant owner is not found
Exception: If workflow execution fails
"""

View File

@ -201,9 +201,13 @@ class TestWebhookServiceLookupWithContainers:
db_session_with_containers, app=app, node_id="node-1", status=AppTriggerStatus.RATE_LIMITED
)
with pytest.raises(ValueError, match="rate limited"):
with pytest.raises(QuotaExceededError) as exc_info:
WebhookService.get_webhook_trigger_and_workflow(webhook_trigger.webhook_id)
assert exc_info.value.feature == QuotaType.TRIGGER.value
assert exc_info.value.tenant_id == tenant.id
assert exc_info.value.required == 1
def test_get_webhook_trigger_and_workflow_raises_when_app_trigger_disabled(
self, db_session_with_containers: Session, flask_app_with_containers: Flask
):

View File

@ -5,6 +5,7 @@ import pytest
from werkzeug.exceptions import NotFound, RequestEntityTooLarge
import controllers.trigger.webhook as module
from services.errors.app import QuotaExceededError
@pytest.fixture(autouse=True)
@ -83,6 +84,39 @@ class TestHandleWebhook:
assert status == 400
assert response["error"] == "Bad Request"
@patch.object(module.WebhookService, "get_webhook_trigger_and_workflow")
@patch.object(module.WebhookService, "extract_and_validate_webhook_data")
@patch.object(
module.WebhookService,
"trigger_workflow_execution",
side_effect=QuotaExceededError(feature="trigger", tenant_id="tenant-1", required=1),
)
def test_quota_exceeded(self, mock_trigger, mock_extract, mock_get):
mock_get.return_value = (DummyWebhookTrigger(), "workflow", "node_config")
mock_extract.return_value = {"input": "x"}
response, status = module.handle_webhook("wh-1")
assert status == 429
assert response == {
"error": "Too Many Requests",
"message": "Trigger event quota exceeded. Please upgrade your plan.",
}
@patch.object(
module.WebhookService,
"get_webhook_trigger_and_workflow",
side_effect=QuotaExceededError(feature="trigger", tenant_id="tenant-1", required=1),
)
def test_rate_limited(self, mock_get):
response, status = module.handle_webhook("wh-1")
assert status == 429
assert response == {
"error": "Too Many Requests",
"message": "Trigger event quota exceeded. Please upgrade your plan.",
}
@patch.object(module.WebhookService, "get_webhook_trigger_and_workflow", side_effect=ValueError("missing"))
def test_value_error_not_found(self, mock_get):
with pytest.raises(NotFound):