allthingssecurity commented on code in PR #28037: URL: https://github.com/apache/camel/pull/28037#discussion_r4306583012
########## components/camel-web3j/src/test/java/org/apache/camel/component/web3j/Web3jConsumerResubscribeTest.java: ########## @@ -0,0 +1,195 @@ +/* + * 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.web3j; + +import java.io.IOException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import io.reactivex.Flowable; +import org.apache.camel.EndpointInject; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.spi.Registry; +import org.apache.camel.test.junit6.CamelTestSupport; +import org.junit.jupiter.api.Test; +import org.web3j.protocol.Web3j; +import org.web3j.protocol.core.methods.response.EthBlock; +import org.web3j.protocol.core.methods.response.Log; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.mock; Review Comment: Applied in 57c4f8cd101e, thanks. _Claude Code on behalf of allthingssecurity_ ########## components/camel-web3j/src/test/java/org/apache/camel/component/web3j/Web3jConsumerResubscribeTest.java: ########## @@ -0,0 +1,195 @@ +/* + * 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.web3j; + +import java.io.IOException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import io.reactivex.Flowable; +import org.apache.camel.EndpointInject; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.spi.Registry; +import org.apache.camel.test.junit6.CamelTestSupport; +import org.junit.jupiter.api.Test; +import org.web3j.protocol.Web3j; +import org.web3j.protocol.core.methods.response.EthBlock; +import org.web3j.protocol.core.methods.response.Log; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * A live subscription that ends with an error (such as a failed eth_getBlockByHash request inside web3j's + * blockFlowable) is subscribed again, instead of leaving a started consumer that receives nothing. + */ +class Web3jConsumerResubscribeTest extends CamelTestSupport { + + private final Web3j web3j = mock(Web3j.class); + + @EndpointInject("mock:result") + private MockEndpoint result; + + @EndpointInject("mock:error") + private MockEndpoint error; + + Web3jConsumerResubscribeTest() { + // the mocked client is stubbed before the context starts + testConfiguration().withUseAdviceWith(true); + } + + @Override + protected void bindToRegistry(Registry registry) { + registry.bind("mockWeb3j", web3j); + } + + private static EthBlock block(String number) { + EthBlock.Block block = new EthBlock.Block(); + block.setNumber(number); + EthBlock ethBlock = new EthBlock(); + ethBlock.setResult(block); + return ethBlock; + } + + private void startRoute(String operation) throws Exception { + context.addRoutes(new RouteBuilder() { + @Override + public void configure() { + onException(Exception.class).handled(true).to("mock:error"); + from("web3j://http://127.0.0.1:8545?web3j=#mockWeb3j&operation=" + operation) + .routeId("web3j").autoStartup(false) + .to("mock:result"); + } + }); + context.start(); + ((Web3jConsumer) context.getRoute("web3j").getConsumer()).setResubscribeDelay(10); + context.getRouteController().startRoute("web3j"); + } + + @Test + void blockSubscriptionEndedByAFailedRequestIsSubscribedAgain() throws Exception { + when(web3j.blockFlowable(anyBoolean())).thenReturn( + Flowable.concat(Flowable.just(block("0x1")), Flowable.error(new IOException("Connection reset"))), + Flowable.concat(Flowable.just(block("0x2")), Flowable.never())); + result.expectedMessageCount(2); + error.expectedMessageCount(1); + + startRoute(Web3jConstants.BLOCK_OBSERVABLE); + + MockEndpoint.assertIsSatisfied(context, 5, TimeUnit.SECONDS); + assertEquals("0x2", result.getReceivedExchanges().get(1).getIn().getBody(EthBlock.Block.class).getNumberRaw()); + } + + @Test + void failuresWhileSubscribingAgainAreRoutedOnce() throws Exception { + // the node is unreachable for three attempts, then it answers again + IOException down = new IOException("Connection refused"); + when(web3j.blockFlowable(anyBoolean())).thenReturn( + Flowable.error(down), Flowable.error(down), Flowable.error(down), + Flowable.concat(Flowable.just(block("0x1")), Flowable.never())); + result.expectedMessageCount(1); + error.expectedMessageCount(1); + + startRoute(Web3jConstants.BLOCK_OBSERVABLE); + + MockEndpoint.assertIsSatisfied(context, 5, TimeUnit.SECONDS); + verify(web3j, times(4)).blockFlowable(anyBoolean()); + } + + @Test + void replayOfARangeIsNotSubscribedAgain() throws Exception { + // a replay of a block range would route the blocks of the range again + when(web3j.replayPastBlocksFlowable(any(), any(), anyBoolean())) + .thenReturn(Flowable.concat(Flowable.just(block("0x1")), Flowable.error(new IOException("Connection reset")))); + result.expectedMessageCount(1); + error.expectedMessageCount(1); + + startRoute(Web3jConstants.REPLAY_BLOCKS_OBSERVABLE); + + MockEndpoint.assertIsSatisfied(context, 5, TimeUnit.SECONDS); + verify(web3j, times(1)).replayPastBlocksFlowable(any(), any(), anyBoolean()); Review Comment: You are right, thanks. Applied in 57c4f8cd101e. I checked that it now covers the guard: with `resubscribes()` forced to return `true`, `replayOfARangeIsNotSubscribedAgain` and `logFilterFromAGivenBlockIsNotSubscribedAgain` both fail (3 of 3 runs), and with the guard in place the camel-web3j tests pass (138 run, 0 failures, 67 skipped integration tests). _Claude Code on behalf of allthingssecurity_ ########## components/camel-web3j/src/test/java/org/apache/camel/component/web3j/Web3jConsumerResubscribeTest.java: ########## @@ -0,0 +1,195 @@ +/* + * 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.web3j; + +import java.io.IOException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import io.reactivex.Flowable; +import org.apache.camel.EndpointInject; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.spi.Registry; +import org.apache.camel.test.junit6.CamelTestSupport; +import org.junit.jupiter.api.Test; +import org.web3j.protocol.Web3j; +import org.web3j.protocol.core.methods.response.EthBlock; +import org.web3j.protocol.core.methods.response.Log; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * A live subscription that ends with an error (such as a failed eth_getBlockByHash request inside web3j's + * blockFlowable) is subscribed again, instead of leaving a started consumer that receives nothing. + */ +class Web3jConsumerResubscribeTest extends CamelTestSupport { + + private final Web3j web3j = mock(Web3j.class); + + @EndpointInject("mock:result") + private MockEndpoint result; + + @EndpointInject("mock:error") + private MockEndpoint error; + + Web3jConsumerResubscribeTest() { + // the mocked client is stubbed before the context starts + testConfiguration().withUseAdviceWith(true); + } + + @Override + protected void bindToRegistry(Registry registry) { + registry.bind("mockWeb3j", web3j); + } + + private static EthBlock block(String number) { + EthBlock.Block block = new EthBlock.Block(); + block.setNumber(number); + EthBlock ethBlock = new EthBlock(); + ethBlock.setResult(block); + return ethBlock; + } + + private void startRoute(String operation) throws Exception { + context.addRoutes(new RouteBuilder() { + @Override + public void configure() { + onException(Exception.class).handled(true).to("mock:error"); + from("web3j://http://127.0.0.1:8545?web3j=#mockWeb3j&operation=" + operation) + .routeId("web3j").autoStartup(false) + .to("mock:result"); + } + }); + context.start(); + ((Web3jConsumer) context.getRoute("web3j").getConsumer()).setResubscribeDelay(10); + context.getRouteController().startRoute("web3j"); + } + + @Test + void blockSubscriptionEndedByAFailedRequestIsSubscribedAgain() throws Exception { + when(web3j.blockFlowable(anyBoolean())).thenReturn( + Flowable.concat(Flowable.just(block("0x1")), Flowable.error(new IOException("Connection reset"))), + Flowable.concat(Flowable.just(block("0x2")), Flowable.never())); + result.expectedMessageCount(2); + error.expectedMessageCount(1); + + startRoute(Web3jConstants.BLOCK_OBSERVABLE); + + MockEndpoint.assertIsSatisfied(context, 5, TimeUnit.SECONDS); + assertEquals("0x2", result.getReceivedExchanges().get(1).getIn().getBody(EthBlock.Block.class).getNumberRaw()); + } + + @Test + void failuresWhileSubscribingAgainAreRoutedOnce() throws Exception { + // the node is unreachable for three attempts, then it answers again + IOException down = new IOException("Connection refused"); + when(web3j.blockFlowable(anyBoolean())).thenReturn( + Flowable.error(down), Flowable.error(down), Flowable.error(down), + Flowable.concat(Flowable.just(block("0x1")), Flowable.never())); + result.expectedMessageCount(1); + error.expectedMessageCount(1); + + startRoute(Web3jConstants.BLOCK_OBSERVABLE); + + MockEndpoint.assertIsSatisfied(context, 5, TimeUnit.SECONDS); + verify(web3j, times(4)).blockFlowable(anyBoolean()); + } + + @Test + void replayOfARangeIsNotSubscribedAgain() throws Exception { + // a replay of a block range would route the blocks of the range again + when(web3j.replayPastBlocksFlowable(any(), any(), anyBoolean())) + .thenReturn(Flowable.concat(Flowable.just(block("0x1")), Flowable.error(new IOException("Connection reset")))); + result.expectedMessageCount(1); + error.expectedMessageCount(1); + + startRoute(Web3jConstants.REPLAY_BLOCKS_OBSERVABLE); + + MockEndpoint.assertIsSatisfied(context, 5, TimeUnit.SECONDS); + verify(web3j, times(1)).replayPastBlocksFlowable(any(), any(), anyBoolean()); + } + + @Test + void logFilterFromAGivenBlockIsNotSubscribedAgain() throws Exception { + // a new filter from the same block would route the logs from that block again + when(web3j.ethLogFlowable(any())).thenReturn(Flowable.error(new IOException("Connection refused"))); + error.expectedMessageCount(1); + + startRoute(Web3jConstants.ETH_LOG_OBSERVABLE + "&fromBlock=16"); + + MockEndpoint.assertIsSatisfied(context, 5, TimeUnit.SECONDS); + verify(web3j, times(1)).ethLogFlowable(any()); Review Comment: Applied in 57c4f8cd101e as well; this test now fails when the guard is removed. _Claude Code on behalf of allthingssecurity_ -- 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]
