diff --git a/api/tests/test_containers_integration_tests/services/test_webhook_service.py b/api/tests/test_containers_integration_tests/services/test_webhook_service.py index 36776778e7..f4e6d8447f 100644 --- a/api/tests/test_containers_integration_tests/services/test_webhook_service.py +++ b/api/tests/test_containers_integration_tests/services/test_webhook_service.py @@ -20,9 +20,9 @@ class TestWebhookService: def mock_external_dependencies(self): """Mock external service dependencies.""" with ( - patch("services.webhook_service.AsyncWorkflowService") as mock_async_service, - patch("services.webhook_service.ToolFileManager") as mock_tool_file_manager, - patch("services.webhook_service.file_factory") as mock_file_factory, + patch("services.trigger.webhook_service.AsyncWorkflowService") as mock_async_service, + patch("services.trigger.webhook_service.ToolFileManager") as mock_tool_file_manager, + patch("services.trigger.webhook_service.file_factory") as mock_file_factory, patch("services.account_service.FeatureService") as mock_feature_service, ): # Mock ToolFileManager @@ -244,106 +244,166 @@ class TestWebhookService: assert webhook_data["method"] == "POST" assert webhook_data["body"]["raw"] == "raw text content" - def test_validate_webhook_request_success(self): - """Test successful webhook request validation.""" - webhook_data = { - "method": "POST", - "headers": {"Authorization": "Bearer token", "Content-Type": "application/json"}, - "query_params": {"version": "1"}, - "body": {"message": "hello"}, - "files": {}, - } + def test_extract_and_validate_webhook_request_success(self): + """Test successful webhook request validation and type conversion.""" + app = Flask(__name__) - node_config = { - "data": { - "method": "post", - "headers": [{"name": "Authorization", "required": True}, {"name": "Content-Type", "required": False}], - "params": [{"name": "version", "required": True}], - "body": [{"name": "message", "type": "string", "required": True}], + with app.test_request_context( + "/webhook", + method="POST", + headers={"Content-Type": "application/json", "Authorization": "Bearer token"}, + query_string="version=1", + json={"message": "hello"}, + ): + webhook_trigger = MagicMock() + node_config = { + "data": { + "method": "post", + "content_type": "application/json", + "headers": [ + {"name": "Authorization", "required": True}, + {"name": "Content-Type", "required": False}, + ], + "params": [{"name": "version", "required": True}], + "body": [{"name": "message", "type": "string", "required": True}], + } } - } - result = WebhookService.validate_webhook_request(webhook_data, node_config) + result = WebhookService.extract_and_validate_webhook_data(webhook_trigger, node_config) - assert result["valid"] is True + assert result["headers"]["Authorization"] == "Bearer token" + assert result["query_params"]["version"] == "1" + assert result["body"]["message"] == "hello" - def test_validate_webhook_request_method_mismatch(self): + def test_extract_and_validate_webhook_request_method_mismatch(self): """Test webhook validation with HTTP method mismatch.""" - webhook_data = {"method": "GET", "headers": {}, "query_params": {}, "body": {}, "files": {}} + app = Flask(__name__) - node_config = {"data": {"method": "post"}} + with app.test_request_context( + "/webhook", + method="GET", + headers={"Content-Type": "application/json"}, + ): + webhook_trigger = MagicMock() + node_config = {"data": {"method": "post", "content_type": "application/json"}} - result = WebhookService.validate_webhook_request(webhook_data, node_config) + with pytest.raises(ValueError, match="HTTP method mismatch"): + WebhookService.extract_and_validate_webhook_data(webhook_trigger, node_config) - assert result["valid"] is False - assert "HTTP method mismatch" in result["error"] - - def test_validate_webhook_request_missing_required_header(self): + def test_extract_and_validate_webhook_request_missing_required_header(self): """Test webhook validation with missing required header.""" - webhook_data = {"method": "POST", "headers": {}, "query_params": {}, "body": {}, "files": {}} + app = Flask(__name__) - node_config = {"data": {"method": "post", "headers": [{"name": "Authorization", "required": True}]}} - - result = WebhookService.validate_webhook_request(webhook_data, node_config) - - assert result["valid"] is False - assert "Required header missing: Authorization" in result["error"] - - def test_validate_webhook_request_case_insensitive_headers(self): - """Test webhook validation with case-insensitive header matching.""" - webhook_data = { - "method": "POST", - "headers": {"authorization": "Bearer token"}, # lowercase - "query_params": {}, - "body": {}, - "files": {}, - } - - node_config = { - "data": { - "method": "post", - "headers": [ - {"name": "Authorization", "required": True} # Pascal case - ], + with app.test_request_context( + "/webhook", + method="POST", + headers={"Content-Type": "application/json"}, + ): + webhook_trigger = MagicMock() + node_config = { + "data": { + "method": "post", + "content_type": "application/json", + "headers": [{"name": "Authorization", "required": True}], + } } - } - result = WebhookService.validate_webhook_request(webhook_data, node_config) + with pytest.raises(ValueError, match="Required header missing: Authorization"): + WebhookService.extract_and_validate_webhook_data(webhook_trigger, node_config) - assert result["valid"] is True + def test_extract_and_validate_webhook_request_case_insensitive_headers(self): + """Test webhook validation with case-insensitive header matching.""" + app = Flask(__name__) - def test_validate_webhook_request_missing_required_param(self): + with app.test_request_context( + "/webhook", + method="POST", + headers={"Content-Type": "application/json", "authorization": "Bearer token"}, + json={"message": "hello"}, + ): + webhook_trigger = MagicMock() + node_config = { + "data": { + "method": "post", + "content_type": "application/json", + "headers": [{"name": "Authorization", "required": True}], + "body": [{"name": "message", "type": "string", "required": True}], + } + } + + result = WebhookService.extract_and_validate_webhook_data(webhook_trigger, node_config) + + assert result["headers"].get("Authorization") == "Bearer token" + + def test_extract_and_validate_webhook_request_missing_required_param(self): """Test webhook validation with missing required query parameter.""" - webhook_data = {"method": "POST", "headers": {}, "query_params": {}, "body": {}, "files": {}} + app = Flask(__name__) - node_config = {"data": {"method": "post", "params": [{"name": "version", "required": True}]}} + with app.test_request_context( + "/webhook", + method="POST", + headers={"Content-Type": "application/json"}, + json={"message": "hello"}, + ): + webhook_trigger = MagicMock() + node_config = { + "data": { + "method": "post", + "content_type": "application/json", + "params": [{"name": "version", "required": True}], + "body": [{"name": "message", "type": "string", "required": True}], + } + } - result = WebhookService.validate_webhook_request(webhook_data, node_config) + with pytest.raises(ValueError, match="Required parameter missing: version"): + WebhookService.extract_and_validate_webhook_data(webhook_trigger, node_config) - assert result["valid"] is False - assert "Required query parameter missing: version" in result["error"] - - def test_validate_webhook_request_missing_required_body_param(self): + def test_extract_and_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": {}} + app = Flask(__name__) - node_config = {"data": {"method": "post", "body": [{"name": "message", "type": "string", "required": True}]}} + with app.test_request_context( + "/webhook", + method="POST", + headers={"Content-Type": "application/json"}, + json={}, + ): + webhook_trigger = MagicMock() + node_config = { + "data": { + "method": "post", + "content_type": "application/json", + "body": [{"name": "message", "type": "string", "required": True}], + } + } - result = WebhookService.validate_webhook_request(webhook_data, node_config) + with pytest.raises(ValueError, match="Required body parameter missing: message"): + WebhookService.extract_and_validate_webhook_data(webhook_trigger, node_config) - assert result["valid"] is False - assert "Required body parameter missing: message" in result["error"] + def test_extract_and_validate_webhook_request_missing_required_file(self): + """Test webhook validation when required file is missing from multipart request.""" + app = Flask(__name__) - def test_validate_webhook_request_missing_required_file(self): - """Test webhook validation with missing required file parameter.""" - webhook_data = {"method": "POST", "headers": {}, "query_params": {}, "body": {}, "files": {}} + with app.test_request_context( + "/webhook", + method="POST", + data={"note": "test"}, + content_type="multipart/form-data", + ): + webhook_trigger = MagicMock() + webhook_trigger.tenant_id = "tenant" + webhook_trigger.created_by = "user" + node_config = { + "data": { + "method": "post", + "content_type": "multipart/form-data", + "body": [{"name": "upload", "type": "file", "required": True}], + } + } - node_config = {"data": {"method": "post", "body": [{"name": "upload", "type": "file", "required": True}]}} + result = WebhookService.extract_and_validate_webhook_data(webhook_trigger, node_config) - result = WebhookService.validate_webhook_request(webhook_data, node_config) - - assert result["valid"] is False - assert "Required file parameter missing: upload" in result["error"] + assert result["files"] == {} def test_trigger_workflow_execution_success(self, test_data, mock_external_dependencies, flask_app_with_containers): """Test successful workflow execution trigger.""" @@ -357,12 +417,12 @@ class TestWebhookService: with flask_app_with_containers.app_context(): # Mock tenant owner lookup to return the test account - with patch("services.webhook_service.select") as mock_select: + with patch("services.trigger.webhook_service.select") as mock_select: mock_query = MagicMock() mock_select.return_value.join.return_value.where.return_value = mock_query # Mock the session to return our test account - with patch("services.webhook_service.Session") as mock_session: + with patch("services.trigger.webhook_service.Session") as mock_session: mock_session_instance = MagicMock() mock_session.return_value.__enter__.return_value = mock_session_instance mock_session_instance.scalar.return_value = test_data["account"] @@ -384,8 +444,8 @@ class TestWebhookService: with flask_app_with_containers.app_context(): # Mock tenant owner lookup to return None with ( - patch("services.webhook_service.select") as mock_select, - patch("services.webhook_service.Session") as mock_session, + patch("services.trigger.webhook_service.select") as mock_select, + patch("services.trigger.webhook_service.Session") as mock_session, ): mock_session_instance = MagicMock() mock_session.return_value.__enter__.return_value = mock_session_instance