From b27b31864781a6c95111662356615c020c2e9323 Mon Sep 17 00:00:00 2001 From: James Meyer Date: Tue, 23 Jun 2026 18:37:00 +0200 Subject: [PATCH 1/4] fix: authenticate version compatibility check with resolved credentials The checkCompatibility health check in Builder.build() used this.callCredentials, which withApiKey() no longer populates since the custom-headers refactor (49e4826, released in 1.18.0): the api key MetadataCredentials are only constructed after the check runs. Against an auth-required server (e.g. Qdrant Cloud) the check therefore ran unauthenticated and logged "Failed to obtain server version", re-introducing #96. Resolve the effective call credentials before running the check and pass them in. Add a regression test asserting the check receives the api key credentials. Fixes #131 --- .../io/qdrant/client/QdrantGrpcClient.java | 15 ++--- .../QdrantGrpcClientCompatibilityTest.java | 61 +++++++++++++++++++ 2 files changed, 69 insertions(+), 7 deletions(-) create mode 100644 src/test/java/io/qdrant/client/QdrantGrpcClientCompatibilityTest.java diff --git a/src/main/java/io/qdrant/client/QdrantGrpcClient.java b/src/main/java/io/qdrant/client/QdrantGrpcClient.java index 67a23d7..5db3425 100644 --- a/src/main/java/io/qdrant/client/QdrantGrpcClient.java +++ b/src/main/java/io/qdrant/client/QdrantGrpcClient.java @@ -273,16 +273,16 @@ public Builder withHeaders(Map headers) { * @return a new instance of {@link QdrantGrpcClient} */ public QdrantGrpcClient build() { - if (checkCompatibility) { - String clientVersion = Builder.class.getPackage().getImplementationVersion(); - checkVersionsCompatibility(clientVersion); - } - CallCredentials credentials = this.callCredentials; if (credentials == null && (apiKey != null || headers != null)) { credentials = new MetadataCredentials(apiKey, headers); } + if (checkCompatibility) { + String clientVersion = Builder.class.getPackage().getImplementationVersion(); + checkVersionsCompatibility(clientVersion, credentials); + } + return new QdrantGrpcClient(channel, shutdownChannelOnClose, credentials, timeout); } @@ -301,11 +301,12 @@ private static ManagedChannel createChannel( return channelBuilder.build(); } - private void checkVersionsCompatibility(String clientVersion) { + private void checkVersionsCompatibility( + String clientVersion, @Nullable CallCredentials credentials) { try { String serverVersion = QdrantGrpc.newBlockingStub(this.channel) - .withCallCredentials(this.callCredentials) + .withCallCredentials(credentials) .healthCheck(QdrantOuterClass.HealthCheckRequest.getDefaultInstance()) .getVersion(); if (!VersionsCompatibilityChecker.isCompatible(clientVersion, serverVersion)) { diff --git a/src/test/java/io/qdrant/client/QdrantGrpcClientCompatibilityTest.java b/src/test/java/io/qdrant/client/QdrantGrpcClientCompatibilityTest.java new file mode 100644 index 0000000..f60b216 --- /dev/null +++ b/src/test/java/io/qdrant/client/QdrantGrpcClientCompatibilityTest.java @@ -0,0 +1,61 @@ +package io.qdrant.client; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import io.grpc.CallCredentials; +import io.grpc.ManagedChannel; +import io.qdrant.client.grpc.QdrantGrpc; +import io.qdrant.client.grpc.QdrantOuterClass; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.MockedStatic; + +/** + * Unit tests for the version compatibility check performed by {@link + * QdrantGrpcClient.Builder#build()}. + * + *

Regression coverage: the check must authenticate with the credentials configured via {@link + * QdrantGrpcClient.Builder#withApiKey(String)}. Previously the check was issued before those + * credentials were resolved, so against an auth-required server (e.g. Qdrant Cloud) it failed with + * {@code "Failed to obtain server version"}. + */ +class QdrantGrpcClientCompatibilityTest { + + @Test + void compatibility_check_uses_api_key_credentials_from_with_api_key() { + ManagedChannel channel = mock(ManagedChannel.class); + QdrantGrpc.QdrantBlockingStub stub = mock(QdrantGrpc.QdrantBlockingStub.class); + QdrantOuterClass.HealthCheckReply reply = + QdrantOuterClass.HealthCheckReply.newBuilder() + .setTitle("qdrant") + .setVersion("1.0.0") + .build(); + + try (MockedStatic grpc = mockStatic(QdrantGrpc.class)) { + grpc.when(() -> QdrantGrpc.newBlockingStub(any())).thenReturn(stub); + when(stub.withCallCredentials(any())).thenReturn(stub); + when(stub.healthCheck(any())).thenReturn(reply); + + // checkCompatibility = true so build() runs the version check. + QdrantGrpcClient client = + QdrantGrpcClient.newBuilder(channel, false, true).withApiKey("my-api-key").build(); + client.close(); + + ArgumentCaptor credentialsCaptor = + ArgumentCaptor.forClass(CallCredentials.class); + verify(stub).withCallCredentials(credentialsCaptor.capture()); + + CallCredentials usedCredentials = credentialsCaptor.getValue(); + assertNotNull( + usedCredentials, + "Version compatibility check must run with the API key credentials, not null"); + assertInstanceOf(MetadataCredentials.class, usedCredentials); + } + } +} From 8f91d0194b64b7a9eabc8322f040b184e49c8026 Mon Sep 17 00:00:00 2001 From: Anush Date: Tue, 23 Jun 2026 22:21:47 +0530 Subject: [PATCH 2/4] ci: Use actions/checkout v5 to resolve dev-vince/actions-publish-javadoc failure --- .github/workflows/cd.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 1951157..f122d22 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -17,7 +17,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - name: Set up JDK 21 uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0 @@ -65,7 +65,7 @@ jobs: ORG_GRADLE_PROJECT_sonatypePassword: ${{ secrets.ORG_GRADLE_PROJECT_SONATYPEPASSWORD }} steps: - name: Checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - name: Set up JDK 21 uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0 From 6ec699ca9705b9fe864d6aa82a51a3176b88ab26 Mon Sep 17 00:00:00 2001 From: Anush Date: Tue, 23 Jun 2026 22:22:20 +0530 Subject: [PATCH 3/4] Apply suggestions from code review Co-authored-by: Anush --- .../io/qdrant/client/QdrantGrpcClientCompatibilityTest.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/test/java/io/qdrant/client/QdrantGrpcClientCompatibilityTest.java b/src/test/java/io/qdrant/client/QdrantGrpcClientCompatibilityTest.java index f60b216..e54fe92 100644 --- a/src/test/java/io/qdrant/client/QdrantGrpcClientCompatibilityTest.java +++ b/src/test/java/io/qdrant/client/QdrantGrpcClientCompatibilityTest.java @@ -19,11 +19,6 @@ /** * Unit tests for the version compatibility check performed by {@link * QdrantGrpcClient.Builder#build()}. - * - *

Regression coverage: the check must authenticate with the credentials configured via {@link - * QdrantGrpcClient.Builder#withApiKey(String)}. Previously the check was issued before those - * credentials were resolved, so against an auth-required server (e.g. Qdrant Cloud) it failed with - * {@code "Failed to obtain server version"}. */ class QdrantGrpcClientCompatibilityTest { @@ -42,7 +37,6 @@ void compatibility_check_uses_api_key_credentials_from_with_api_key() { when(stub.withCallCredentials(any())).thenReturn(stub); when(stub.healthCheck(any())).thenReturn(reply); - // checkCompatibility = true so build() runs the version check. QdrantGrpcClient client = QdrantGrpcClient.newBuilder(channel, false, true).withApiKey("my-api-key").build(); client.close(); From 9b4a5983b7bf22ae0c4184de3d37c0263bbc6ce6 Mon Sep 17 00:00:00 2001 From: Anush008 Date: Tue, 23 Jun 2026 22:27:05 +0530 Subject: [PATCH 4/4] chore: Bump version to v1.18.3 --- README.md | 6 +++--- example/build.gradle | 2 +- gradle.properties | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 2308136..8134ae8 100644 --- a/README.md +++ b/README.md @@ -38,20 +38,20 @@ To install the library, add the following lines to your build config file. io.qdrant client - 1.18.2 + 1.18.3 ``` #### SBT ```sbt -libraryDependencies += "io.qdrant" % "client" % "1.18.2" +libraryDependencies += "io.qdrant" % "client" % "1.18.3" ``` #### Gradle ```gradle -implementation 'io.qdrant:client:1.18.2' +implementation 'io.qdrant:client:1.18.3' ``` > [!NOTE] diff --git a/example/build.gradle b/example/build.gradle index 4a1a172..1952c13 100644 --- a/example/build.gradle +++ b/example/build.gradle @@ -13,7 +13,7 @@ repositories { dependencies { // Qdrant Java client - implementation 'io.qdrant:client:1.18.2' + implementation 'io.qdrant:client:1.18.3' // gRPC dependencies - use the same version as Qdrant client implementation 'io.grpc:grpc-netty-shaded:1.65.1' diff --git a/gradle.properties b/gradle.properties index 82672dd..dc0b8fe 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,4 +5,4 @@ qdrantProtosVersion=v1.18.0 qdrantVersion=v1.18.0 # The version of the client to generate -packageVersion=1.18.2 +packageVersion=1.18.3