arunsrajan commented on code in PR #26010:
URL: https://github.com/apache/camel/pull/26010#discussion_r3913346505


##########
components/camel-alibaba/camel-alibaba-eventbridge/src/test/java/org/apache/camel/component/alibaba/eventbridge/MapCloudEventValidationTest.java:
##########
@@ -0,0 +1,332 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.alibaba.eventbridge;
+
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import com.aliyun.eventbridge.EventBridgeClient;
+import com.aliyun.eventbridge.models.CloudEvent;
+import com.aliyun.eventbridge.models.EventBusEntry;
+import com.aliyun.eventbridge.models.EventRuleDTO;
+import com.aliyun.eventbridge.models.ListEventBusesRequest;
+import com.aliyun.eventbridge.models.ListEventBusesResponse;
+import com.aliyun.eventbridge.models.ListRulesRequest;
+import com.aliyun.eventbridge.models.ListRulesResponse;
+import org.apache.camel.Exchange;
+import org.apache.camel.component.alibaba.eventbridge.models.AllowedEventBus;
+import 
org.apache.camel.component.alibaba.eventbridge.models.AllowedEventSource;
+import 
org.apache.camel.component.alibaba.eventbridge.models.ClientConfigurations;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+class MapCloudEventValidationTest extends CamelTestSupport {
+
+    private EventBridgeClient eventBridgeClient;
+    private EventSourceCache eventSourceCache;
+    private MapCloudEventValidator validator;
+
+    @BeforeEach
+    void initTest() {
+        eventBridgeClient = mock(EventBridgeClient.class);
+        eventSourceCache = new EventSourceCache(300000L);
+        validator = new MapCloudEventValidator(eventSourceCache);
+    }
+
+    @Test
+    void testValidateAndBuildSuccessWithFullMap() {
+        ClientConfigurations config
+                = new ClientConfigurations(null, "default-bus", null, null, 
null, false, false, true, Map.of(), 300000L);
+
+        Map<String, Object> map = new HashMap<>();
+        map.put("eventBusName", "my-bus");
+        map.put("source", "acs:oss:cn-hangzhou:12345:my-bucket");
+        map.put("type", "oss:ObjectCreated:PutObject");
+        map.put("id", "event-id-123");
+        map.put("specversion", "1.0");
+        map.put("subject", "my-object.jpg");
+        map.put("time", "2026-08-23T10:15:30Z");
+        map.put("datacontenttype", "application/json");
+        map.put("dataschema", "http://example.com/schema.json";);
+        map.put("data", Map.of("fileSize", 1024, "bucket", "my-bucket"));
+
+        CloudEvent event = validator.validateAndBuild(map, config, 
eventBridgeClient);
+
+        assertThat(event).isNotNull();
+        
assertThat(event.getSource().toString()).isEqualTo("acs:oss:cn-hangzhou:12345:my-bucket");
+        assertThat(event.getType()).isEqualTo("oss:ObjectCreated:PutObject");
+        assertThat(event.getId()).isEqualTo("event-id-123");
+        assertThat(event.getSubject()).isEqualTo("my-object.jpg");
+        assertThat(event.getSpecversion()).isEqualTo("1.0");
+        assertThat(event.getDatacontenttype()).isEqualTo("application/json");
+        
assertThat(event.getDataschema().toString()).isEqualTo("http://example.com/schema.json";);
+        assertThat(new String(event.getData(), 
StandardCharsets.UTF_8)).contains("\"fileSize\":1024");
+    }
+
+    @Test
+    void testValidateAndBuildWithFallbackConfig() {
+        ClientConfigurations config
+                = new ClientConfigurations(null, "default-bus", 
"my.custom.app", "order.created", "order-999");
+
+        Map<String, Object> map = new HashMap<>();
+        map.put("data", "{\"orderId\":\"999\"}");
+
+        CloudEvent event = validator.validateAndBuild(map, config, 
eventBridgeClient);
+
+        assertThat(event).isNotNull();
+        assertThat(event.getSource().toString()).isEqualTo("my.custom.app");
+        assertThat(event.getType()).isEqualTo("order.created");
+        assertThat(event.getSubject()).isEqualTo("order-999");
+        assertThat(new String(event.getData(), 
StandardCharsets.UTF_8)).isEqualTo("{\"orderId\":\"999\"}");
+    }
+
+    @Test
+    void testValidateFailsWhenBusNameMissing() {
+        ClientConfigurations config = new ClientConfigurations();
+        Map<String, Object> map = Map.of("source", "my.source", "type", 
"my.type");
+
+        assertThatThrownBy(() -> validator.validateAndBuild(map, config, 
eventBridgeClient))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("Event bus name is required");
+    }
+
+    @Test
+    void testValidateFailsWhenSourceMissing() {
+        ClientConfigurations config = new ClientConfigurations(null, 
"test-bus", null, null, null);
+        Map<String, Object> map = Map.of("type", "my.type");
+
+        assertThatThrownBy(() -> validator.validateAndBuild(map, config, 
eventBridgeClient))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("Event 'source' cannot be empty");
+    }
+
+    @Test
+    void testValidateFailsWhenTypeMissing() {
+        ClientConfigurations config = new ClientConfigurations(null, 
"test-bus", "my.source", null, null);
+        Map<String, Object> map = Map.of("source", "my.source");
+
+        assertThatThrownBy(() -> validator.validateAndBuild(map, config, 
eventBridgeClient))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("Event 'type' cannot be empty");
+    }
+
+    @Test
+    void testValidateFailsWhenInvalidSpecversion() {
+        ClientConfigurations config = new ClientConfigurations(null, 
"test-bus", null, null, null, false, true);
+
+        Map<String, Object> map = Map.of(
+                "source", "my.source",
+                "type", "my.type",
+                "specversion", "0.3");
+
+        assertThatThrownBy(() -> validator.validateAndBuild(map, config, 
eventBridgeClient))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("Invalid CloudEvent specversion: '0.3'");
+    }
+
+    @Test
+    void testSingleBusDslWithColonsInSourceAndTypes() {
+        String dsl
+                = "acs:oss:cn-hangzhou:12345:my-bucket -> 
oss:ObjectCreated:PutObject, oss:ObjectCreated:PostObject ; app.orders -> 
order:created:v1";
+        Map<String, AllowedEventBus> buses = 
AlibabaEventBridgeUtils.parseAllowedBusesFromString(dsl, "order-bus");
+
+        assertThat(buses).containsKey("order-bus");
+        AllowedEventBus bus = buses.get("order-bus");
+        
assertThat(bus.allowedSources()).containsKeys("acs:oss:cn-hangzhou:12345:my-bucket",
 "app.orders");
+
+        AllowedEventSource ossSource = 
bus.allowedSources().get("acs:oss:cn-hangzhou:12345:my-bucket");
+        assertThat(ossSource.allowedEventTypes()).containsExactlyInAnyOrder(
+                "oss:ObjectCreated:PutObject", "oss:ObjectCreated:PostObject");
+
+        ClientConfigurations config = new ClientConfigurations(
+                null, "order-bus", null, null, null, false, false, true, 
buses, 300000L);
+
+        Map<String, Object> validEvent = Map.of(
+                "source", "acs:oss:cn-hangzhou:12345:my-bucket",
+                "type", "oss:ObjectCreated:PutObject");
+
+        CloudEvent event = validator.validateAndBuild(validEvent, config, 
eventBridgeClient);
+        assertThat(event).isNotNull();
+
+        Map<String, Object> invalidTypeEvent = Map.of(
+                "source", "acs:oss:cn-hangzhou:12345:my-bucket",
+                "type", "oss:ObjectDeleted:DeleteObject");
+
+        assertThatThrownBy(() -> validator.validateAndBuild(invalidTypeEvent, 
config, eventBridgeClient))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("Event type 
'oss:ObjectDeleted:DeleteObject' is not allowed for event source");
+    }
+
+    @Test
+    void testMultiBusDslWithColonsInSourceAndTypes() {
+        String dsl
+                = "orders-bus[ acs:oss:cn-hangzhou:12345:orders -> 
oss:ObjectCreated:PutObject ; app.orders -> order:created:v1 ]"
+                  + " | payments-bus[ app.payments -> payment:authorized:v1, 
payment:captured:v1 ]";
+
+        Map<String, AllowedEventBus> buses = 
AlibabaEventBridgeUtils.parseAllowedBusesFromString(dsl, null);
+        assertThat(buses).containsKeys("orders-bus", "payments-bus");
+
+        ClientConfigurations config = new ClientConfigurations(
+                null, "orders-bus", null, null, null, false, false, true, 
buses, 300000L);
+
+        Map<String, Object> event1 = Map.of(
+                "eventBusName", "orders-bus",
+                "source", "app.orders",
+                "type", "order:created:v1");
+        assertThat(validator.validateAndBuild(event1, config, 
eventBridgeClient)).isNotNull();
+
+        Map<String, Object> event2 = Map.of(
+                "eventBusName", "payments-bus",
+                "source", "app.payments",
+                "type", "payment:captured:v1");
+        assertThat(validator.validateAndBuild(event2, config, 
eventBridgeClient)).isNotNull();
+
+        Map<String, Object> invalidEvent1 = Map.of(
+                "eventBusName", "orders-bus",
+                "source", "app.payments",
+                "type", "payment:captured:v1");
+        assertThatThrownBy(() -> validator.validateAndBuild(invalidEvent1, 
config, eventBridgeClient))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("Event source 'app.payments' is not in 
the allowed sources list for bus 'orders-bus'");
+
+        Map<String, Object> invalidEvent2 = Map.of(
+                "eventBusName", "unknown-bus",
+                "source", "app.orders",
+                "type", "order:created:v1");
+        assertThatThrownBy(() -> validator.validateAndBuild(invalidEvent2, 
config, eventBridgeClient))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("Event bus 'unknown-bus' is not in the 
allowed event buses list");
+    }
+
+    @Test
+    void testJsonConfigurationParsing() {
+        String json = """
+                {
+                  "orders-bus": {
+                    "acs:oss:cn-hangzhou:12345:orders": 
["oss:ObjectCreated:PutObject", "oss:ObjectCreated:PostObject"],
+                    "app.orders": ["order:created:v1"]
+                  },
+                  "payments-bus": {
+                    "app.payments": ["payment:authorized:v1"]
+                  }
+                }
+                """;
+
+        Map<String, AllowedEventBus> buses = 
AlibabaEventBridgeUtils.parseAllowedBusesFromString(json, null);
+        assertThat(buses).containsKeys("orders-bus", "payments-bus");
+
+        AllowedEventBus ordersBus = buses.get("orders-bus");
+        
assertThat(ordersBus.allowedSources().get("acs:oss:cn-hangzhou:12345:orders").allowedEventTypes())
+                .contains("oss:ObjectCreated:PutObject", 
"oss:ObjectCreated:PostObject");
+    }
+
+    @Test
+    void testValidatedCacheWorkflowSuccess() {

Review Comment:
   Added comprehensive unit tests in `MapCloudEventValidationTest.java` 
covering all reported scenarios:
   - `testValidateCloudEventDirectObjectValidation`: tests direct `CloudEvent` 
object validation against whitelists and cloud rules.
   - `testCloudApiFailureFailsClosedOnBusCheck`: verifies fail-closed exception 
handling when `listEventBuses` fails.
   - `testCloudApiFailureFailsClosedOnRulesCheck`: verifies fail-closed 
exception handling when `listRules` fails.
   - `testPrefixFilterPatternMatching`: verifies Alibaba rule prefix matching 
(`{"prefix": "..."}`).
   - `testValidateEventSpecFalseAllowsNonStandardValues`: verifies disabling 
`validateEventSpec` permits non-standard specification attributes.
   - `testPerMessageCacheTtlHeaderOverride`: verifies per-exchange cache TTL 
header overrides.
   - `testRejectionWhenMapBodyOverridesBusOrSourceOutsideWhitelist`: verifies 
body overrides outside whitelist are rejected.
   



##########
components/camel-alibaba/camel-alibaba-common/src/main/java/org/apache/camel/component/alibaba/common/OpenApiClientSupport.java:
##########


Review Comment:
   Confirmed. `OpenApiClientSupport` preserves the exact credential/header 
resolution precedence (`header` -> `property` -> `endpoint default / client 
config`) across all Alibaba components.
   
   All test suites across all 7 Alibaba modules have been run and are green 
(49/49 tests passing):
   - `camel-alibaba-common`: SUCCESS
   - `camel-alibaba-fc`: SUCCESS (1/1 tests)
   - `camel-alibaba-kms`: SUCCESS (1/1 tests)
   - `camel-alibaba-mns`: SUCCESS (7/7 tests)
   - `camel-alibaba-oss`: SUCCESS (14/14 tests)
   - `camel-alibaba-sms`: SUCCESS (1/1 tests)
   - `camel-alibaba-eventbridge`: SUCCESS (24/24 tests)
   



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to