diff --git a/internal/destregistry/metadata/providers/aws_kinesis/metadata.json b/internal/destregistry/metadata/providers/aws_kinesis/metadata.json index 7bc5e81d5..dcf40a87d 100644 --- a/internal/destregistry/metadata/providers/aws_kinesis/metadata.json +++ b/internal/destregistry/metadata/providers/aws_kinesis/metadata.json @@ -26,7 +26,7 @@ "label": "Endpoint", "description": "Custom endpoint URL for AWS Kinesis (optional, for testing or VPC endpoints)", "required": false, - "pattern": "^https?:\\/\\/[\\w\\-]+(?:\\.[\\w\\-]+)*(?::\\d{1,5})?(?:\\/[\\w\\-\\/\\.~:?#\\[\\]@!$&'\\(\\)*+,;=]*)?$" + "pattern": "^https?:\\/\\/[\\w\\-]+(?:\\.[\\w\\-]+)*(?::\\d{1,5})?(?:\\/[\\w\\-\\/\\.~:%?#\\[\\]@!$&'\\(\\)*+,;=]*)?$" }, { "key": "partition_key_template", diff --git a/internal/destregistry/metadata/providers/aws_sqs/metadata.json b/internal/destregistry/metadata/providers/aws_sqs/metadata.json index 8e0ca8f1a..99f130fc4 100644 --- a/internal/destregistry/metadata/providers/aws_sqs/metadata.json +++ b/internal/destregistry/metadata/providers/aws_sqs/metadata.json @@ -7,7 +7,7 @@ "label": "Queue URL", "description": "The URL of your AWS SQS queue", "required": true, - "pattern": "^https?:\\/\\/[\\w\\-]+(?:\\.[\\w\\-]+)*(?::\\d{1,5})?(?:\\/[\\w\\-\\/\\.~:?#\\[\\]@!$&'\\(\\)*+,;=]*)?$" + "pattern": "^https?:\\/\\/[\\w\\-]+(?:\\.[\\w\\-]+)*(?::\\d{1,5})?(?:\\/[\\w\\-\\/\\.~:%?#\\[\\]@!$&'\\(\\)*+,;=]*)?$" } ], "credential_fields": [ diff --git a/internal/destregistry/metadata/providers/webhook/metadata.json b/internal/destregistry/metadata/providers/webhook/metadata.json index 0496d8499..8227d8883 100644 --- a/internal/destregistry/metadata/providers/webhook/metadata.json +++ b/internal/destregistry/metadata/providers/webhook/metadata.json @@ -7,7 +7,7 @@ "label": "Webhook URL", "description": "The URL to send webhook events to via HTTP POST", "required": true, - "pattern": "^https?:\\/\\/[\\w\\-]+(?:\\.[\\w\\-]+)*(?::\\d{1,5})?(?:\\/[\\w\\-\\/\\.~:?#\\[\\]@!$&'\\(\\)*+,;=]*)?$" + "pattern": "^https?:\\/\\/[\\w\\-]+(?:\\.[\\w\\-]+)*(?::\\d{1,5})?(?:\\/[\\w\\-\\/\\.~:%?#\\[\\]@!$&'\\(\\)*+,;=]*)?$" }, { "key": "custom_headers", diff --git a/internal/destregistry/metadata/providers/webhook_standard/metadata.json b/internal/destregistry/metadata/providers/webhook_standard/metadata.json index e3f240cd0..09b501452 100644 --- a/internal/destregistry/metadata/providers/webhook_standard/metadata.json +++ b/internal/destregistry/metadata/providers/webhook_standard/metadata.json @@ -7,7 +7,7 @@ "label": "Webhook URL", "description": "The URL to send webhook events to via HTTP POST (Standard Webhooks compliant)", "required": true, - "pattern": "^https?:\\/\\/[\\w\\-]+(?:\\.[\\w\\-]+)*(?::\\d{1,5})?(?:\\/[\\w\\-\\/\\.~:?#\\[\\]@!$&'\\(\\)*+,;=]*)?$" + "pattern": "^https?:\\/\\/[\\w\\-]+(?:\\.[\\w\\-]+)*(?::\\d{1,5})?(?:\\/[\\w\\-\\/\\.~:%?#\\[\\]@!$&'\\(\\)*+,;=]*)?$" }, { "key": "custom_headers", diff --git a/internal/destregistry/providers/destwebhook/destwebhook_validate_test.go b/internal/destregistry/providers/destwebhook/destwebhook_validate_test.go index 3b505529b..3268f185f 100644 --- a/internal/destregistry/providers/destwebhook/destwebhook_validate_test.go +++ b/internal/destregistry/providers/destwebhook/destwebhook_validate_test.go @@ -72,6 +72,51 @@ func TestWebhookDestination_Validate(t *testing.T) { assert.Equal(t, "config.url", validationErr.Errors[0].Field) assert.Equal(t, "pattern", validationErr.Errors[0].Type) }) + + t.Run("should accept valid URLs", func(t *testing.T) { + t.Parallel() + validURLs := []string{ + "https://example.com", + "https://example.com/path", + "https://example.com:8080/path", + "https://example.com/path?query=value", + "https://example.com/path#fragment", + "https://sub.example.com/path", + "http://localhost:3000/webhook", + // Percent-encoded URLs (Azure Logic Apps, etc.) + "https://example.com/path?param=%2Fencoded%2Fslash", + "https://example.com/path%2Fwith%2Fencoded", + "https://logic.azure.com/workflows/abc123/triggers/manual?api-version=2016&sp=%2Ftriggers%2Fmanual%2Frun", + } + for _, url := range validURLs { + t.Run(url, func(t *testing.T) { + t.Parallel() + dest := validDestination + dest.Config = map[string]string{"url": url} + assert.NoError(t, webhookDestination.Validate(context.Background(), &dest)) + }) + } + }) + + t.Run("should reject invalid URLs", func(t *testing.T) { + t.Parallel() + invalidURLs := []string{ + "not-a-url", + "ftp://example.com", + "://missing-scheme.com", + "https://", + "", + } + for _, url := range invalidURLs { + t.Run(url, func(t *testing.T) { + t.Parallel() + dest := validDestination + dest.Config = map[string]string{"url": url} + err := webhookDestination.Validate(context.Background(), &dest) + assert.Error(t, err) + }) + } + }) } func TestWebhookDestination_ValidateSecrets(t *testing.T) {