diff --git a/build.gradle b/build.gradle index ad2d6956..775dc98a 100644 --- a/build.gradle +++ b/build.gradle @@ -28,15 +28,16 @@ dependencies { runtime 'io.netty:netty-tcnative-boringssl-static:2.0.59.Final' implementation 'org.slf4j:slf4j-api:2.0.5' implementation 'com.google.api.grpc:proto-google-common-protos:2.14.3' + implementation 'com.squareup.okhttp3:okhttp:4.10.0' + testImplementation("com.squareup.okhttp3:mockwebserver:4.10.0") implementation 'org.asynchttpclient:async-http-client:2.12.1' implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.14.2' implementation group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.14.2' compileOnly "org.apache.tomcat:annotations-api:6.0.53" // necessary for Java 9+ testImplementation "io.grpc:grpc-testing:${grpcVersion}" - testImplementation 'junit:junit:4.13.2' testImplementation "org.hamcrest:hamcrest:2.2" - testImplementation 'org.mockito:mockito-core:4.8.0' + testImplementation 'org.mockito:mockito-inline:4.8.0' testImplementation 'org.slf4j:slf4j-simple:2.0.5' testImplementation 'com.fasterxml.jackson.core:jackson-databind:2.14.2' testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.0' diff --git a/src/main/java/io/pinecone/PineconeClient.java b/src/main/java/io/pinecone/PineconeClient.java index a149a846..c7536ad2 100644 --- a/src/main/java/io/pinecone/PineconeClient.java +++ b/src/main/java/io/pinecone/PineconeClient.java @@ -1,5 +1,7 @@ package io.pinecone; +import io.pinecone.exceptions.PineconeValidationException; + /** * Top-level client for connecting and making calls to Pinecone services. One instance can * be used to connect to multiple services and shared across threads. diff --git a/src/main/java/io/pinecone/PineconeClientConfig.java b/src/main/java/io/pinecone/PineconeClientConfig.java index 8a386808..cf1eba11 100644 --- a/src/main/java/io/pinecone/PineconeClientConfig.java +++ b/src/main/java/io/pinecone/PineconeClientConfig.java @@ -1,5 +1,7 @@ package io.pinecone; +import io.pinecone.exceptions.PineconeValidationException; + import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -17,9 +19,6 @@ public class PineconeClientConfig { */ private String apiKey; - /** - * Required project name - */ private String projectName; private String environment; diff --git a/src/main/java/io/pinecone/PineconeConnection.java b/src/main/java/io/pinecone/PineconeConnection.java index 65579428..00e5f41b 100644 --- a/src/main/java/io/pinecone/PineconeConnection.java +++ b/src/main/java/io/pinecone/PineconeConnection.java @@ -6,6 +6,8 @@ import io.grpc.netty.NegotiationType; import io.grpc.netty.NettyChannelBuilder; import io.grpc.stub.MetadataUtils; +import io.pinecone.exceptions.PineconeException; +import io.pinecone.exceptions.PineconeValidationException; import io.pinecone.proto.VectorServiceGrpc; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/io/pinecone/PineconeConnectionConfig.java b/src/main/java/io/pinecone/PineconeConnectionConfig.java index 8590079c..de09c6ae 100644 --- a/src/main/java/io/pinecone/PineconeConnectionConfig.java +++ b/src/main/java/io/pinecone/PineconeConnectionConfig.java @@ -1,6 +1,7 @@ package io.pinecone; import io.grpc.ManagedChannel; +import io.pinecone.exceptions.PineconeValidationException; import java.util.function.BiFunction; diff --git a/src/main/java/io/pinecone/PineconeIndexOperationClient.java b/src/main/java/io/pinecone/PineconeIndexOperationClient.java index b0a09d52..3d934229 100644 --- a/src/main/java/io/pinecone/PineconeIndexOperationClient.java +++ b/src/main/java/io/pinecone/PineconeIndexOperationClient.java @@ -1,52 +1,92 @@ package io.pinecone; +import io.pinecone.exceptions.FailedRequestInfo; +import io.pinecone.exceptions.HttpErrorMapper; +import io.pinecone.exceptions.PineconeConfigurationException; import io.pinecone.model.CreateIndexRequest; -import org.asynchttpclient.AsyncHttpClient; -import org.asynchttpclient.DefaultAsyncHttpClient; +import okhttp3.*; import java.io.IOException; public class PineconeIndexOperationClient { - private AsyncHttpClient client; - private PineconeClientConfig clientConfig; - private String url; + private final OkHttpClient client; + private final PineconeClientConfig clientConfig; + private final String url; + public static final String ACCEPT_HEADER = "accept"; + public static final String API_KEY_HEADER_NAME = "Api-Key"; + public static final String BASE_URL_PREFIX = "https://controller."; + public static final String BASE_URL_SUFFIX = ".pinecone.io/databases/"; + public static final String CONTENT_TYPE = "content-type"; + public static final String CONTENT_TYPE_JSON = "application/json"; + public static final String DELETE = "DELETE"; + public static final String EMPTY_RESOURCE_PATH = ""; + public static final String POST = "POST"; + public static final String TEXT_PLAIN = "text/plain"; - PineconeIndexOperationClient(PineconeClientConfig clientConfig, AsyncHttpClient client) { - this.clientConfig = clientConfig; + private PineconeIndexOperationClient(PineconeClientConfig clientConfig, OkHttpClient client, String url) { this.client = client; - this.url = "https://controller." + clientConfig.getEnvironment() + ".pinecone.io/databases/"; + this.clientConfig = clientConfig; + this.url = url; + } + + public PineconeIndexOperationClient(PineconeClientConfig clientConfig, OkHttpClient client) { + this(clientConfig, client, createUrl(clientConfig)); } public PineconeIndexOperationClient(PineconeClientConfig clientConfig) { - this.clientConfig = clientConfig; - client = new DefaultAsyncHttpClient(); - this.url = "https://controller." + clientConfig.getEnvironment() + ".pinecone.io/databases/"; + this(clientConfig, new OkHttpClient()); } - public void deleteIndex(String indexName) throws IOException { - System.out.println("Sending delete index request:"); - client.prepare("DELETE", url + indexName) - .setHeader("accept", "text/plain") - .setHeader("Api-Key", clientConfig.getApiKey()) - .execute() - .toCompletableFuture() - .join(); + private static String createUrl(PineconeClientConfig clientConfig) { + if (clientConfig.getApiKey() == null || clientConfig.getEnvironment() == null) { + throw new PineconeConfigurationException("Both API key and environment name are required for index operations."); + } - client.close(); + return BASE_URL_PREFIX + clientConfig.getEnvironment() + BASE_URL_SUFFIX; + } + + public void deleteIndex(String indexName) throws IOException { + try (Response response = client.newCall(buildRequest(DELETE, indexName, TEXT_PLAIN,null)).execute()) { + handleResponse(response); + } } public void createIndex(CreateIndexRequest createIndexRequest) throws IOException { - client.prepare("POST", url) - .setHeader("accept", "text/plain") - .setHeader("content-type", "application/json") - .setHeader("Api-Key", clientConfig.getApiKey()) - .setBody(createIndexRequest.toJson()) - .execute() - .toCompletableFuture() - .join(); + MediaType mediaType = MediaType.parse("application/json; charset=utf-8"); + RequestBody requestBody = RequestBody.create(createIndexRequest.toJson(), mediaType); - client.close(); + try (Response response = client.newCall(buildRequest(POST, EMPTY_RESOURCE_PATH, TEXT_PLAIN, requestBody)).execute()) { + handleResponse(response); + } } -} + private Request buildRequest(String method, String path, String header, RequestBody requestBody) { + Request.Builder builder = new Request.Builder() + .url(url + path) + .addHeader(ACCEPT_HEADER, header) + .addHeader(API_KEY_HEADER_NAME, clientConfig.getApiKey()); + + if (POST.equals(method)) { + builder.post(requestBody); + builder.addHeader(CONTENT_TYPE, CONTENT_TYPE_JSON); + } else if (DELETE.equals(method)) { + builder.delete(); + } + + return builder.build(); + } + private void handleResponse(Response response) throws IOException { + if (!response.isSuccessful()) { + int statusCode = response.code(); + String responseBodyString = (response.body() != null) ? response.body().string() : null; + FailedRequestInfo failedRequestInfo = new FailedRequestInfo(statusCode, responseBodyString); + HttpErrorMapper.mapHttpStatusError(failedRequestInfo); + } + } + + public void close() { + client.dispatcher().executorService().shutdown(); + client.connectionPool().evictAll(); + } +} \ No newline at end of file diff --git a/src/main/java/io/pinecone/exceptions/FailedRequestInfo.java b/src/main/java/io/pinecone/exceptions/FailedRequestInfo.java new file mode 100644 index 00000000..2de2367a --- /dev/null +++ b/src/main/java/io/pinecone/exceptions/FailedRequestInfo.java @@ -0,0 +1,20 @@ +package io.pinecone.exceptions; + +public class FailedRequestInfo { + private final int status; + private final String message; + + public FailedRequestInfo(int status, String message) { + this.status = status; + this.message = message; + } + + public int getStatus() { + return status; + } + + public String getMessage() { + return message; + } +} + diff --git a/src/main/java/io/pinecone/exceptions/HttpErrorMapper.java b/src/main/java/io/pinecone/exceptions/HttpErrorMapper.java new file mode 100644 index 00000000..6d702785 --- /dev/null +++ b/src/main/java/io/pinecone/exceptions/HttpErrorMapper.java @@ -0,0 +1,22 @@ +package io.pinecone.exceptions; + +public class HttpErrorMapper { + + public static void mapHttpStatusError(FailedRequestInfo failedRequestInfo) { + int statusCode = failedRequestInfo.getStatus(); + switch (statusCode) { + case 400: + throw new PineconeBadRequestException(failedRequestInfo.getMessage()); + case 401: + throw new PineconeAuthorizationException(failedRequestInfo.getMessage()); + case 404: + throw new PineconeNotFoundException(failedRequestInfo.getMessage()); + case 409: + throw new PineconeAlreadyExistsException(failedRequestInfo.getMessage()); + case 500: + throw new PineconeInternalServerException(failedRequestInfo.getMessage()); + default: + throw new PineconeUnmappedHttpException(failedRequestInfo.getMessage()); + } + } +} diff --git a/src/main/java/io/pinecone/exceptions/PineconeAlreadyExistsException.java b/src/main/java/io/pinecone/exceptions/PineconeAlreadyExistsException.java new file mode 100644 index 00000000..3b5aa94d --- /dev/null +++ b/src/main/java/io/pinecone/exceptions/PineconeAlreadyExistsException.java @@ -0,0 +1,12 @@ +package io.pinecone.exceptions; + +public class PineconeAlreadyExistsException extends PineconeException { + + public PineconeAlreadyExistsException(String message) { + super(message); + } + + public PineconeAlreadyExistsException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/io/pinecone/exceptions/PineconeAuthorizationException.java b/src/main/java/io/pinecone/exceptions/PineconeAuthorizationException.java new file mode 100644 index 00000000..6b5e56cd --- /dev/null +++ b/src/main/java/io/pinecone/exceptions/PineconeAuthorizationException.java @@ -0,0 +1,12 @@ +package io.pinecone.exceptions; + +public class PineconeAuthorizationException extends PineconeException { + + public PineconeAuthorizationException(String message) { + super(message); + } + + public PineconeAuthorizationException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/io/pinecone/exceptions/PineconeBadRequestException.java b/src/main/java/io/pinecone/exceptions/PineconeBadRequestException.java new file mode 100644 index 00000000..0eb3a7fe --- /dev/null +++ b/src/main/java/io/pinecone/exceptions/PineconeBadRequestException.java @@ -0,0 +1,12 @@ +package io.pinecone.exceptions; + +public class PineconeBadRequestException extends PineconeException { + + public PineconeBadRequestException(String message) { + super(message); + } + + public PineconeBadRequestException(String message, Throwable cause) { + super(message, cause); + } +} \ No newline at end of file diff --git a/src/main/java/io/pinecone/exceptions/PineconeConfigurationException.java b/src/main/java/io/pinecone/exceptions/PineconeConfigurationException.java new file mode 100644 index 00000000..7f5b8972 --- /dev/null +++ b/src/main/java/io/pinecone/exceptions/PineconeConfigurationException.java @@ -0,0 +1,12 @@ +package io.pinecone.exceptions; + +public class PineconeConfigurationException extends PineconeException { + + public PineconeConfigurationException(String message) { + super(message); + } + + public PineconeConfigurationException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/io/pinecone/PineconeException.java b/src/main/java/io/pinecone/exceptions/PineconeException.java similarity index 88% rename from src/main/java/io/pinecone/PineconeException.java rename to src/main/java/io/pinecone/exceptions/PineconeException.java index c3b841b5..631f1318 100644 --- a/src/main/java/io/pinecone/PineconeException.java +++ b/src/main/java/io/pinecone/exceptions/PineconeException.java @@ -1,4 +1,4 @@ -package io.pinecone; +package io.pinecone.exceptions; public class PineconeException extends RuntimeException { diff --git a/src/main/java/io/pinecone/exceptions/PineconeInternalServerException.java b/src/main/java/io/pinecone/exceptions/PineconeInternalServerException.java new file mode 100644 index 00000000..bdd1ae20 --- /dev/null +++ b/src/main/java/io/pinecone/exceptions/PineconeInternalServerException.java @@ -0,0 +1,12 @@ +package io.pinecone.exceptions; + +public class PineconeInternalServerException extends PineconeException { + + public PineconeInternalServerException(String message) { + super(message); + } + + public PineconeInternalServerException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/io/pinecone/exceptions/PineconeNotFoundException.java b/src/main/java/io/pinecone/exceptions/PineconeNotFoundException.java new file mode 100644 index 00000000..a28cd36e --- /dev/null +++ b/src/main/java/io/pinecone/exceptions/PineconeNotFoundException.java @@ -0,0 +1,12 @@ +package io.pinecone.exceptions; + +public class PineconeNotFoundException extends PineconeException { + + public PineconeNotFoundException(String message) { + super(message); + } + + public PineconeNotFoundException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/io/pinecone/exceptions/PineconeUnmappedHttpException.java b/src/main/java/io/pinecone/exceptions/PineconeUnmappedHttpException.java new file mode 100644 index 00000000..aa65d341 --- /dev/null +++ b/src/main/java/io/pinecone/exceptions/PineconeUnmappedHttpException.java @@ -0,0 +1,12 @@ +package io.pinecone.exceptions; + +public class PineconeUnmappedHttpException extends PineconeException { + + public PineconeUnmappedHttpException(String message) { + super(message); + } + + public PineconeUnmappedHttpException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/io/pinecone/PineconeValidationException.java b/src/main/java/io/pinecone/exceptions/PineconeValidationException.java similarity index 89% rename from src/main/java/io/pinecone/PineconeValidationException.java rename to src/main/java/io/pinecone/exceptions/PineconeValidationException.java index 6f4ae7f7..d3b776f4 100644 --- a/src/main/java/io/pinecone/PineconeValidationException.java +++ b/src/main/java/io/pinecone/exceptions/PineconeValidationException.java @@ -1,4 +1,4 @@ -package io.pinecone; +package io.pinecone.exceptions; public class PineconeValidationException extends PineconeException { diff --git a/src/main/java/io/pinecone/model/CreateIndexRequest.java b/src/main/java/io/pinecone/model/CreateIndexRequest.java index d56a7c86..a92c9eb3 100644 --- a/src/main/java/io/pinecone/model/CreateIndexRequest.java +++ b/src/main/java/io/pinecone/model/CreateIndexRequest.java @@ -3,7 +3,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import io.pinecone.PineconeValidationException; +import io.pinecone.exceptions.PineconeValidationException; public class CreateIndexRequest { private String indexName; @@ -186,7 +186,7 @@ public static void validateJsonObject(JsonNode jsonNode) { if(jsonNode.get("name").isNull()) { throw new PineconeValidationException("Index name cannot be empty"); } - System.out.println("jsonString: " + jsonNode.get("dimension")); + if(jsonNode.get("dimension").isNull()) { throw new PineconeValidationException("Dimension cannot be empty"); } diff --git a/src/test/java/io/pinecone/PineconeIndexOperationClientTest.java b/src/test/java/io/pinecone/PineconeIndexOperationClientTest.java index 29f377f6..b06c4256 100644 --- a/src/test/java/io/pinecone/PineconeIndexOperationClientTest.java +++ b/src/test/java/io/pinecone/PineconeIndexOperationClientTest.java @@ -1,154 +1,147 @@ package io.pinecone; +import io.pinecone.exceptions.PineconeConfigurationException; +import io.pinecone.exceptions.PineconeValidationException; import io.pinecone.model.CreateIndexRequest; import io.pinecone.model.IndexMetadataConfig; -import org.asynchttpclient.*; +import okhttp3.*; +import okhttp3.mockwebserver.MockWebServer; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.BeforeAll; import java.io.IOException; import java.util.Arrays; import java.util.List; -import java.util.concurrent.CompletableFuture; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.*; public class PineconeIndexOperationClientTest { + private static MockWebServer mockWebServer; + + @BeforeAll + public static void setUp() throws IOException { + mockWebServer = new MockWebServer(); + mockWebServer.start(); + } + + @AfterAll + public static void tearDown() throws IOException { + mockWebServer.shutdown(); + } - @SuppressWarnings("unchecked") @Test - public void testIndexDeletion() throws IOException { - AsyncHttpClient mockClient = mock(DefaultAsyncHttpClient.class); - PineconeClientConfig clientConfig = new PineconeClientConfig().withApiKey("testApiKey").withEnvironment("testEnvironment"); - Response mockResponse = mock(Response.class); - BoundRequestBuilder mockBoundRequestBuilder = mock(BoundRequestBuilder.class); - ListenableFuture mockResponseFuture = mock(ListenableFuture.class); - CompletableFuture mockCompletableFuture = mock(CompletableFuture.class); - - when(mockResponse.getStatusCode()).thenReturn(202); - when(mockClient.prepare(anyString(), anyString())).thenReturn(mockBoundRequestBuilder); - when(mockBoundRequestBuilder.setHeader(anyString(), anyString())).thenReturn(mockBoundRequestBuilder); - when(mockBoundRequestBuilder.execute()).thenReturn(mockResponseFuture); - when(mockResponseFuture.toCompletableFuture()).thenReturn(mockCompletableFuture); - - PineconeIndexOperationClient indexService = new PineconeIndexOperationClient(clientConfig, mockClient); - indexService.deleteIndex("testIndex"); - - verify(mockClient, times(1)).prepare(eq("DELETE"), anyString()); - verify(mockBoundRequestBuilder).setHeader(eq("Api-Key"), eq("testApiKey")); - verify(mockBoundRequestBuilder).execute(); - verify(mockClient).close(); + public void IndexOpsWithoutApiKey() throws IOException { + PineconeClientConfig clientConfig = new PineconeClientConfig() + .withEnvironment("testEnvironment"); + OkHttpClient mockClient = mock(OkHttpClient.class); + + assertThrows(PineconeConfigurationException.class, () -> new PineconeIndexOperationClient(clientConfig, mockClient)); } - @SuppressWarnings("unchecked") @Test - public void testIndexCreation() throws IOException { - AsyncHttpClient mockClient = mock(DefaultAsyncHttpClient.class); - PineconeClientConfig clientConfig = new PineconeClientConfig().withApiKey("testApiKey").withEnvironment("testEnvironment"); - Response mockResponse = mock(Response.class); - BoundRequestBuilder mockBoundRequestBuilder = mock(BoundRequestBuilder.class); - ListenableFuture mockResponseFuture = mock(ListenableFuture.class); - CompletableFuture mockCompletableFuture = mock(CompletableFuture.class); - - when(mockResponse.getStatusCode()).thenReturn(201); - when(mockClient.prepare(anyString(), anyString())).thenReturn(mockBoundRequestBuilder); - when(mockBoundRequestBuilder.setHeader(anyString(), anyString())).thenReturn(mockBoundRequestBuilder); - when(mockBoundRequestBuilder.setBody(anyString())).thenReturn(mockBoundRequestBuilder); - when(mockBoundRequestBuilder.execute()).thenReturn(mockResponseFuture); - when(mockResponseFuture.toCompletableFuture()).thenReturn(mockCompletableFuture); - - PineconeIndexOperationClient indexService = new PineconeIndexOperationClient(clientConfig, mockClient); - CreateIndexRequest createIndexRequest = new CreateIndexRequest().withIndexName("test_name").withDimension(3); - indexService.createIndex(createIndexRequest); - - verify(mockClient, times(1)).prepare(eq("POST"), anyString()); - verify(mockBoundRequestBuilder).setHeader(eq("accept"), eq("text/plain")); - verify(mockBoundRequestBuilder).setHeader(eq("content-type"), eq("application/json")); - verify(mockBoundRequestBuilder).execute(); - verify(mockClient).close(); + public void IndexOpsWithoutEnvironment() throws IOException { + PineconeClientConfig clientConfig = new PineconeClientConfig() + .withApiKey("testApiKey"); + OkHttpClient mockClient = mock(OkHttpClient.class); + + assertThrows(PineconeConfigurationException.class, () -> new PineconeIndexOperationClient(clientConfig, mockClient)); } @Test - public void testIndexCreationThrowsExceptionWithNullIndex() { - AsyncHttpClient mockClient = mock(DefaultAsyncHttpClient.class); - PineconeClientConfig clientConfig = new PineconeClientConfig().withApiKey("testApiKey").withEnvironment("testEnvironment"); - Response mockResponse = mock(Response.class); - BoundRequestBuilder mockBoundRequestBuilder = mock(BoundRequestBuilder.class); - ListenableFuture mockResponseFuture = mock(ListenableFuture.class); - CompletableFuture mockCompletableFuture = mock(CompletableFuture.class); - - when(mockResponse.getStatusCode()).thenReturn(201); - when(mockClient.prepare(anyString(), anyString())).thenReturn(mockBoundRequestBuilder); - when(mockBoundRequestBuilder.setHeader(anyString(), anyString())).thenReturn(mockBoundRequestBuilder); - when(mockBoundRequestBuilder.setBody(anyString())).thenReturn(mockBoundRequestBuilder); - when(mockBoundRequestBuilder.execute()).thenReturn(mockResponseFuture); - when(mockResponseFuture.toCompletableFuture()).thenReturn(mockCompletableFuture); - - PineconeIndexOperationClient indexService = new PineconeIndexOperationClient(clientConfig, mockClient); - CreateIndexRequest createIndexRequest = new CreateIndexRequest(); - - try{ - indexService.createIndex(createIndexRequest); - fail("Expected validation exception not occurred"); - } - catch (PineconeValidationException validationException) { - System.out.println("Expected PineconeValidationException with index not null occurred!"); - } - catch (IOException e) { - throw new RuntimeException(e); - } + public void IndexOpsWithoutApiKeyAndEnvironment() throws IOException { + PineconeClientConfig clientConfig = new PineconeClientConfig(); + OkHttpClient mockClient = mock(OkHttpClient.class); + + assertThrows(PineconeConfigurationException.class, () -> new PineconeIndexOperationClient(clientConfig, mockClient)); } @Test - public void testIndexCreationThrowsExceptionWithNullDimension() { - AsyncHttpClient mockClient = mock(DefaultAsyncHttpClient.class); - PineconeClientConfig clientConfig = new PineconeClientConfig().withApiKey("testApiKey").withEnvironment("testEnvironment"); - Response mockResponse = mock(Response.class); - BoundRequestBuilder mockBoundRequestBuilder = mock(BoundRequestBuilder.class); - ListenableFuture mockResponseFuture = mock(ListenableFuture.class); - CompletableFuture mockCompletableFuture = mock(CompletableFuture.class); - - when(mockResponse.getStatusCode()).thenReturn(201); - when(mockClient.prepare(anyString(), anyString())).thenReturn(mockBoundRequestBuilder); - when(mockBoundRequestBuilder.setHeader(anyString(), anyString())).thenReturn(mockBoundRequestBuilder); - when(mockBoundRequestBuilder.setBody(anyString())).thenReturn(mockBoundRequestBuilder); - when(mockBoundRequestBuilder.execute()).thenReturn(mockResponseFuture); - when(mockResponseFuture.toCompletableFuture()).thenReturn(mockCompletableFuture); - - PineconeIndexOperationClient indexService = new PineconeIndexOperationClient(clientConfig, mockClient); - CreateIndexRequest createIndexRequest = new CreateIndexRequest().withIndexName("test_name"); - - try{ - indexService.createIndex(createIndexRequest); - fail("Expected validation exception not occurred"); - } - catch (PineconeValidationException validationException) { - System.out.println("Expected PineconeValidationException with dimension not null occurred!"); - } catch (IOException e) { - throw new RuntimeException(e); - } + public void testDeleteIndex() throws IOException { + String indexName = "testIndex"; + PineconeClientConfig clientConfig = new PineconeClientConfig() + .withApiKey("testApiKey") + .withEnvironment("testEnvironment"); + + Call mockCall = mock(Call.class); + when(mockCall.execute()).thenReturn(new Response.Builder() + .request(new Request.Builder().url("http://localhost").build()) + .protocol(Protocol.HTTP_1_1) + .code(200) + .message("The index has been successfully deleted.") + .body(ResponseBody.create(MediaType.parse("text/plain"), "Response body")) + .build()); + + OkHttpClient mockClient = mock(OkHttpClient.class); + when(mockClient.newCall(any(Request.class))).thenReturn(mockCall); + PineconeIndexOperationClient indexOperationClient = new PineconeIndexOperationClient(clientConfig, mockClient); + indexOperationClient.deleteIndex(indexName); + + verify(mockClient, times(1)).newCall(any(Request.class)); + verify(mockCall, times(1)).execute(); } @Test - public void testIndexCreationWithAllFields() { - AsyncHttpClient mockClient = mock(DefaultAsyncHttpClient.class); - PineconeClientConfig clientConfig = new PineconeClientConfig().withApiKey("testApiKey").withEnvironment("testEnvironment"); - Response mockResponse = mock(Response.class); - BoundRequestBuilder mockBoundRequestBuilder = mock(BoundRequestBuilder.class); - ListenableFuture mockResponseFuture = mock(ListenableFuture.class); - CompletableFuture mockCompletableFuture = mock(CompletableFuture.class); - - when(mockResponse.getStatusCode()).thenReturn(201); - when(mockClient.prepare(anyString(), anyString())).thenReturn(mockBoundRequestBuilder); - when(mockBoundRequestBuilder.setHeader(anyString(), anyString())).thenReturn(mockBoundRequestBuilder); - when(mockBoundRequestBuilder.setBody(anyString())).thenReturn(mockBoundRequestBuilder); - when(mockBoundRequestBuilder.execute()).thenReturn(mockResponseFuture); - when(mockResponseFuture.toCompletableFuture()).thenReturn(mockCompletableFuture); + public void testCreateIndex() throws IOException { + PineconeClientConfig clientConfig = new PineconeClientConfig() + .withApiKey("testApiKey") + .withEnvironment("testEnvironment"); + CreateIndexRequest createIndexRequest = new CreateIndexRequest() + .withIndexName("test_name").withDimension(3); + + Call mockCall = mock(Call.class); + when(mockCall.execute()).thenReturn(new Response.Builder() + .request(new Request.Builder().url("http://localhost").build()) + .protocol(Protocol.HTTP_1_1) + .code(201) + .message("The index has been successfully created") + .body(ResponseBody.create(MediaType.parse("text/plain"), "Response body")) + .build()); + + OkHttpClient mockClient = mock(OkHttpClient.class); + when(mockClient.newCall(any(Request.class))).thenReturn(mockCall); + PineconeIndexOperationClient indexOperationClient = new PineconeIndexOperationClient(clientConfig, mockClient); + indexOperationClient.createIndex(createIndexRequest); + + verify(mockClient, times(1)).newCall(any(Request.class)); + verify(mockCall, times(1)).execute(); + } + + @Test + public void testCreateIndexWithNullIndex() { + PineconeClientConfig clientConfig = new PineconeClientConfig() + .withApiKey("testApiKey") + .withEnvironment("testEnvironment"); + CreateIndexRequest createIndexRequest = new CreateIndexRequest().withDimension(3); + + OkHttpClient mockClient = mock(OkHttpClient.class); + PineconeIndexOperationClient indexOperationClient = new PineconeIndexOperationClient(clientConfig, mockClient); + assertThrows(PineconeValidationException.class, () -> indexOperationClient.createIndex(createIndexRequest)); + } + + @Test + public void testCreateIndexWithNullDimensions() { + PineconeClientConfig clientConfig = new PineconeClientConfig() + .withApiKey("testApiKey") + .withEnvironment("testEnvironment"); + CreateIndexRequest createIndexRequest = new CreateIndexRequest().withIndexName("testIndexName"); + + OkHttpClient mockClient = mock(OkHttpClient.class); + PineconeIndexOperationClient indexOperationClient = new PineconeIndexOperationClient(clientConfig, mockClient); + assertThrows(PineconeValidationException.class, () -> indexOperationClient.createIndex(createIndexRequest)); + } + + @Test + public void testCreateIndexWithAllFields() throws IOException { + PineconeClientConfig clientConfig = new PineconeClientConfig() + .withApiKey("testApiKey") + .withEnvironment("testEnvironment"); IndexMetadataConfig metadataConfig = new IndexMetadataConfig(); List indexedItems = Arrays.asList("A", "B", "C", "D"); metadataConfig.setIndexed(indexedItems); - PineconeIndexOperationClient indexService = new PineconeIndexOperationClient(clientConfig, mockClient); + CreateIndexRequest createIndexRequest = new CreateIndexRequest() .withIndexName("test_name") .withDimension(3) @@ -159,11 +152,22 @@ public void testIndexCreationWithAllFields() { .withMetadataConfig(metadataConfig) .withSourceCollection("step"); - try { - indexService.createIndex(createIndexRequest); - } - catch (IOException e) { - throw new RuntimeException(e); - } + Call mockCall = mock(Call.class); + when(mockCall.execute()).thenReturn(new Response.Builder() + .request(new Request.Builder().url("http://localhost").build()) + .protocol(Protocol.HTTP_1_1) + .code(201) + .message("The index has been successfully created") + .body(ResponseBody.create(MediaType.parse("text/plain"), "Response body")) + .build()); + + OkHttpClient mockClient = mock(OkHttpClient.class); + when(mockClient.newCall(any(Request.class))).thenReturn(mockCall); + + PineconeIndexOperationClient indexOperationClient = new PineconeIndexOperationClient(clientConfig, mockClient); + indexOperationClient.createIndex(createIndexRequest); + + verify(mockClient, times(1)).newCall(any(Request.class)); + verify(mockCall, times(1)).execute(); } } \ No newline at end of file