query param of webhook backend support

This commit is contained in:
hjlarry 2025-09-30 21:20:45 +08:00
parent 32f4d1af8b
commit 080cdda4fa
3 changed files with 164 additions and 16 deletions

View File

@ -5,7 +5,7 @@ Trigger Manager for loading and managing trigger providers and triggers
import logging
from collections.abc import Mapping
from threading import Lock
from typing import Any, Optional
from typing import Any
from flask import Request

View File

@ -90,7 +90,7 @@ class WebhookService:
data = {
"method": request.method,
"headers": dict(request.headers),
"query_params": dict(request.args),
"query_params": cls._extract_query_params(),
"body": {},
"files": {},
}
@ -120,6 +120,43 @@ class WebhookService:
return data
@classmethod
def _extract_query_params(cls) -> dict[str, Any]:
"""Extract query parameters preserving multi-value entries."""
if not request.args:
return {}
query_params: dict[str, Any] = {}
for key, value in request.args.items():
query_params[key] = cls._convert_query_param_value(value)
return query_params
@classmethod
def _convert_query_param_value(cls, value: str) -> Any:
"""Convert query parameter strings to numbers or booleans when applicable."""
lower_value = value.lower()
bool_map = {
"true": True,
"false": False,
"yes": True,
"no": False,
}
if lower_value in bool_map:
return bool_map[lower_value]
if cls._can_convert_to_number(value):
try:
numeric_value = float(value)
if numeric_value.is_integer():
return int(numeric_value)
return numeric_value
except ValueError:
return value
return value
@classmethod
def _validate_content_length(cls) -> None:
"""Validate request content length against maximum allowed size."""
@ -285,11 +322,24 @@ class WebhookService:
return cls._validation_error(f"Required header missing: {header_name}")
# Validate required query parameters
query_params = webhook_data.get("query_params", {})
for param in node_data.get("params", []):
if param.get("required", False):
param_name = param.get("name", "")
if param_name not in webhook_data["query_params"]:
return cls._validation_error(f"Required query parameter missing: {param_name}")
param_name = param.get("name", "")
param_type = param.get("type", SegmentType.STRING)
is_required = param.get("required", False)
param_exists = param_name in query_params
if is_required and not param_exists:
return cls._validation_error(f"Required query parameter missing: {param_name}")
if not param_exists:
continue
if param_exists and param_type != SegmentType.STRING:
param_value = query_params[param_name]
validation_result = cls._validate_form_parameter_type(param_name, param_value, param_type)
if not validation_result["valid"]:
return validation_result
return {"valid": True}

View File

@ -26,12 +26,30 @@ class TestWebhookServiceUnit:
assert webhook_data["method"] == "POST"
assert webhook_data["headers"]["Authorization"] == "Bearer token"
assert webhook_data["query_params"]["version"] == "1"
assert webhook_data["query_params"]["version"] == 1
assert webhook_data["query_params"]["format"] == "json"
assert webhook_data["body"]["message"] == "hello"
assert webhook_data["body"]["count"] == 42
assert webhook_data["files"] == {}
def test_extract_webhook_data_query_params_remain_strings(self):
"""Query parameters remain raw strings during extraction."""
app = Flask(__name__)
with app.test_request_context(
"/webhook",
method="GET",
headers={"Content-Type": "application/json"},
query_string="count=42&threshold=3.14&enabled=true&note=text",
):
webhook_trigger = MagicMock()
webhook_data = WebhookService.extract_webhook_data(webhook_trigger)
assert webhook_data["query_params"]["count"] == 42
assert webhook_data["query_params"]["threshold"] == 3.14
assert webhook_data["query_params"]["enabled"] == True
assert webhook_data["query_params"]["note"] == "text"
def test_extract_webhook_data_form_urlencoded(self):
"""Test webhook data extraction from form URL encoded request."""
app = Flask(__name__)
@ -182,6 +200,92 @@ class TestWebhookServiceUnit:
assert result["valid"] is False
assert "Required query parameter missing: version" in result["error"]
def test_validate_webhook_request_query_param_number_type(self):
"""Numeric query parameters should validate with numeric types."""
webhook_data = {
"method": "POST",
"headers": {},
"query_params": {"count": "42"},
"body": {},
"files": {},
}
node_config = {
"data": {
"method": "post",
"params": [{"name": "count", "required": True, "type": "number"}],
}
}
result = WebhookService.validate_webhook_request(webhook_data, node_config)
assert result["valid"] is True
def test_validate_webhook_request_query_param_number_type_invalid(self):
"""Numeric query parameter validation should fail for non-numeric values."""
webhook_data = {
"method": "POST",
"headers": {},
"query_params": {"count": "forty-two"},
"body": {},
"files": {},
}
node_config = {
"data": {
"method": "post",
"params": [{"name": "count", "required": True, "type": "number"}],
}
}
result = WebhookService.validate_webhook_request(webhook_data, node_config)
assert result["valid"] is False
assert "must be a valid number" in result["error"]
def test_validate_webhook_request_query_param_boolean_type(self):
"""Boolean query parameters should validate with supported boolean strings."""
webhook_data = {
"method": "POST",
"headers": {},
"query_params": {"enabled": "true"},
"body": {},
"files": {},
}
node_config = {
"data": {
"method": "post",
"params": [{"name": "enabled", "required": True, "type": "boolean"}],
}
}
result = WebhookService.validate_webhook_request(webhook_data, node_config)
assert result["valid"] is True
def test_validate_webhook_request_query_param_string_type_preserved(self):
"""String typed query parameters remain as strings even if boolean-like."""
webhook_data = {
"method": "POST",
"headers": {},
"query_params": {"flag": "true"},
"body": {},
"files": {},
}
node_config = {
"data": {
"method": "post",
"params": [{"name": "flag", "required": True, "type": "string"}],
}
}
result = WebhookService.validate_webhook_request(webhook_data, node_config)
assert result["valid"] is True
assert webhook_data["query_params"]["flag"] == "true"
def test_validate_webhook_request_missing_required_body_param(self):
"""Test webhook validation with missing required body parameter."""
webhook_data = {"method": "POST", "headers": {}, "query_params": {}, "body": {}, "files": {}}
@ -515,7 +619,7 @@ class TestWebhookServiceUnit:
assert "must be an array of numbers" in result["error"]
def test_validate_json_parameter_type_array_bool(self):
"""Test JSON parameter type validation for array[bool] type."""
"""Test JSON parameter type validation for array[boolean] type."""
# Valid array of booleans
result = WebhookService._validate_json_parameter_type("flags", [True, False, True], "array[boolean]")
assert result["valid"] is True
@ -540,12 +644,6 @@ class TestWebhookServiceUnit:
assert result["valid"] is False
assert "must be an array of objects" in result["error"]
def test_validate_json_parameter_type_unknown_type(self):
"""Test JSON parameter type validation for unknown type."""
# Unknown type should return valid and log warning
result = WebhookService._validate_json_parameter_type("data", "anything", "unknown_type")
assert result["valid"] is True
def test_validate_webhook_request_json_type_validation(self):
"""Test webhook validation with JSON parameter type validation."""
# Test valid JSON types
@ -573,11 +671,11 @@ class TestWebhookServiceUnit:
"body": [
{"name": "name", "type": "string", "required": True},
{"name": "age", "type": "number", "required": True},
{"name": "active", "type": "bool", "required": True},
{"name": "active", "type": "boolean", "required": True},
{"name": "profile", "type": "object", "required": True},
{"name": "tags", "type": "array[string]", "required": True},
{"name": "scores", "type": "array[number]", "required": True},
{"name": "flags", "type": "array[bool]", "required": True},
{"name": "flags", "type": "array[boolean]", "required": True},
{"name": "items", "type": "array[object]", "required": True},
],
}