Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,26 @@
import io.pinecone.exceptions.PineconeBadRequestException;
import io.pinecone.exceptions.PineconeNotFoundException;
import io.pinecone.exceptions.PineconeUnmappedHttpException;
import io.pinecone.exceptions.PineconeValidationException;
import io.pinecone.helpers.TestIndexResourcesManager;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.openapitools.client.model.*;

import static io.pinecone.helpers.IndexManager.waitUntilIndexIsReady;
import java.util.Arrays;

import static org.junit.jupiter.api.Assertions.*;

public class CreateDescribeListAndDeleteIndexTest {

private static final TestIndexResourcesManager indexManager = TestIndexResourcesManager.getInstance();
private static Pinecone controlPlaneClient = new Pinecone.Builder(System.getenv("PINECONE_API_KEY")).build();
private static String indexName;
private static int dimension;
// Serverless currently has limited availability in specific regions, hard-code us-west-2 for now
private static final String serverlessRegion = "us-west-2";
private static final Pinecone controlPlaneClient = new Pinecone.Builder(System.getenv("PINECONE_API_KEY")).build();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a lint; ignore

private static String indexName;
private static int dimension;

@BeforeAll
public static void setUp() throws InterruptedException {
indexName = indexManager.getServerlessIndexName();
Expand Down Expand Up @@ -61,6 +65,17 @@ public void createIndexWithInvalidName() {
}
}

@Test
public void createServerlessIndexWithInvalidName() {
try {
controlPlaneClient.createServerlessIndex("Invalid-name", "cosine", 3, "aws", "us-west-2");

fail("Expected to throw PineconeBadRequestException");
} catch (PineconeBadRequestException expected) {
assertTrue(expected.getLocalizedMessage().contains("Name must consist of lower case alphanumeric characters or '-'"));
}
}

@Test
public void createIndexWithInvalidDimension() {
ServerlessSpec serverlessSpec = new ServerlessSpec().cloud(ServerlessSpec.CloudEnum.AWS).region(serverlessRegion);
Expand All @@ -80,6 +95,16 @@ public void createIndexWithInvalidDimension() {
}
}

@Test
public void createServerlessIndexWithInvalidDimension() {
try {
controlPlaneClient.createServerlessIndex("serverless-test-index", "cosine", -3, "aws", "us-west-2");
fail("Expected to throw PineconeValidationException");
} catch (PineconeValidationException expected) {
assertTrue(expected.getLocalizedMessage().contains("Dimension must be greater than 0"));
}
}

@Test
public void createIndexInvalidCloud() {
ServerlessSpec serverlessSpec = new ServerlessSpec().cloud(ServerlessSpec.CloudEnum.AZURE).region(serverlessRegion);
Expand All @@ -99,6 +124,16 @@ public void createIndexInvalidCloud() {
}
}

@Test
public void createServerlessIndexWithInvalidCloud() {
try {
controlPlaneClient.createServerlessIndex("serverless-test-index", "cosine", 3, "blah", "us-west-2");
fail("Expected to throw PineconeValidationException");
} catch (PineconeValidationException expected) {
assertTrue(expected.getLocalizedMessage().contains("Cloud cannot be null or empty. Must be one of " + Arrays.toString(ServerlessSpec.CloudEnum.values())));
}
}

@Test
public void createIndexInvalidRegion() {
ServerlessSpec serverlessSpec = new ServerlessSpec().cloud(ServerlessSpec.CloudEnum.AWS).region("invalid-region");
Expand All @@ -117,4 +152,14 @@ public void createIndexInvalidRegion() {
assertTrue(expected.getLocalizedMessage().contains("Resource cloud: aws region: invalid-region not found"));
}
}

@Test
public void createServerlessIndexWithInvalidRegion() {
try {
controlPlaneClient.createServerlessIndex("serverless-test-index", "cosine", 3, "aws", "invalid-region");
fail("Expected to throw PineconeNotFoundException");
} catch (PineconeNotFoundException expected) {
assertTrue(expected.getLocalizedMessage().contains("Resource cloud: aws region: invalid-region not found"));
}
}
}
61 changes: 61 additions & 0 deletions src/main/java/io/pinecone/clients/Pinecone.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
import org.openapitools.client.api.ManageIndexesApi;
import org.openapitools.client.model.*;

import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

public class Pinecone {
Expand All @@ -38,6 +41,64 @@ public IndexModel createIndex(CreateIndexRequest createIndexRequest) throws Pine
return indexModel;
}

public IndexModel createServerlessIndex(String indexName, String metric, int dimension, String cloud,
String region) {
Comment on lines +44 to +45
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like these as simple string types for simplicity. Can we add javadoc to this method? I bet copilot will write most of it for you.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might leave this to @rohanshah18 since I think he's been working on that for this SDK in general, but I'll confirm in Slack :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good callout though, yeah we need to add javadoc comments all over.

if (indexName == null || indexName.isEmpty()) {
throw new PineconeValidationException("Index name cannot be null or empty");
}

if (metric == null || metric.isEmpty()) {
throw new PineconeValidationException("Metric cannot be null or empty. Must be one of " + Arrays.toString(IndexMetric.values()));
}
if (!(metric == null)) {
try {
IndexMetric.fromValue(metric.toLowerCase());
} catch (IllegalArgumentException e) {
throw new PineconeValidationException("Metric cannot be null or empty. Must be one of " + Arrays.toString(IndexMetric.values()));
}
}

if (dimension < 1) {
throw new PineconeValidationException("Dimension must be greater than 0. See limits for more info: https://docs.pinecone.io/reference/limits");
}

if (cloud == null || cloud.isEmpty()) {
throw new PineconeValidationException("Cloud cannot be null or empty. Must be one of " + Arrays.toString(ServerlessSpec.CloudEnum.values()));
}
if (!(cloud == null)) {
try {
ServerlessSpec.CloudEnum.fromValue(cloud.toLowerCase());
} catch (IllegalArgumentException e) {
throw new PineconeValidationException("Cloud cannot be null or empty. Must be one of " + Arrays.toString(ServerlessSpec.CloudEnum.values()));
}
}

if (region == null || region.isEmpty()) {
throw new PineconeValidationException("Region cannot be null or empty");
}

// Convert user string for "metric" arg into IndexMetric
IndexMetric userMetric = IndexMetric.fromValue(metric.toLowerCase());

// Convert user string for "cloud" arg into ServerlessSpec.CloudEnum
ServerlessSpec.CloudEnum cloudProvider = ServerlessSpec.CloudEnum.fromValue(cloud.toLowerCase());

ServerlessSpec serverlessSpec = new ServerlessSpec().cloud(cloudProvider).region(region);
CreateIndexRequestSpec createServerlessIndexRequestSpec = new CreateIndexRequestSpec().serverless(serverlessSpec);

IndexModel indexModel = null;
try {
indexModel = manageIndexesApi.createIndex(new CreateIndexRequest()
.name(indexName)
.metric(userMetric)
.dimension(dimension)
.spec(createServerlessIndexRequestSpec));
} catch (ApiException apiException) {
handleApiException(apiException);
}
return indexModel;
}

public IndexModel describeIndex(String indexName) throws PineconeException {
IndexModel indexModel = null;
try {
Expand Down
75 changes: 73 additions & 2 deletions src/test/java/io/pinecone/PineconeIndexOperationsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@
import java.util.Arrays;
import java.util.List;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;

Expand Down Expand Up @@ -47,6 +46,78 @@ public void testDeleteIndex() throws IOException {
assertEquals(requestCaptor.getValue().url().toString(), "https://api.pinecone.io/indexes/testIndex");
}

@Test
public void testCreateServerlessIndex() throws IOException {
String filePath = "src/test/resources/serverlessIndexJsonString.json";
String indexJsonStringServerless = new String(Files.readAllBytes(Paths.get(filePath)));

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("OK")
.body(ResponseBody.create(indexJsonStringServerless, MediaType.parse("application/json")))
.build());

OkHttpClient mockClient = mock(OkHttpClient.class);
when(mockClient.newCall(any(Request.class))).thenReturn(mockCall);

Pinecone client = new Pinecone.Builder("testAPiKey").withOkHttpClient(mockClient).build();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for covering all these test cases!


client.createServerlessIndex("testServerlessIndex", "cosine", 3, "aws", "us-west-2");
verify(mockCall, times(1)).execute();

PineconeValidationException thrownEmptyIndexName = assertThrows(PineconeValidationException.class,
() -> client.createServerlessIndex("", "cosine", 3, "aws", "us-west-2"));
assertEquals("Index name cannot be null or empty", thrownEmptyIndexName.getMessage());

PineconeValidationException thrownNullIndexName = assertThrows(PineconeValidationException.class,
() -> client.createServerlessIndex(null, "cosine", 3, "aws", "us-west-2"));
assertEquals("Index name cannot be null or empty", thrownNullIndexName.getMessage());

PineconeValidationException thrownEmptyMetric = assertThrows(PineconeValidationException.class,
() -> client.createServerlessIndex("testServerlessIndex", "", 3, "aws", "us-west-2"));
assertEquals("Metric cannot be null or empty. Must be one of " + Arrays.toString(IndexMetric.values()), thrownEmptyMetric.getMessage());

PineconeValidationException thrownInvalidMetric = assertThrows(PineconeValidationException.class,
() -> client.createServerlessIndex("testServerlessIndex", "blah", 3, "aws", "us-west-2"));
assertEquals(String.format("Metric cannot be null or empty. Must be one of " + Arrays.toString(IndexMetric.values())), thrownInvalidMetric.getMessage());

PineconeValidationException thrownNullMetric = assertThrows(PineconeValidationException.class,
() -> client.createServerlessIndex("testServerlessIndex", null, 3, "aws", "us-west-2"));
assertEquals("Metric cannot be null or empty. Must be one of " + Arrays.toString(IndexMetric.values()),
thrownNullMetric.getMessage());

PineconeValidationException thrownNegativeDimension = assertThrows(PineconeValidationException.class,
() -> client.createServerlessIndex("testServerlessIndex", "cosine", -3, "aws", "us-west-2"));
assertEquals("Dimension must be greater than 0. See limits for more info: https://docs.pinecone.io/reference/limits", thrownNegativeDimension.getMessage());

PineconeValidationException thrownEmptyCloud = assertThrows(PineconeValidationException.class,
() -> client.createServerlessIndex("testServerlessIndex", "cosine", 3, "", "us-west-2"));
assertEquals("Cloud cannot be null or empty. Must be one of " + Arrays.toString(ServerlessSpec.CloudEnum.values()),
thrownEmptyCloud.getMessage());

PineconeValidationException thrownNullCloud = assertThrows(PineconeValidationException.class,
() -> client.createServerlessIndex("testServerlessIndex", "cosine", 3, null, "us-west-2"));
assertEquals("Cloud cannot be null or empty. Must be one of " + Arrays.toString(ServerlessSpec.CloudEnum.values()),
thrownNullCloud.getMessage());

PineconeValidationException thrownInvalidCloud = assertThrows(PineconeValidationException.class,
() -> client.createServerlessIndex("testServerlessIndex", "cosine", 3, "wooooo", "us-west-2"));
assertEquals("Cloud cannot be null or empty. Must be one of " + Arrays.toString(ServerlessSpec.CloudEnum.values()),
thrownInvalidCloud.getMessage());

PineconeValidationException thrownEmptyRegion = assertThrows(PineconeValidationException.class,
() -> client.createServerlessIndex("testServerlessIndex", "cosine", 3, "aws", ""));
assertEquals("Region cannot be null or empty", thrownEmptyRegion.getMessage());

PineconeValidationException thrownNullRegion = assertThrows(PineconeValidationException.class,
() -> client.createServerlessIndex("testServerlessIndex", "cosine", 3, "aws", null));
assertEquals("Region cannot be null or empty", thrownNullRegion.getMessage());
}


@Test
public void testCreatePodIndex() throws IOException {
String filePath = "src/test/resources/podIndexJsonString.json";
Expand Down