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]
