From 50e2b7937b4dcdf7fbaada44a553d6cc78574b73 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 14 Nov 2025 08:50:15 +0000 Subject: [PATCH 01/11] Initial plan From 20e588a2a4971eb63d89dd00870b900228f27688 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 14 Nov 2025 09:22:29 +0000 Subject: [PATCH 02/11] Add unit test for Spring Cloud Azure Service Bus JMS CachingConnectionFactory behavior Verify that when using CachingConnectionFactory mode, producer and consumer caching is properly configured for reusing MessageProducer and MessageConsumer instances for the same destination. Test covers both standard and premium pricing tiers. Co-authored-by: Netyyyy <92105726+Netyyyy@users.noreply.github.com> --- ...msConnectionFactoryConfigurationTests.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryConfigurationTests.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryConfigurationTests.java index 10c9fe284a21..b45ab8ec708a 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryConfigurationTests.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryConfigurationTests.java @@ -5,6 +5,7 @@ import com.azure.servicebus.jms.ServiceBusJmsConnectionFactory; import com.azure.spring.cloud.autoconfigure.implementation.context.properties.AzureGlobalProperties; +import jakarta.jms.ConnectionFactory; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.messaginghub.pooled.jms.JmsPoolConnectionFactory; @@ -140,6 +141,36 @@ void useCacheConnectionViaAdditionConfigurationFile(String pricingTier) { }); } + @ParameterizedTest + @ValueSource(strings = { "standard", "premium" }) + void cachingConnectionFactoryCachesProducersAndConsumersForSameDestination(String pricingTier) { + this.contextRunner + .withPropertyValues( + "spring.jms.servicebus.pricing-tier=" + pricingTier, + "spring.jms.servicebus.pool.enabled=false", + "spring.jms.cache.producers=true", + "spring.jms.cache.consumers=true" + ) + .run(context -> { + assertThat(context).hasSingleBean(CachingConnectionFactory.class); + + ConnectionFactory connectionFactory = context.getBean(ConnectionFactory.class); + assertThat(connectionFactory).isInstanceOf(CachingConnectionFactory.class); + + CachingConnectionFactory cachingFactory = (CachingConnectionFactory) connectionFactory; + + // Verify that producer and consumer caching is enabled + // When these properties are true, CachingConnectionFactory will cache and reuse + // MessageProducer and MessageConsumer instances for the same destination + assertThat(cachingFactory.isCacheProducers()) + .as("CachingConnectionFactory should cache MessageProducers for the same destination") + .isTrue(); + assertThat(cachingFactory.isCacheConsumers()) + .as("CachingConnectionFactory should cache MessageConsumers for the same destination") + .isTrue(); + }); + } + @Configuration @PropertySource("classpath:servicebus/additional.properties") static class AdditionalPropertySourceConfiguration { From 8c1338696b876e4a3652b1f0760f4b0639b80456 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 1 Dec 2025 09:07:25 +0000 Subject: [PATCH 03/11] Update azure-servicebus-jms to 2.1.0 and simplify UT code - Updated azure-servicebus-jms version from 2.0.0 to 2.1.0 in: - eng/versioning/external_dependencies.txt - sdk/spring/spring-cloud-azure-autoconfigure/pom.xml - sdk/spring/spring-cloud-azure-starter-servicebus-jms/pom.xml - Simplified test code per review feedback: - Removed unnecessary blank lines - Simplified bean retrieval by getting CachingConnectionFactory directly - Removed unused imports - Test verifies that CachingConnectionFactory caches producers/consumers for the same destination when configured with appropriate properties Co-authored-by: Netyyyy <92105726+Netyyyy@users.noreply.github.com> --- eng/versioning/external_dependencies.txt | 2 +- sdk/spring/spring-cloud-azure-autoconfigure/pom.xml | 4 ++-- .../ServiceBusJmsConnectionFactoryConfigurationTests.java | 8 +------- .../spring-cloud-azure-starter-servicebus-jms/pom.xml | 2 +- 4 files changed, 5 insertions(+), 11 deletions(-) diff --git a/eng/versioning/external_dependencies.txt b/eng/versioning/external_dependencies.txt index 47e532c6ee5c..dc83f86589f0 100644 --- a/eng/versioning/external_dependencies.txt +++ b/eng/versioning/external_dependencies.txt @@ -8,7 +8,7 @@ # Format; # groupId:artifactId;dependency-version ch.qos.logback:logback-classic;1.3.14 -com.azure:azure-servicebus-jms;2.0.0 +com.azure:azure-servicebus-jms;2.1.0 com.azure.tools:azure-autorest-customization;1.0.0-beta.11 com.fasterxml:aalto-xml;1.3.3 com.fasterxml.jackson.core:jackson-annotations;2.18.4 diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/pom.xml b/sdk/spring/spring-cloud-azure-autoconfigure/pom.xml index b6cdb1aefa60..d9a5de8fe98d 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/pom.xml +++ b/sdk/spring/spring-cloud-azure-autoconfigure/pom.xml @@ -140,7 +140,7 @@ com.azure azure-servicebus-jms - 2.0.0 + 2.1.0 true @@ -542,7 +542,7 @@ - com.azure:azure-servicebus-jms:[2.0.0] + com.azure:azure-servicebus-jms:[2.1.0] com.mysql:mysql-connector-j:[9.4.0] com.fasterxml.jackson.core:jackson-annotations:[2.19.2] com.fasterxml.jackson.core:jackson-core:[2.19.2] diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryConfigurationTests.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryConfigurationTests.java index b45ab8ec708a..9b061edcc597 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryConfigurationTests.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryConfigurationTests.java @@ -5,7 +5,6 @@ import com.azure.servicebus.jms.ServiceBusJmsConnectionFactory; import com.azure.spring.cloud.autoconfigure.implementation.context.properties.AzureGlobalProperties; -import jakarta.jms.ConnectionFactory; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.messaginghub.pooled.jms.JmsPoolConnectionFactory; @@ -153,12 +152,7 @@ void cachingConnectionFactoryCachesProducersAndConsumersForSameDestination(Strin ) .run(context -> { assertThat(context).hasSingleBean(CachingConnectionFactory.class); - - ConnectionFactory connectionFactory = context.getBean(ConnectionFactory.class); - assertThat(connectionFactory).isInstanceOf(CachingConnectionFactory.class); - - CachingConnectionFactory cachingFactory = (CachingConnectionFactory) connectionFactory; - + CachingConnectionFactory cachingFactory = context.getBean(CachingConnectionFactory.class); // Verify that producer and consumer caching is enabled // When these properties are true, CachingConnectionFactory will cache and reuse // MessageProducer and MessageConsumer instances for the same destination diff --git a/sdk/spring/spring-cloud-azure-starter-servicebus-jms/pom.xml b/sdk/spring/spring-cloud-azure-starter-servicebus-jms/pom.xml index 26566866ec53..3761f861defd 100644 --- a/sdk/spring/spring-cloud-azure-starter-servicebus-jms/pom.xml +++ b/sdk/spring/spring-cloud-azure-starter-servicebus-jms/pom.xml @@ -106,7 +106,7 @@ com.azure azure-servicebus-jms - 2.0.0 + 2.1.0 com.azure From fab27cef7f77be9bfa4762ced9fc29849dceb0b3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 2 Dec 2025 01:30:02 +0000 Subject: [PATCH 04/11] Add test demonstrating producer reuse for same destination Added new test method cachingConnectionFactoryReusesSameProducerForSameDestination that: - Demonstrates the same producer is returned for the same destination (queue1) - Demonstrates a different producer is returned for different destinations (queue2) - Uses Mockito.verify to prove createProducer is only called once per destination, confirming the caching behavior Co-authored-by: Netyyyy <92105726+Netyyyy@users.noreply.github.com> --- ...msConnectionFactoryConfigurationTests.java | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryConfigurationTests.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryConfigurationTests.java index 9b061edcc597..0512d6576e14 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryConfigurationTests.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryConfigurationTests.java @@ -5,6 +5,13 @@ import com.azure.servicebus.jms.ServiceBusJmsConnectionFactory; import com.azure.spring.cloud.autoconfigure.implementation.context.properties.AzureGlobalProperties; +import jakarta.jms.Connection; +import jakarta.jms.ConnectionFactory; +import jakarta.jms.JMSException; +import jakarta.jms.MessageProducer; +import jakarta.jms.Queue; +import jakarta.jms.Session; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.messaginghub.pooled.jms.JmsPoolConnectionFactory; @@ -18,6 +25,10 @@ import static com.azure.spring.cloud.autoconfigure.implementation.util.TestServiceBusUtils.CONNECTION_STRING_FORMAT; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; class ServiceBusJmsConnectionFactoryConfigurationTests { @@ -165,6 +176,68 @@ void cachingConnectionFactoryCachesProducersAndConsumersForSameDestination(Strin }); } + @Test + void cachingConnectionFactoryReusesSameProducerForSameDestination() throws JMSException { + // Create mock objects for JMS components + ConnectionFactory mockTargetConnectionFactory = mock(ConnectionFactory.class); + Connection mockConnection = mock(Connection.class); + Session mockSession = mock(Session.class); + Queue mockQueue1 = mock(Queue.class); + Queue mockQueue2 = mock(Queue.class); + MessageProducer mockProducer1 = mock(MessageProducer.class); + MessageProducer mockProducer2 = mock(MessageProducer.class); + + // Setup mock behavior + when(mockTargetConnectionFactory.createConnection()).thenReturn(mockConnection); + when(mockConnection.createSession(anyBoolean(), anyInt())).thenReturn(mockSession); + when(mockSession.createQueue("queue1")).thenReturn(mockQueue1); + when(mockSession.createQueue("queue2")).thenReturn(mockQueue2); + when(mockSession.createProducer(mockQueue1)).thenReturn(mockProducer1); + when(mockSession.createProducer(mockQueue2)).thenReturn(mockProducer2); + when(mockQueue1.toString()).thenReturn("queue1"); + when(mockQueue2.toString()).thenReturn("queue2"); + + // Create CachingConnectionFactory with caching enabled + CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory(mockTargetConnectionFactory); + cachingConnectionFactory.setCacheProducers(true); + + // Get connection and session + Connection connection = cachingConnectionFactory.createConnection(); + Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); + + // Create queues + Queue queue1 = session.createQueue("queue1"); + Queue queue2 = session.createQueue("queue2"); + + // First call: create producer for queue1 + MessageProducer producer1ForQueue1 = session.createProducer(queue1); + // Second call: create producer for same queue1 - should return cached producer + MessageProducer producer2ForQueue1 = session.createProducer(queue1); + // Third call: create producer for different queue2 - should return different producer + MessageProducer producerForQueue2 = session.createProducer(queue2); + + // Verify: same producer is returned for the same destination (queue1) + // CachingConnectionFactory wraps the producer, so we compare by string representation + // which includes the underlying producer reference + assertThat(producer1ForQueue1.toString()) + .as("Same producer should be returned for the same destination") + .isEqualTo(producer2ForQueue1.toString()); + + // Verify: different producer is returned for different destination (queue2) + assertThat(producer1ForQueue1.toString()) + .as("Different producer should be returned for different destination") + .isNotEqualTo(producerForQueue2.toString()); + + // Additionally verify the underlying mock was only called once for queue1 + // This proves caching is working - if no caching, mockSession.createProducer(queue1) + // would be called twice + org.mockito.Mockito.verify(mockSession, org.mockito.Mockito.times(1)).createProducer(mockQueue1); + org.mockito.Mockito.verify(mockSession, org.mockito.Mockito.times(1)).createProducer(mockQueue2); + + // Cleanup + connection.close(); + } + @Configuration @PropertySource("classpath:servicebus/additional.properties") static class AdditionalPropertySourceConfiguration { From df5d337ee39f92f863366c11807c5ba6b7abc4b7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 2 Dec 2025 01:32:52 +0000 Subject: [PATCH 05/11] Address code review comments: use static imports for Mockito methods - Use statically imported verify and times instead of fully qualified names - Improve comment formatting for better readability Co-authored-by: Netyyyy <92105726+Netyyyy@users.noreply.github.com> --- ...iceBusJmsConnectionFactoryConfigurationTests.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryConfigurationTests.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryConfigurationTests.java index 0512d6576e14..b5851af2a421 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryConfigurationTests.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryConfigurationTests.java @@ -28,6 +28,8 @@ import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; class ServiceBusJmsConnectionFactoryConfigurationTests { @@ -228,11 +230,11 @@ void cachingConnectionFactoryReusesSameProducerForSameDestination() throws JMSEx .as("Different producer should be returned for different destination") .isNotEqualTo(producerForQueue2.toString()); - // Additionally verify the underlying mock was only called once for queue1 - // This proves caching is working - if no caching, mockSession.createProducer(queue1) - // would be called twice - org.mockito.Mockito.verify(mockSession, org.mockito.Mockito.times(1)).createProducer(mockQueue1); - org.mockito.Mockito.verify(mockSession, org.mockito.Mockito.times(1)).createProducer(mockQueue2); + // Verify the underlying mock was only called once for each queue. + // This proves caching is working - without caching, mockSession.createProducer(queue1) + // would be called twice. + verify(mockSession, times(1)).createProducer(mockQueue1); + verify(mockSession, times(1)).createProducer(mockQueue2); // Cleanup connection.close(); From f966bd20b29e8c20783eee9d4dfaeaa19443988e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 2 Dec 2025 06:03:09 +0000 Subject: [PATCH 06/11] Update test to demonstrate azure-servicebus-jms 2.1.0 fix for destination caching The test now demonstrates the specific fix in azure-servicebus-jms 2.1.0: - In 2.0.0, each Queue instance had unique toString() like "ServiceBusJmsQueue@11655" which prevented CachingConnectionFactory from caching producers for the same queue - In 2.1.0, toString() returns the queue name, enabling proper caching Test verifies that: - Multiple Queue instances with same toString() value use cached producer - Different Queue instances with different toString() values use different producers - Mockito.verify confirms createProducer is only called once for same-named queues Co-authored-by: Netyyyy <92105726+Netyyyy@users.noreply.github.com> --- ...msConnectionFactoryConfigurationTests.java | 55 ++++++++++++------- 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryConfigurationTests.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryConfigurationTests.java index b5851af2a421..577e632ea200 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryConfigurationTests.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryConfigurationTests.java @@ -184,7 +184,11 @@ void cachingConnectionFactoryReusesSameProducerForSameDestination() throws JMSEx ConnectionFactory mockTargetConnectionFactory = mock(ConnectionFactory.class); Connection mockConnection = mock(Connection.class); Session mockSession = mock(Session.class); - Queue mockQueue1 = mock(Queue.class); + // Create two separate Queue instances for the same queue name "queue1" + // In azure-servicebus-jms 2.0.0, each Queue instance had a unique toString() like "ServiceBusJmsQueue@11655" + // In azure-servicebus-jms 2.1.0, toString() returns the queue name, enabling proper caching + Queue mockQueue1FirstCall = mock(Queue.class); + Queue mockQueue1SecondCall = mock(Queue.class); Queue mockQueue2 = mock(Queue.class); MessageProducer mockProducer1 = mock(MessageProducer.class); MessageProducer mockProducer2 = mock(MessageProducer.class); @@ -192,11 +196,19 @@ void cachingConnectionFactoryReusesSameProducerForSameDestination() throws JMSEx // Setup mock behavior when(mockTargetConnectionFactory.createConnection()).thenReturn(mockConnection); when(mockConnection.createSession(anyBoolean(), anyInt())).thenReturn(mockSession); - when(mockSession.createQueue("queue1")).thenReturn(mockQueue1); + // Return different Queue instances for the same queue name (simulating real JMS behavior) + when(mockSession.createQueue("queue1")) + .thenReturn(mockQueue1FirstCall) + .thenReturn(mockQueue1SecondCall); when(mockSession.createQueue("queue2")).thenReturn(mockQueue2); - when(mockSession.createProducer(mockQueue1)).thenReturn(mockProducer1); + when(mockSession.createProducer(mockQueue1FirstCall)).thenReturn(mockProducer1); + when(mockSession.createProducer(mockQueue1SecondCall)).thenReturn(mockProducer1); when(mockSession.createProducer(mockQueue2)).thenReturn(mockProducer2); - when(mockQueue1.toString()).thenReturn("queue1"); + // Key fix in azure-servicebus-jms 2.1.0: toString() returns the queue name + // CachingConnectionFactory uses destination.toString() as cache key + // See: https://github.com/spring-projects/spring-framework/blob/main/spring-jms/src/main/java/org/springframework/jms/connection/CachingConnectionFactory.java#L360 + when(mockQueue1FirstCall.toString()).thenReturn("queue1"); + when(mockQueue1SecondCall.toString()).thenReturn("queue1"); when(mockQueue2.toString()).thenReturn("queue2"); // Create CachingConnectionFactory with caching enabled @@ -207,33 +219,38 @@ void cachingConnectionFactoryReusesSameProducerForSameDestination() throws JMSEx Connection connection = cachingConnectionFactory.createConnection(); Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); - // Create queues - Queue queue1 = session.createQueue("queue1"); + // Create queues - these are separate Queue instances but with same toString() value + Queue queue1FirstCall = session.createQueue("queue1"); + Queue queue1SecondCall = session.createQueue("queue1"); Queue queue2 = session.createQueue("queue2"); - // First call: create producer for queue1 - MessageProducer producer1ForQueue1 = session.createProducer(queue1); - // Second call: create producer for same queue1 - should return cached producer - MessageProducer producer2ForQueue1 = session.createProducer(queue1); + // First call: create producer for queue1 (first Queue instance) + MessageProducer producer1ForQueue1 = session.createProducer(queue1FirstCall); + // Second call: create producer for queue1 (second Queue instance, but same toString()) + // With azure-servicebus-jms 2.1.0 fix, this should return cached producer + MessageProducer producer2ForQueue1 = session.createProducer(queue1SecondCall); // Third call: create producer for different queue2 - should return different producer MessageProducer producerForQueue2 = session.createProducer(queue2); - // Verify: same producer is returned for the same destination (queue1) - // CachingConnectionFactory wraps the producer, so we compare by string representation - // which includes the underlying producer reference + // Verify: same producer is returned for different Queue instances with same toString() value + // This demonstrates the azure-servicebus-jms 2.1.0 fix where toString() returns queue name assertThat(producer1ForQueue1.toString()) - .as("Same producer should be returned for the same destination") + .as("Same producer should be returned when destination.toString() returns the same value") .isEqualTo(producer2ForQueue1.toString()); - // Verify: different producer is returned for different destination (queue2) + // Verify: different producer is returned for different destination assertThat(producer1ForQueue1.toString()) .as("Different producer should be returned for different destination") .isNotEqualTo(producerForQueue2.toString()); - // Verify the underlying mock was only called once for each queue. - // This proves caching is working - without caching, mockSession.createProducer(queue1) - // would be called twice. - verify(mockSession, times(1)).createProducer(mockQueue1); + // Verify the underlying mock was only called once for queue1 Queue instances. + // This proves caching is working based on destination.toString() value. + // In azure-servicebus-jms 2.0.0, createProducer would be called twice because + // each Queue had unique toString() like "ServiceBusJmsQueue@11655". + // In azure-servicebus-jms 2.1.0, createProducer is called once because + // toString() returns "queue1" for both Queue instances. + verify(mockSession, times(1)).createProducer(mockQueue1FirstCall); + verify(mockSession, times(0)).createProducer(mockQueue1SecondCall); verify(mockSession, times(1)).createProducer(mockQueue2); // Cleanup From d06054b88e07800b4c0cd0e7a0abb3e549e409c5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 2 Dec 2025 06:24:28 +0000 Subject: [PATCH 07/11] Add test for 2.0.0 bug where unique toString() prevents caching Added new test cachingConnectionFactoryFailsToReuseProducerWithUniqueToString that demonstrates the bug in azure-servicebus-jms 2.0.0 where each Queue instance had a unique toString() like "ServiceBusJmsQueue@11655", which prevented CachingConnectionFactory from caching producers for the same queue name. The test verifies that: - When toString() returns unique values, createProducer is called twice - Different producers are returned even for the same queue name - This contrasts with the fixed 2.1.0 behavior where toString() returns the queue name, enabling proper caching Co-authored-by: Netyyyy <92105726+Netyyyy@users.noreply.github.com> --- ...msConnectionFactoryConfigurationTests.java | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryConfigurationTests.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryConfigurationTests.java index 577e632ea200..44ce2c5118a8 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryConfigurationTests.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryConfigurationTests.java @@ -257,6 +257,67 @@ void cachingConnectionFactoryReusesSameProducerForSameDestination() throws JMSEx connection.close(); } + @Test + void cachingConnectionFactoryFailsToReuseProducerWithUniqueToString() throws JMSException { + // This test demonstrates the bug in azure-servicebus-jms 2.0.0 where each Queue instance + // had a unique toString() like "ServiceBusJmsQueue@11655", preventing CachingConnectionFactory + // from reusing producers for the same queue name. + + // Create mock objects for JMS components + ConnectionFactory mockTargetConnectionFactory = mock(ConnectionFactory.class); + Connection mockConnection = mock(Connection.class); + Session mockSession = mock(Session.class); + Queue mockQueue1FirstCall = mock(Queue.class); + Queue mockQueue1SecondCall = mock(Queue.class); + MessageProducer mockProducer1 = mock(MessageProducer.class); + MessageProducer mockProducer2 = mock(MessageProducer.class); + + // Setup mock behavior + when(mockTargetConnectionFactory.createConnection()).thenReturn(mockConnection); + when(mockConnection.createSession(anyBoolean(), anyInt())).thenReturn(mockSession); + when(mockSession.createQueue("queue1")) + .thenReturn(mockQueue1FirstCall) + .thenReturn(mockQueue1SecondCall); + when(mockSession.createProducer(mockQueue1FirstCall)).thenReturn(mockProducer1); + when(mockSession.createProducer(mockQueue1SecondCall)).thenReturn(mockProducer2); + // Simulate azure-servicebus-jms 2.0.0 behavior: each Queue has unique toString() + // This prevents CachingConnectionFactory from caching because it uses toString() as key + when(mockQueue1FirstCall.toString()).thenReturn("ServiceBusJmsQueue@11655"); + when(mockQueue1SecondCall.toString()).thenReturn("ServiceBusJmsQueue@22766"); + + // Create CachingConnectionFactory with caching enabled + CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory(mockTargetConnectionFactory); + cachingConnectionFactory.setCacheProducers(true); + + // Get connection and session + Connection connection = cachingConnectionFactory.createConnection(); + Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); + + // Create queues - these are separate Queue instances with unique toString() values + Queue queue1FirstCall = session.createQueue("queue1"); + Queue queue1SecondCall = session.createQueue("queue1"); + + // First call: create producer for queue1 (first Queue instance) + MessageProducer producer1ForQueue1 = session.createProducer(queue1FirstCall); + // Second call: create producer for queue1 (second Queue instance with different toString()) + // With azure-servicebus-jms 2.0.0, this creates a NEW producer because toString() differs + MessageProducer producer2ForQueue1 = session.createProducer(queue1SecondCall); + + // Verify: different producers are returned because toString() values differ + // This demonstrates the bug in azure-servicebus-jms 2.0.0 + assertThat(producer1ForQueue1.toString()) + .as("Different producers returned when destination.toString() differs (2.0.0 bug)") + .isNotEqualTo(producer2ForQueue1.toString()); + + // Verify createProducer was called TWICE - once for each unique toString() value + // This proves caching is NOT working due to unique toString() values in 2.0.0 + verify(mockSession, times(1)).createProducer(mockQueue1FirstCall); + verify(mockSession, times(1)).createProducer(mockQueue1SecondCall); + + // Cleanup + connection.close(); + } + @Configuration @PropertySource("classpath:servicebus/additional.properties") static class AdditionalPropertySourceConfiguration { From 13994fbf37f35504f7866a8d599572cd07968ab3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 2 Dec 2025 06:58:45 +0000 Subject: [PATCH 08/11] Use ServiceBusJmsQueue to test toString() consistency in 2.1.0 Updated tests to use actual ServiceBusJmsQueue instances instead of mocked Queue: - Added test serviceBusJmsQueueToStringReturnsConsistentValueForSameQueueName that verifies ServiceBusJmsQueue.toString() returns consistent values for the same queue name (the key fix in azure-servicebus-jms 2.1.0) - Updated cachingConnectionFactoryReusesSameProducerForSameDestination to use ServiceBusJmsQueue instances via reflection - This test would fail with azure-servicebus-jms 2.0.0 because toString() returned unique values like "ServiceBusJmsQueue@hashcode" Co-authored-by: Netyyyy <92105726+Netyyyy@users.noreply.github.com> --- ...msConnectionFactoryConfigurationTests.java | 181 ++++++++---------- 1 file changed, 78 insertions(+), 103 deletions(-) diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryConfigurationTests.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryConfigurationTests.java index 44ce2c5118a8..400f19f1b6d8 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryConfigurationTests.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryConfigurationTests.java @@ -4,10 +4,10 @@ package com.azure.spring.cloud.autoconfigure.implementation.jms; import com.azure.servicebus.jms.ServiceBusJmsConnectionFactory; +import com.azure.servicebus.jms.ServiceBusJmsQueue; import com.azure.spring.cloud.autoconfigure.implementation.context.properties.AzureGlobalProperties; import jakarta.jms.Connection; import jakarta.jms.ConnectionFactory; -import jakarta.jms.JMSException; import jakarta.jms.MessageProducer; import jakarta.jms.Queue; import jakarta.jms.Session; @@ -28,8 +28,6 @@ import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; class ServiceBusJmsConnectionFactoryConfigurationTests { @@ -179,37 +177,76 @@ void cachingConnectionFactoryCachesProducersAndConsumersForSameDestination(Strin } @Test - void cachingConnectionFactoryReusesSameProducerForSameDestination() throws JMSException { + void serviceBusJmsQueueToStringReturnsConsistentValueForSameQueueName() throws Exception { + // Create mock inner queues for ServiceBusJmsQueue instances + Queue mockInnerQueue1 = mock(Queue.class); + Queue mockInnerQueue2 = mock(Queue.class); + Queue mockInnerQueue3 = mock(Queue.class); + when(mockInnerQueue1.getQueueName()).thenReturn("queue1"); + when(mockInnerQueue2.getQueueName()).thenReturn("queue1"); + when(mockInnerQueue3.getQueueName()).thenReturn("queue2"); + + // Create ServiceBusJmsQueue instances using reflection (constructor is package-private) + // In azure-servicebus-jms 2.1.0, toString() returns "ServiceBusJmsQueue{queueName='...'}" + // In azure-servicebus-jms 2.0.0, toString() returned "ServiceBusJmsQueue@hashcode" (default Object.toString()) + ServiceBusJmsQueue serviceBusQueue1FirstCall = createServiceBusJmsQueue(mockInnerQueue1); + ServiceBusJmsQueue serviceBusQueue1SecondCall = createServiceBusJmsQueue(mockInnerQueue2); + ServiceBusJmsQueue serviceBusQueue2 = createServiceBusJmsQueue(mockInnerQueue3); + + // Verify ServiceBusJmsQueue toString() returns consistent value for same queue name + // This is the key fix in azure-servicebus-jms 2.1.0 + // Without this fix, CachingConnectionFactory cannot cache producers because it uses + // destination.toString() as cache key (see CachingConnectionFactory.java#L360) + assertThat(serviceBusQueue1FirstCall.toString()) + .as("ServiceBusJmsQueue 2.1.0 toString() should return consistent value for same queue name") + .isEqualTo(serviceBusQueue1SecondCall.toString()); + + // Verify toString() includes the queue name + assertThat(serviceBusQueue1FirstCall.toString()) + .as("ServiceBusJmsQueue toString() should include queue name") + .contains("queue1"); + + // Verify toString() returns different value for different queue name + assertThat(serviceBusQueue1FirstCall.toString()) + .as("ServiceBusJmsQueue 2.1.0 toString() should return different value for different queue name") + .isNotEqualTo(serviceBusQueue2.toString()); + + assertThat(serviceBusQueue2.toString()) + .as("ServiceBusJmsQueue toString() should include queue name") + .contains("queue2"); + } + + @Test + void cachingConnectionFactoryReusesSameProducerForSameDestination() throws Exception { // Create mock objects for JMS components ConnectionFactory mockTargetConnectionFactory = mock(ConnectionFactory.class); Connection mockConnection = mock(Connection.class); Session mockSession = mock(Session.class); - // Create two separate Queue instances for the same queue name "queue1" - // In azure-servicebus-jms 2.0.0, each Queue instance had a unique toString() like "ServiceBusJmsQueue@11655" - // In azure-servicebus-jms 2.1.0, toString() returns the queue name, enabling proper caching - Queue mockQueue1FirstCall = mock(Queue.class); - Queue mockQueue1SecondCall = mock(Queue.class); - Queue mockQueue2 = mock(Queue.class); + // Create mock inner queues for ServiceBusJmsQueue instances + Queue mockInnerQueue1 = mock(Queue.class); + Queue mockInnerQueue2 = mock(Queue.class); + Queue mockInnerQueue3 = mock(Queue.class); + when(mockInnerQueue1.getQueueName()).thenReturn("queue1"); + when(mockInnerQueue2.getQueueName()).thenReturn("queue1"); + when(mockInnerQueue3.getQueueName()).thenReturn("queue2"); + + // Create ServiceBusJmsQueue instances using reflection (constructor is package-private) + ServiceBusJmsQueue serviceBusQueue1FirstCall = createServiceBusJmsQueue(mockInnerQueue1); + ServiceBusJmsQueue serviceBusQueue1SecondCall = createServiceBusJmsQueue(mockInnerQueue2); + ServiceBusJmsQueue serviceBusQueue2 = createServiceBusJmsQueue(mockInnerQueue3); MessageProducer mockProducer1 = mock(MessageProducer.class); MessageProducer mockProducer2 = mock(MessageProducer.class); - // Setup mock behavior + // Setup mock behavior for connection and session when(mockTargetConnectionFactory.createConnection()).thenReturn(mockConnection); when(mockConnection.createSession(anyBoolean(), anyInt())).thenReturn(mockSession); - // Return different Queue instances for the same queue name (simulating real JMS behavior) when(mockSession.createQueue("queue1")) - .thenReturn(mockQueue1FirstCall) - .thenReturn(mockQueue1SecondCall); - when(mockSession.createQueue("queue2")).thenReturn(mockQueue2); - when(mockSession.createProducer(mockQueue1FirstCall)).thenReturn(mockProducer1); - when(mockSession.createProducer(mockQueue1SecondCall)).thenReturn(mockProducer1); - when(mockSession.createProducer(mockQueue2)).thenReturn(mockProducer2); - // Key fix in azure-servicebus-jms 2.1.0: toString() returns the queue name - // CachingConnectionFactory uses destination.toString() as cache key - // See: https://github.com/spring-projects/spring-framework/blob/main/spring-jms/src/main/java/org/springframework/jms/connection/CachingConnectionFactory.java#L360 - when(mockQueue1FirstCall.toString()).thenReturn("queue1"); - when(mockQueue1SecondCall.toString()).thenReturn("queue1"); - when(mockQueue2.toString()).thenReturn("queue2"); + .thenReturn(serviceBusQueue1FirstCall) + .thenReturn(serviceBusQueue1SecondCall); + when(mockSession.createQueue("queue2")).thenReturn(serviceBusQueue2); + when(mockSession.createProducer(serviceBusQueue1FirstCall)).thenReturn(mockProducer1); + when(mockSession.createProducer(serviceBusQueue1SecondCall)).thenReturn(mockProducer1); + when(mockSession.createProducer(serviceBusQueue2)).thenReturn(mockProducer2); // Create CachingConnectionFactory with caching enabled CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory(mockTargetConnectionFactory); @@ -219,103 +256,41 @@ void cachingConnectionFactoryReusesSameProducerForSameDestination() throws JMSEx Connection connection = cachingConnectionFactory.createConnection(); Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); - // Create queues - these are separate Queue instances but with same toString() value + // Create queues - these are separate ServiceBusJmsQueue instances with same toString() value Queue queue1FirstCall = session.createQueue("queue1"); Queue queue1SecondCall = session.createQueue("queue1"); Queue queue2 = session.createQueue("queue2"); - // First call: create producer for queue1 (first Queue instance) + // First call: create producer for queue1 (first ServiceBusJmsQueue instance) MessageProducer producer1ForQueue1 = session.createProducer(queue1FirstCall); - // Second call: create producer for queue1 (second Queue instance, but same toString()) - // With azure-servicebus-jms 2.1.0 fix, this should return cached producer + // Second call: create producer for queue1 (second ServiceBusJmsQueue instance, same toString()) + // With azure-servicebus-jms 2.1.0, this returns cached producer because toString() is consistent MessageProducer producer2ForQueue1 = session.createProducer(queue1SecondCall); // Third call: create producer for different queue2 - should return different producer MessageProducer producerForQueue2 = session.createProducer(queue2); - // Verify: same producer is returned for different Queue instances with same toString() value - // This demonstrates the azure-servicebus-jms 2.1.0 fix where toString() returns queue name + // Verify: same producer is returned for same queue name due to consistent toString() assertThat(producer1ForQueue1.toString()) - .as("Same producer should be returned when destination.toString() returns the same value") + .as("Same producer should be returned for ServiceBusJmsQueue instances with same queue name") .isEqualTo(producer2ForQueue1.toString()); - // Verify: different producer is returned for different destination + // Verify: different producer is returned for different queue name assertThat(producer1ForQueue1.toString()) - .as("Different producer should be returned for different destination") + .as("Different producer should be returned for different queue name") .isNotEqualTo(producerForQueue2.toString()); - // Verify the underlying mock was only called once for queue1 Queue instances. - // This proves caching is working based on destination.toString() value. - // In azure-servicebus-jms 2.0.0, createProducer would be called twice because - // each Queue had unique toString() like "ServiceBusJmsQueue@11655". - // In azure-servicebus-jms 2.1.0, createProducer is called once because - // toString() returns "queue1" for both Queue instances. - verify(mockSession, times(1)).createProducer(mockQueue1FirstCall); - verify(mockSession, times(0)).createProducer(mockQueue1SecondCall); - verify(mockSession, times(1)).createProducer(mockQueue2); - // Cleanup connection.close(); } - - @Test - void cachingConnectionFactoryFailsToReuseProducerWithUniqueToString() throws JMSException { - // This test demonstrates the bug in azure-servicebus-jms 2.0.0 where each Queue instance - // had a unique toString() like "ServiceBusJmsQueue@11655", preventing CachingConnectionFactory - // from reusing producers for the same queue name. - - // Create mock objects for JMS components - ConnectionFactory mockTargetConnectionFactory = mock(ConnectionFactory.class); - Connection mockConnection = mock(Connection.class); - Session mockSession = mock(Session.class); - Queue mockQueue1FirstCall = mock(Queue.class); - Queue mockQueue1SecondCall = mock(Queue.class); - MessageProducer mockProducer1 = mock(MessageProducer.class); - MessageProducer mockProducer2 = mock(MessageProducer.class); - - // Setup mock behavior - when(mockTargetConnectionFactory.createConnection()).thenReturn(mockConnection); - when(mockConnection.createSession(anyBoolean(), anyInt())).thenReturn(mockSession); - when(mockSession.createQueue("queue1")) - .thenReturn(mockQueue1FirstCall) - .thenReturn(mockQueue1SecondCall); - when(mockSession.createProducer(mockQueue1FirstCall)).thenReturn(mockProducer1); - when(mockSession.createProducer(mockQueue1SecondCall)).thenReturn(mockProducer2); - // Simulate azure-servicebus-jms 2.0.0 behavior: each Queue has unique toString() - // This prevents CachingConnectionFactory from caching because it uses toString() as key - when(mockQueue1FirstCall.toString()).thenReturn("ServiceBusJmsQueue@11655"); - when(mockQueue1SecondCall.toString()).thenReturn("ServiceBusJmsQueue@22766"); - - // Create CachingConnectionFactory with caching enabled - CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory(mockTargetConnectionFactory); - cachingConnectionFactory.setCacheProducers(true); - - // Get connection and session - Connection connection = cachingConnectionFactory.createConnection(); - Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); - - // Create queues - these are separate Queue instances with unique toString() values - Queue queue1FirstCall = session.createQueue("queue1"); - Queue queue1SecondCall = session.createQueue("queue1"); - - // First call: create producer for queue1 (first Queue instance) - MessageProducer producer1ForQueue1 = session.createProducer(queue1FirstCall); - // Second call: create producer for queue1 (second Queue instance with different toString()) - // With azure-servicebus-jms 2.0.0, this creates a NEW producer because toString() differs - MessageProducer producer2ForQueue1 = session.createProducer(queue1SecondCall); - - // Verify: different producers are returned because toString() values differ - // This demonstrates the bug in azure-servicebus-jms 2.0.0 - assertThat(producer1ForQueue1.toString()) - .as("Different producers returned when destination.toString() differs (2.0.0 bug)") - .isNotEqualTo(producer2ForQueue1.toString()); - - // Verify createProducer was called TWICE - once for each unique toString() value - // This proves caching is NOT working due to unique toString() values in 2.0.0 - verify(mockSession, times(1)).createProducer(mockQueue1FirstCall); - verify(mockSession, times(1)).createProducer(mockQueue1SecondCall); - - // Cleanup - connection.close(); + + /** + * Creates a ServiceBusJmsQueue instance using reflection since the constructor is package-private. + */ + private ServiceBusJmsQueue createServiceBusJmsQueue(Queue innerQueue) throws Exception { + java.lang.reflect.Constructor constructor = + ServiceBusJmsQueue.class.getDeclaredConstructor(Queue.class); + constructor.setAccessible(true); + return constructor.newInstance(innerQueue); } @Configuration From bd12a012c6253fbb0e4fcc0e23527ab2fa27470d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 2 Dec 2025 08:04:29 +0000 Subject: [PATCH 09/11] Use ServiceBusJmsSession to create ServiceBusJmsQueue instances Per feedback: 1. Updated test to use ServiceBusJmsSession instead of creating ServiceBusJmsQueue directly via reflection 2. Removed cachingConnectionFactoryCachesProducersAndConsumersForSameDestination test as it's no longer needed 3. Removed serviceBusJmsQueueToStringReturnsConsistentValueForSameQueueName test as it's covered by the main test The test now creates ServiceBusJmsQueue instances through ServiceBusJmsSession.createQueue() which is the proper API path. This verifies that in azure-servicebus-jms 2.1.0, the toString() method returns consistent values for the same queue name. Co-authored-by: Netyyyy <92105726+Netyyyy@users.noreply.github.com> --- ...msConnectionFactoryConfigurationTests.java | 123 ++++++------------ 1 file changed, 37 insertions(+), 86 deletions(-) diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryConfigurationTests.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryConfigurationTests.java index 400f19f1b6d8..3952184a2155 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryConfigurationTests.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryConfigurationTests.java @@ -4,7 +4,6 @@ package com.azure.spring.cloud.autoconfigure.implementation.jms; import com.azure.servicebus.jms.ServiceBusJmsConnectionFactory; -import com.azure.servicebus.jms.ServiceBusJmsQueue; import com.azure.spring.cloud.autoconfigure.implementation.context.properties.AzureGlobalProperties; import jakarta.jms.Connection; import jakarta.jms.ConnectionFactory; @@ -151,102 +150,54 @@ void useCacheConnectionViaAdditionConfigurationFile(String pricingTier) { }); } - @ParameterizedTest - @ValueSource(strings = { "standard", "premium" }) - void cachingConnectionFactoryCachesProducersAndConsumersForSameDestination(String pricingTier) { - this.contextRunner - .withPropertyValues( - "spring.jms.servicebus.pricing-tier=" + pricingTier, - "spring.jms.servicebus.pool.enabled=false", - "spring.jms.cache.producers=true", - "spring.jms.cache.consumers=true" - ) - .run(context -> { - assertThat(context).hasSingleBean(CachingConnectionFactory.class); - CachingConnectionFactory cachingFactory = context.getBean(CachingConnectionFactory.class); - // Verify that producer and consumer caching is enabled - // When these properties are true, CachingConnectionFactory will cache and reuse - // MessageProducer and MessageConsumer instances for the same destination - assertThat(cachingFactory.isCacheProducers()) - .as("CachingConnectionFactory should cache MessageProducers for the same destination") - .isTrue(); - assertThat(cachingFactory.isCacheConsumers()) - .as("CachingConnectionFactory should cache MessageConsumers for the same destination") - .isTrue(); - }); - } - - @Test - void serviceBusJmsQueueToStringReturnsConsistentValueForSameQueueName() throws Exception { - // Create mock inner queues for ServiceBusJmsQueue instances - Queue mockInnerQueue1 = mock(Queue.class); - Queue mockInnerQueue2 = mock(Queue.class); - Queue mockInnerQueue3 = mock(Queue.class); - when(mockInnerQueue1.getQueueName()).thenReturn("queue1"); - when(mockInnerQueue2.getQueueName()).thenReturn("queue1"); - when(mockInnerQueue3.getQueueName()).thenReturn("queue2"); - - // Create ServiceBusJmsQueue instances using reflection (constructor is package-private) - // In azure-servicebus-jms 2.1.0, toString() returns "ServiceBusJmsQueue{queueName='...'}" - // In azure-servicebus-jms 2.0.0, toString() returned "ServiceBusJmsQueue@hashcode" (default Object.toString()) - ServiceBusJmsQueue serviceBusQueue1FirstCall = createServiceBusJmsQueue(mockInnerQueue1); - ServiceBusJmsQueue serviceBusQueue1SecondCall = createServiceBusJmsQueue(mockInnerQueue2); - ServiceBusJmsQueue serviceBusQueue2 = createServiceBusJmsQueue(mockInnerQueue3); - - // Verify ServiceBusJmsQueue toString() returns consistent value for same queue name - // This is the key fix in azure-servicebus-jms 2.1.0 - // Without this fix, CachingConnectionFactory cannot cache producers because it uses - // destination.toString() as cache key (see CachingConnectionFactory.java#L360) - assertThat(serviceBusQueue1FirstCall.toString()) - .as("ServiceBusJmsQueue 2.1.0 toString() should return consistent value for same queue name") - .isEqualTo(serviceBusQueue1SecondCall.toString()); - - // Verify toString() includes the queue name - assertThat(serviceBusQueue1FirstCall.toString()) - .as("ServiceBusJmsQueue toString() should include queue name") - .contains("queue1"); - - // Verify toString() returns different value for different queue name - assertThat(serviceBusQueue1FirstCall.toString()) - .as("ServiceBusJmsQueue 2.1.0 toString() should return different value for different queue name") - .isNotEqualTo(serviceBusQueue2.toString()); - - assertThat(serviceBusQueue2.toString()) - .as("ServiceBusJmsQueue toString() should include queue name") - .contains("queue2"); - } - @Test void cachingConnectionFactoryReusesSameProducerForSameDestination() throws Exception { // Create mock objects for JMS components ConnectionFactory mockTargetConnectionFactory = mock(ConnectionFactory.class); Connection mockConnection = mock(Connection.class); - Session mockSession = mock(Session.class); - // Create mock inner queues for ServiceBusJmsQueue instances + // Create a mock inner session that ServiceBusJmsSession will wrap + Session mockInnerSession = mock(Session.class); + // Create mock inner queues that the inner session returns Queue mockInnerQueue1 = mock(Queue.class); Queue mockInnerQueue2 = mock(Queue.class); Queue mockInnerQueue3 = mock(Queue.class); when(mockInnerQueue1.getQueueName()).thenReturn("queue1"); when(mockInnerQueue2.getQueueName()).thenReturn("queue1"); when(mockInnerQueue3.getQueueName()).thenReturn("queue2"); + when(mockInnerSession.createQueue("queue1")) + .thenReturn(mockInnerQueue1) + .thenReturn(mockInnerQueue2); + when(mockInnerSession.createQueue("queue2")).thenReturn(mockInnerQueue3); + + // Create ServiceBusJmsSession using reflection (constructor is package-private) + // ServiceBusJmsSession.createQueue() wraps the inner Queue in ServiceBusJmsQueue + // which has the proper toString() implementation in azure-servicebus-jms 2.1.0 + Session serviceBusJmsSession = createServiceBusJmsSession(mockInnerSession); - // Create ServiceBusJmsQueue instances using reflection (constructor is package-private) - ServiceBusJmsQueue serviceBusQueue1FirstCall = createServiceBusJmsQueue(mockInnerQueue1); - ServiceBusJmsQueue serviceBusQueue1SecondCall = createServiceBusJmsQueue(mockInnerQueue2); - ServiceBusJmsQueue serviceBusQueue2 = createServiceBusJmsQueue(mockInnerQueue3); + // Create ServiceBusJmsQueue instances through ServiceBusJmsSession + // In azure-servicebus-jms 2.1.0, ServiceBusJmsQueue.toString() returns consistent value based on queue name + // In azure-servicebus-jms 2.0.0, toString() returned unique values like "ServiceBusJmsQueue@hashcode" + Queue serviceBusQueue1FirstCall = serviceBusJmsSession.createQueue("queue1"); + Queue serviceBusQueue1SecondCall = serviceBusJmsSession.createQueue("queue1"); + Queue serviceBusQueue2 = serviceBusJmsSession.createQueue("queue2"); + + // Verify toString() returns consistent value for same queue name (key fix in 2.1.0) + assertThat(serviceBusQueue1FirstCall.toString()) + .as("ServiceBusJmsQueue toString() should return consistent value for same queue name") + .isEqualTo(serviceBusQueue1SecondCall.toString()); + assertThat(serviceBusQueue1FirstCall.toString()) + .as("ServiceBusJmsQueue toString() should return different value for different queue name") + .isNotEqualTo(serviceBusQueue2.toString()); + MessageProducer mockProducer1 = mock(MessageProducer.class); MessageProducer mockProducer2 = mock(MessageProducer.class); // Setup mock behavior for connection and session when(mockTargetConnectionFactory.createConnection()).thenReturn(mockConnection); - when(mockConnection.createSession(anyBoolean(), anyInt())).thenReturn(mockSession); - when(mockSession.createQueue("queue1")) - .thenReturn(serviceBusQueue1FirstCall) - .thenReturn(serviceBusQueue1SecondCall); - when(mockSession.createQueue("queue2")).thenReturn(serviceBusQueue2); - when(mockSession.createProducer(serviceBusQueue1FirstCall)).thenReturn(mockProducer1); - when(mockSession.createProducer(serviceBusQueue1SecondCall)).thenReturn(mockProducer1); - when(mockSession.createProducer(serviceBusQueue2)).thenReturn(mockProducer2); + when(mockConnection.createSession(anyBoolean(), anyInt())).thenReturn(serviceBusJmsSession); + when(mockInnerSession.createProducer(serviceBusQueue1FirstCall)).thenReturn(mockProducer1); + when(mockInnerSession.createProducer(serviceBusQueue1SecondCall)).thenReturn(mockProducer1); + when(mockInnerSession.createProducer(serviceBusQueue2)).thenReturn(mockProducer2); // Create CachingConnectionFactory with caching enabled CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory(mockTargetConnectionFactory); @@ -256,7 +207,7 @@ void cachingConnectionFactoryReusesSameProducerForSameDestination() throws Excep Connection connection = cachingConnectionFactory.createConnection(); Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); - // Create queues - these are separate ServiceBusJmsQueue instances with same toString() value + // Create queues - these are ServiceBusJmsQueue instances with consistent toString() values Queue queue1FirstCall = session.createQueue("queue1"); Queue queue1SecondCall = session.createQueue("queue1"); Queue queue2 = session.createQueue("queue2"); @@ -284,13 +235,13 @@ void cachingConnectionFactoryReusesSameProducerForSameDestination() throws Excep } /** - * Creates a ServiceBusJmsQueue instance using reflection since the constructor is package-private. + * Creates a ServiceBusJmsSession instance using reflection since the constructor is package-private. */ - private ServiceBusJmsQueue createServiceBusJmsQueue(Queue innerQueue) throws Exception { - java.lang.reflect.Constructor constructor = - ServiceBusJmsQueue.class.getDeclaredConstructor(Queue.class); + private Session createServiceBusJmsSession(Session innerSession) throws Exception { + Class sessionClass = Class.forName("com.azure.servicebus.jms.ServiceBusJmsSession"); + java.lang.reflect.Constructor constructor = sessionClass.getDeclaredConstructor(Session.class); constructor.setAccessible(true); - return constructor.newInstance(innerQueue); + return (Session) constructor.newInstance(innerSession); } @Configuration From d67765c9db7a6c1b2269b1473e3ca379f8bdc285 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 2 Dec 2025 08:57:51 +0000 Subject: [PATCH 10/11] Simplify test to focus on producer caching assertion Per feedback: 1. Removed toString() consistency assertions (implementation details) 2. Each ServiceBusJmsQueue instance now gets a unique mock producer so that in azure-servicebus-jms 2.0.0 the test would fail at the producer equality assertion, not at producer creation The test now fails at the right place for 2.0.0: - With 2.0.0, toString() returns unique values so no cache hit, leading to different producers being returned - With 2.1.0, toString() returns consistent values based on queue name so cache hits occur, leading to same producer being returned Co-authored-by: Netyyyy <92105726+Netyyyy@users.noreply.github.com> --- ...msConnectionFactoryConfigurationTests.java | 27 ++++++++----------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryConfigurationTests.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryConfigurationTests.java index 3952184a2155..31ceafbe55ca 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryConfigurationTests.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryConfigurationTests.java @@ -175,29 +175,22 @@ void cachingConnectionFactoryReusesSameProducerForSameDestination() throws Excep Session serviceBusJmsSession = createServiceBusJmsSession(mockInnerSession); // Create ServiceBusJmsQueue instances through ServiceBusJmsSession - // In azure-servicebus-jms 2.1.0, ServiceBusJmsQueue.toString() returns consistent value based on queue name - // In azure-servicebus-jms 2.0.0, toString() returned unique values like "ServiceBusJmsQueue@hashcode" Queue serviceBusQueue1FirstCall = serviceBusJmsSession.createQueue("queue1"); Queue serviceBusQueue1SecondCall = serviceBusJmsSession.createQueue("queue1"); Queue serviceBusQueue2 = serviceBusJmsSession.createQueue("queue2"); - - // Verify toString() returns consistent value for same queue name (key fix in 2.1.0) - assertThat(serviceBusQueue1FirstCall.toString()) - .as("ServiceBusJmsQueue toString() should return consistent value for same queue name") - .isEqualTo(serviceBusQueue1SecondCall.toString()); - assertThat(serviceBusQueue1FirstCall.toString()) - .as("ServiceBusJmsQueue toString() should return different value for different queue name") - .isNotEqualTo(serviceBusQueue2.toString()); MessageProducer mockProducer1 = mock(MessageProducer.class); MessageProducer mockProducer2 = mock(MessageProducer.class); + MessageProducer mockProducer3 = mock(MessageProducer.class); // Setup mock behavior for connection and session when(mockTargetConnectionFactory.createConnection()).thenReturn(mockConnection); when(mockConnection.createSession(anyBoolean(), anyInt())).thenReturn(serviceBusJmsSession); + // Each ServiceBusJmsQueue instance gets a different producer from the underlying session + // CachingConnectionFactory should cache based on destination.toString() when(mockInnerSession.createProducer(serviceBusQueue1FirstCall)).thenReturn(mockProducer1); - when(mockInnerSession.createProducer(serviceBusQueue1SecondCall)).thenReturn(mockProducer1); - when(mockInnerSession.createProducer(serviceBusQueue2)).thenReturn(mockProducer2); + when(mockInnerSession.createProducer(serviceBusQueue1SecondCall)).thenReturn(mockProducer2); + when(mockInnerSession.createProducer(serviceBusQueue2)).thenReturn(mockProducer3); // Create CachingConnectionFactory with caching enabled CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory(mockTargetConnectionFactory); @@ -207,20 +200,22 @@ void cachingConnectionFactoryReusesSameProducerForSameDestination() throws Excep Connection connection = cachingConnectionFactory.createConnection(); Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); - // Create queues - these are ServiceBusJmsQueue instances with consistent toString() values + // Create queues - these are ServiceBusJmsQueue instances Queue queue1FirstCall = session.createQueue("queue1"); Queue queue1SecondCall = session.createQueue("queue1"); Queue queue2 = session.createQueue("queue2"); // First call: create producer for queue1 (first ServiceBusJmsQueue instance) MessageProducer producer1ForQueue1 = session.createProducer(queue1FirstCall); - // Second call: create producer for queue1 (second ServiceBusJmsQueue instance, same toString()) - // With azure-servicebus-jms 2.1.0, this returns cached producer because toString() is consistent + // Second call: create producer for queue1 (second ServiceBusJmsQueue instance) + // In azure-servicebus-jms 2.1.0, this returns cached producer because toString() is consistent + // In azure-servicebus-jms 2.0.0, this would return a different producer because toString() was unique MessageProducer producer2ForQueue1 = session.createProducer(queue1SecondCall); // Third call: create producer for different queue2 - should return different producer MessageProducer producerForQueue2 = session.createProducer(queue2); - // Verify: same producer is returned for same queue name due to consistent toString() + // Verify: same producer is returned for same queue name + // This assertion would fail with azure-servicebus-jms 2.0.0 because toString() returned unique values assertThat(producer1ForQueue1.toString()) .as("Same producer should be returned for ServiceBusJmsQueue instances with same queue name") .isEqualTo(producer2ForQueue1.toString()); From 4568cce1854432b4e5730ebb2a2f83df7efadb0a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 2 Dec 2025 09:22:19 +0000 Subject: [PATCH 11/11] Fix mock to handle any Destination for createProducer Changed mock from specific ServiceBusJmsQueue instances to any(Destination.class) so that createProducer returns proper mock producers regardless of which ServiceBusJmsQueue instance is passed. This ensures the test doesn't return null in 2.0.0 when cache miss occurs. Each call to createProducer now returns a new mock producer. In 2.1.0, CachingConnectionFactory caches based on toString() so same producer is returned. In 2.0.0, toString() was unique so no caching, different producers. Co-authored-by: Netyyyy <92105726+Netyyyy@users.noreply.github.com> --- ...msConnectionFactoryConfigurationTests.java | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryConfigurationTests.java b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryConfigurationTests.java index 31ceafbe55ca..1e58a992365c 100644 --- a/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryConfigurationTests.java +++ b/sdk/spring/spring-cloud-azure-autoconfigure/src/test/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsConnectionFactoryConfigurationTests.java @@ -7,6 +7,7 @@ import com.azure.spring.cloud.autoconfigure.implementation.context.properties.AzureGlobalProperties; import jakarta.jms.Connection; import jakarta.jms.ConnectionFactory; +import jakarta.jms.Destination; import jakarta.jms.MessageProducer; import jakarta.jms.Queue; import jakarta.jms.Session; @@ -24,6 +25,7 @@ import static com.azure.spring.cloud.autoconfigure.implementation.util.TestServiceBusUtils.CONNECTION_STRING_FORMAT; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; @@ -165,20 +167,20 @@ void cachingConnectionFactoryReusesSameProducerForSameDestination() throws Excep when(mockInnerQueue2.getQueueName()).thenReturn("queue1"); when(mockInnerQueue3.getQueueName()).thenReturn("queue2"); when(mockInnerSession.createQueue("queue1")) + .thenReturn(mockInnerQueue1) + .thenReturn(mockInnerQueue2) .thenReturn(mockInnerQueue1) .thenReturn(mockInnerQueue2); - when(mockInnerSession.createQueue("queue2")).thenReturn(mockInnerQueue3); + when(mockInnerSession.createQueue("queue2")) + .thenReturn(mockInnerQueue3) + .thenReturn(mockInnerQueue3); // Create ServiceBusJmsSession using reflection (constructor is package-private) // ServiceBusJmsSession.createQueue() wraps the inner Queue in ServiceBusJmsQueue // which has the proper toString() implementation in azure-servicebus-jms 2.1.0 Session serviceBusJmsSession = createServiceBusJmsSession(mockInnerSession); - - // Create ServiceBusJmsQueue instances through ServiceBusJmsSession - Queue serviceBusQueue1FirstCall = serviceBusJmsSession.createQueue("queue1"); - Queue serviceBusQueue1SecondCall = serviceBusJmsSession.createQueue("queue1"); - Queue serviceBusQueue2 = serviceBusJmsSession.createQueue("queue2"); + // Create mock producers - each call to createProducer returns a new unique producer MessageProducer mockProducer1 = mock(MessageProducer.class); MessageProducer mockProducer2 = mock(MessageProducer.class); MessageProducer mockProducer3 = mock(MessageProducer.class); @@ -186,11 +188,13 @@ void cachingConnectionFactoryReusesSameProducerForSameDestination() throws Excep // Setup mock behavior for connection and session when(mockTargetConnectionFactory.createConnection()).thenReturn(mockConnection); when(mockConnection.createSession(anyBoolean(), anyInt())).thenReturn(serviceBusJmsSession); - // Each ServiceBusJmsQueue instance gets a different producer from the underlying session - // CachingConnectionFactory should cache based on destination.toString() - when(mockInnerSession.createProducer(serviceBusQueue1FirstCall)).thenReturn(mockProducer1); - when(mockInnerSession.createProducer(serviceBusQueue1SecondCall)).thenReturn(mockProducer2); - when(mockInnerSession.createProducer(serviceBusQueue2)).thenReturn(mockProducer3); + // Mock createProducer to return different producers for each call + // This simulates that each ServiceBusJmsQueue instance would get a different producer + // if caching doesn't work (i.e., toString() returns unique values as in 2.0.0) + when(mockInnerSession.createProducer(any(Destination.class))) + .thenReturn(mockProducer1) + .thenReturn(mockProducer2) + .thenReturn(mockProducer3); // Create CachingConnectionFactory with caching enabled CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory(mockTargetConnectionFactory);