diff --git a/src/main/java/org/typesense/api/Client.java b/src/main/java/org/typesense/api/Client.java index b892628..0b49098 100644 --- a/src/main/java/org/typesense/api/Client.java +++ b/src/main/java/org/typesense/api/Client.java @@ -41,28 +41,16 @@ public class Client { public MultiSearch multiSearch; public Client(Configuration configuration, OkHttpClient okHttpClient){ - this.configuration = configuration; - this.apiCall = new ApiCall(configuration, okHttpClient); - collections = new Collections(apiCall); - this.individualCollections = new HashMap<>(); - this.aliases = new Aliases(this.apiCall); - this.individualAliases = new HashMap<>(); - this.keys = new Keys(this.apiCall); - this.individualKeys = new HashMap<>(); - this.health = new Health(this.apiCall); - this.operations = new Operations(this.apiCall); - this.metrics = new Metrics(this.apiCall); - this.debug = new Debug(this.apiCall); - this.multiSearch = new MultiSearch(this.apiCall); - this.analytics = new Analytics(this.apiCall); - this.stemming = new Stemming(this.apiCall); - this.stopwords = new Stopwords(this.apiCall); - this.individualStopwordsSets = new HashMap<>(); + this(configuration, new ApiCall(configuration, okHttpClient)); } public Client(Configuration configuration){ + this(configuration, new ApiCall(configuration)); + } + + private Client(Configuration configuration, ApiCall apiCall){ this.configuration = configuration; - this.apiCall = new ApiCall(configuration); + this.apiCall = apiCall; collections = new Collections(apiCall); this.individualCollections = new HashMap<>(); this.aliases = new Aliases(this.apiCall); diff --git a/src/main/java/org/typesense/api/SynonymSet.java b/src/main/java/org/typesense/api/SynonymSet.java index 4672409..973b01a 100644 --- a/src/main/java/org/typesense/api/SynonymSet.java +++ b/src/main/java/org/typesense/api/SynonymSet.java @@ -1,5 +1,8 @@ package org.typesense.api; +import java.util.HashMap; +import java.util.Map; + import org.typesense.api.utils.URLEncoding; import org.typesense.model.SynonymSetCreateSchema; @@ -11,9 +14,14 @@ public class SynonymSet { private String synonymSetName; private ApiCall apiCall; + private SynonymSetItems items; + private Map individualItems; + public SynonymSet(String synonymSetName, ApiCall apiCall) { this.synonymSetName = synonymSetName; this.apiCall = apiCall; + this.items = new SynonymSetItems(this.synonymSetName, this.apiCall); + this.individualItems = new HashMap<>(); } public SynonymSetCreateSchema retrieve() throws Exception { @@ -28,6 +36,21 @@ public SynonymSetDeleteSchema delete() throws Exception { return this.apiCall.delete(this.getEndpoint(), null, SynonymSetDeleteSchema.class); } + public SynonymSetItems items() { + return this.items; + } + + public SynonymSetItem items(String itemId) { + SynonymSetItem retVal; + + if (!this.individualItems.containsKey(itemId)) { + this.individualItems.put(itemId, new SynonymSetItem(this.synonymSetName, itemId, this.apiCall)); + } + + retVal = this.individualItems.get(itemId); + return retVal; + } + public String getEndpoint() { return "/synonym_sets/" + URLEncoding.encodeURIComponent(this.synonymSetName); } diff --git a/src/main/java/org/typesense/api/SynonymSetItem.java b/src/main/java/org/typesense/api/SynonymSetItem.java new file mode 100644 index 0000000..5c3773b --- /dev/null +++ b/src/main/java/org/typesense/api/SynonymSetItem.java @@ -0,0 +1,36 @@ +package org.typesense.api; + +import org.typesense.api.utils.URLEncoding; +import org.typesense.model.SynonymItemDeleteSchema; +import org.typesense.model.SynonymItemSchema; +import org.typesense.model.SynonymItemUpsertSchema; + +public class SynonymSetItem { + + private String synonymSetName; + private String itemId; + private ApiCall apiCall; + + public SynonymSetItem(String synonymSetName, String itemId, ApiCall apiCall) { + this.synonymSetName = synonymSetName; + this.itemId = itemId; + this.apiCall = apiCall; + } + + public SynonymItemSchema retrieve() throws Exception { + return this.apiCall.get(this.getEndpoint(), null, SynonymItemSchema.class); + } + + public SynonymItemSchema upsert(SynonymItemUpsertSchema synonymItemUpsertSchema) throws Exception { + return this.apiCall.put(this.getEndpoint(), synonymItemUpsertSchema, null, SynonymItemSchema.class); + } + + public SynonymItemDeleteSchema delete() throws Exception { + return this.apiCall.delete(this.getEndpoint(), null, SynonymItemDeleteSchema.class); + } + + public String getEndpoint() { + return SynonymSets.RESOURCEPATH + "/" + URLEncoding.encodeURIComponent(this.synonymSetName) + + SynonymSetItems.RESOURCEPATH + "/" + URLEncoding.encodeURIComponent(this.itemId); + } +} diff --git a/src/main/java/org/typesense/api/SynonymSetItems.java b/src/main/java/org/typesense/api/SynonymSetItems.java new file mode 100644 index 0000000..4a713a9 --- /dev/null +++ b/src/main/java/org/typesense/api/SynonymSetItems.java @@ -0,0 +1,25 @@ +package org.typesense.api; + +import org.typesense.api.utils.URLEncoding; +import org.typesense.model.SynonymItemSchema; + +public class SynonymSetItems { + + private String synonymSetName; + private ApiCall apiCall; + public final static String RESOURCEPATH = "/items"; + + public SynonymSetItems(String synonymSetName, ApiCall apiCall) { + this.synonymSetName = synonymSetName; + this.apiCall = apiCall; + } + + public SynonymItemSchema[] retrieve() throws Exception { + return this.apiCall.get(this.getEndpoint(), null, SynonymItemSchema[].class); + } + + public String getEndpoint() { + return SynonymSets.RESOURCEPATH + "/" + URLEncoding.encodeURIComponent(this.synonymSetName) + + SynonymSetItems.RESOURCEPATH; + } +} diff --git a/src/test/java/org/typesense/api/ClientTest.java b/src/test/java/org/typesense/api/ClientTest.java new file mode 100644 index 0000000..8a33fc6 --- /dev/null +++ b/src/test/java/org/typesense/api/ClientTest.java @@ -0,0 +1,55 @@ +package org.typesense.api; + +import okhttp3.Call; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import org.junit.jupiter.api.Test; +import org.typesense.resources.Node; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.time.Duration; +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +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 ClientTest { + + @Test + void testCustomOkHttpClientServesSynonymAndCurationSets() throws Exception { + OkHttpClient okHttpClient = mock(OkHttpClient.class); + Call call = mock(Call.class); + when(okHttpClient.newCall(any(Request.class))).thenReturn(call); + when(call.execute()).thenThrow(new IOException("no server")); + + Configuration configuration = new Configuration( + Arrays.asList(new Node("http", "localhost", "8108")), Duration.ofSeconds(3), "xyz"); + Client client = new Client(configuration, okHttpClient); + + assertThrows(IOException.class, () -> client.synonymSets().retrieve()); + assertThrows(IOException.class, () -> client.synonymSet("products").retrieve()); + assertThrows(IOException.class, () -> client.curationSets().retrieve()); + assertThrows(IOException.class, () -> client.curationSet("products").retrieve()); + verify(okHttpClient, times(4)).newCall(any(Request.class)); + } + + @Test + void testDefaultConstructorResetsNodeIndex() throws Exception { + Configuration configuration = new Configuration( + Arrays.asList(new Node("http", "localhost", "8108"), new Node("http", "localhost", "7108")), + Duration.ofSeconds(3), "xyz"); + new ApiCall(configuration, mock(OkHttpClient.class)).getNode(); + + new Client(configuration); + + Field nodeIndex = ApiCall.class.getDeclaredField("nodeIndex"); + nodeIndex.setAccessible(true); + assertEquals(0, nodeIndex.get(null)); + } +} diff --git a/src/test/java/org/typesense/api/SynonymSetItemsTest.java b/src/test/java/org/typesense/api/SynonymSetItemsTest.java new file mode 100644 index 0000000..88a798d --- /dev/null +++ b/src/test/java/org/typesense/api/SynonymSetItemsTest.java @@ -0,0 +1,96 @@ +package org.typesense.api; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.typesense.api.exceptions.ObjectNotFound; +import org.typesense.model.SynonymItemDeleteSchema; +import org.typesense.model.SynonymItemSchema; +import org.typesense.model.SynonymItemUpsertSchema; + +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.*; + +class SynonymSetItemsTest { + + private Client client; + private Helper helper; + private String synonymSetName; + + @BeforeEach + void setUp() throws Exception { + helper = new Helper(); + client = helper.getClient(); + + if (!Helper.isV30OrAbove(client)) { + org.junit.jupiter.api.Assumptions.assumeTrue(false, "Skipping test - requires Typesense v30 or above"); + } + + helper.teardown(); + synonymSetName = "test-synonym-set-" + System.currentTimeMillis(); + client.synonymSets().upsert(synonymSetName, helper.createTestSynonymSetData()); + } + + @AfterEach + void tearDown() throws Exception { + helper.teardown(); + } + + @Test + void testRetrieveAll() throws Exception { + SynonymItemSchema[] result = client.synonymSet(synonymSetName).items().retrieve(); + + assertEquals(1, result.length); + assertEquals("dummy", result[0].getId()); + assertEquals(Arrays.asList("foo", "bar", "baz"), result[0].getSynonyms()); + } + + @Test + void testRetrieve() throws Exception { + SynonymItemSchema result = client.synonymSet(synonymSetName).items("dummy").retrieve(); + + assertEquals("dummy", result.getId()); + assertEquals(Arrays.asList("foo", "bar", "baz"), result.getSynonyms()); + } + + @Test + void testUpsert() throws Exception { + SynonymItemUpsertSchema item = new SynonymItemUpsertSchema() + .root("smart phone") + .synonyms(Arrays.asList("iphone", "android")); + + SynonymItemSchema result = client.synonymSet(synonymSetName).items("smart?phone").upsert(item); + + assertEquals("smart?phone", result.getId()); + assertEquals("smart phone", result.getRoot()); + assertEquals(Arrays.asList("iphone", "android"), result.getSynonyms()); + assertEquals(2, client.synonymSet(synonymSetName).items().retrieve().length); + } + + @Test + void testUpsertReplacesExistingItem() throws Exception { + SynonymItemUpsertSchema item = new SynonymItemUpsertSchema().synonyms(Arrays.asList("foo", "qux")); + + client.synonymSet(synonymSetName).items("dummy").upsert(item); + + SynonymItemSchema result = client.synonymSet(synonymSetName).items("dummy").retrieve(); + assertEquals(Arrays.asList("foo", "qux"), result.getSynonyms()); + assertEquals(1, client.synonymSet(synonymSetName).items().retrieve().length); + } + + @Test + void testUpsertIntoMissingSet() { + SynonymItemUpsertSchema item = new SynonymItemUpsertSchema().synonyms(Arrays.asList("foo", "bar")); + + assertThrows(ObjectNotFound.class, () -> client.synonymSet("missing-set").items("dummy").upsert(item)); + } + + @Test + void testDelete() throws Exception { + SynonymItemDeleteSchema result = client.synonymSet(synonymSetName).items("dummy").delete(); + + assertEquals("dummy", result.getId()); + assertThrows(ObjectNotFound.class, () -> client.synonymSet(synonymSetName).items("dummy").retrieve()); + } +}