From d27d598871b7a97f21f308ac5487cec57eb610e7 Mon Sep 17 00:00:00 2001 From: Tarek Ahmed Date: Mon, 16 Mar 2026 17:15:33 +0200 Subject: [PATCH 1/3] Add related searches and related browse pages, add tests --- .../client/models/RelatedBrowsePage.java | 77 +++++++++++++++++++ .../client/models/RelatedSearch.java | 26 +++++++ .../client/models/SearchResponseInner.java | 34 ++++++++ .../client/SearchResponseTest.java | 44 +++++++++++ .../test/resources/response.search.item.json | 24 +++++- 5 files changed, 204 insertions(+), 1 deletion(-) create mode 100644 constructorio-client/src/main/java/io/constructor/client/models/RelatedBrowsePage.java create mode 100644 constructorio-client/src/main/java/io/constructor/client/models/RelatedSearch.java diff --git a/constructorio-client/src/main/java/io/constructor/client/models/RelatedBrowsePage.java b/constructorio-client/src/main/java/io/constructor/client/models/RelatedBrowsePage.java new file mode 100644 index 00000000..a102f611 --- /dev/null +++ b/constructorio-client/src/main/java/io/constructor/client/models/RelatedBrowsePage.java @@ -0,0 +1,77 @@ +package io.constructor.client.models; + +import com.google.gson.annotations.SerializedName; + +/** + * Constructor.io Related Browse Page - uses Gson/Reflection to load data in + */ +public class RelatedBrowsePage { + + @SerializedName("filter_name") + private String filterName; + + @SerializedName("filter_value") + private String filterValue; + + @SerializedName("display_name") + private String displayName; + + @SerializedName("image_url") + private String imageUrl; + + /** + * @return the filter name + */ + public String getFilterName() { + return filterName; + } + + /** + * @return the filter value + */ + public String getFilterValue() { + return filterValue; + } + + /** + * @return the display name + */ + public String getDisplayName() { + return displayName; + } + + /** + * @return the image URL + */ + public String getImageUrl() { + return imageUrl; + } + + /** + * @param filterName the filter name to set + */ + public void setFilterName(String filterName) { + this.filterName = filterName; + } + + /** + * @param filterValue the filter value to set + */ + public void setFilterValue(String filterValue) { + this.filterValue = filterValue; + } + + /** + * @param displayName the display name to set + */ + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + /** + * @param imageUrl the image URL to set + */ + public void setImageUrl(String imageUrl) { + this.imageUrl = imageUrl; + } +} diff --git a/constructorio-client/src/main/java/io/constructor/client/models/RelatedSearch.java b/constructorio-client/src/main/java/io/constructor/client/models/RelatedSearch.java new file mode 100644 index 00000000..aba93496 --- /dev/null +++ b/constructorio-client/src/main/java/io/constructor/client/models/RelatedSearch.java @@ -0,0 +1,26 @@ +package io.constructor.client.models; + +import com.google.gson.annotations.SerializedName; + +/** + * Constructor.io Related Search - uses Gson/Reflection to load data in + */ +public class RelatedSearch { + + @SerializedName("query") + private String query; + + /** + * @return the related search query + */ + public String getQuery() { + return query; + } + + /** + * @param query the related search query to set + */ + public void setQuery(String query) { + this.query = query; + } +} diff --git a/constructorio-client/src/main/java/io/constructor/client/models/SearchResponseInner.java b/constructorio-client/src/main/java/io/constructor/client/models/SearchResponseInner.java index b2664bb2..b32661db 100644 --- a/constructorio-client/src/main/java/io/constructor/client/models/SearchResponseInner.java +++ b/constructorio-client/src/main/java/io/constructor/client/models/SearchResponseInner.java @@ -15,6 +15,12 @@ public class SearchResponseInner extends BaseResultsResponse { @SerializedName("features") private List features; + @SerializedName("related_searches") + private List relatedSearches; + + @SerializedName("related_browse_pages") + private List relatedBrowsePages; + /** * @return redirect data */ @@ -47,4 +53,32 @@ public void setRefinedContent(List refinedContent) { public void setFeatures(List features) { this.features = features; } + + /** + * @return list of related searches + */ + public List getRelatedSearches() { + return relatedSearches; + } + + /** + * @return list of related browse pages + */ + public List getRelatedBrowsePages() { + return relatedBrowsePages; + } + + /** + * @param relatedSearches list of related searches to set + */ + public void setRelatedSearches(List relatedSearches) { + this.relatedSearches = relatedSearches; + } + + /** + * @param relatedBrowsePages list of related browse pages to set + */ + public void setRelatedBrowsePages(List relatedBrowsePages) { + this.relatedBrowsePages = relatedBrowsePages; + } } diff --git a/constructorio-client/src/test/java/io/constructor/client/SearchResponseTest.java b/constructorio-client/src/test/java/io/constructor/client/SearchResponseTest.java index 477592f2..c8c885d5 100644 --- a/constructorio-client/src/test/java/io/constructor/client/SearchResponseTest.java +++ b/constructorio-client/src/test/java/io/constructor/client/SearchResponseTest.java @@ -336,4 +336,48 @@ public void createSearchResponseShouldReturnAResultWithVariationSlice() throws E .get(0), "801764002"); } + + @Test + public void createSearchResponseShouldReturnAResultWithRelatedSearches() throws Exception { + String string = Utils.getTestResource("response.search.item.json"); + SearchResponse response = ConstructorIO.createSearchResponse(string); + assertEquals( + "search result [related searches] exists", + response.getResponse().getRelatedSearches().size(), + 2); + assertEquals( + "search result related search [query] exists", + response.getResponse().getRelatedSearches().get(0).getQuery(), + "related item search"); + assertEquals( + "search result related search [query] second item exists", + response.getResponse().getRelatedSearches().get(1).getQuery(), + "similar products"); + } + + @Test + public void createSearchResponseShouldReturnAResultWithRelatedBrowsePages() throws Exception { + String string = Utils.getTestResource("response.search.item.json"); + SearchResponse response = ConstructorIO.createSearchResponse(string); + assertEquals( + "search result [related browse pages] exists", + response.getResponse().getRelatedBrowsePages().size(), + 2); + assertEquals( + "search result related browse page [filter name] exists", + response.getResponse().getRelatedBrowsePages().get(0).getFilterName(), + "group_id"); + assertEquals( + "search result related browse page [filter value] exists", + response.getResponse().getRelatedBrowsePages().get(0).getFilterValue(), + "electronics"); + assertEquals( + "search result related browse page [display name] exists", + response.getResponse().getRelatedBrowsePages().get(0).getDisplayName(), + "Electronics"); + assertEquals( + "search result related browse page [image url] exists", + response.getResponse().getRelatedBrowsePages().get(0).getImageUrl(), + "https://example.com/electronics.jpg"); + } } diff --git a/constructorio-client/src/test/resources/response.search.item.json b/constructorio-client/src/test/resources/response.search.item.json index 08a0cc28..24ab931f 100644 --- a/constructorio-client/src/test/resources/response.search.item.json +++ b/constructorio-client/src/test/resources/response.search.item.json @@ -193,7 +193,29 @@ "name": "query_items_ctr_and_l2r", "display_name": "CTR & LTR" } - }] + }], + "related_searches": [ + { + "query": "related item search" + }, + { + "query": "similar products" + } + ], + "related_browse_pages": [ + { + "filter_name": "group_id", + "filter_value": "electronics", + "display_name": "Electronics", + "image_url": "https://example.com/electronics.jpg" + }, + { + "filter_name": "Brand", + "filter_value": "sample_brand", + "display_name": "Sample Brand Products", + "image_url": "https://example.com/sample_brand.jpg" + } + ] }, "result_id": "7938b9e8-369b-4f0c-ae74-69b85dcb5b12", "request": { From 4b07aeb63ba295f9493ebecf4e860d3d8b81bd79 Mon Sep 17 00:00:00 2001 From: Tarek Ahmed Date: Mon, 16 Mar 2026 17:18:32 +0200 Subject: [PATCH 2/3] Lint --- .../io/constructor/client/models/RelatedBrowsePage.java | 6 ++---- .../java/io/constructor/client/models/RelatedSearch.java | 4 +--- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/constructorio-client/src/main/java/io/constructor/client/models/RelatedBrowsePage.java b/constructorio-client/src/main/java/io/constructor/client/models/RelatedBrowsePage.java index a102f611..7da8d0df 100644 --- a/constructorio-client/src/main/java/io/constructor/client/models/RelatedBrowsePage.java +++ b/constructorio-client/src/main/java/io/constructor/client/models/RelatedBrowsePage.java @@ -2,9 +2,7 @@ import com.google.gson.annotations.SerializedName; -/** - * Constructor.io Related Browse Page - uses Gson/Reflection to load data in - */ +/** Constructor.io Related Browse Page - uses Gson/Reflection to load data in */ public class RelatedBrowsePage { @SerializedName("filter_name") @@ -32,7 +30,7 @@ public String getFilterName() { public String getFilterValue() { return filterValue; } - + /** * @return the display name */ diff --git a/constructorio-client/src/main/java/io/constructor/client/models/RelatedSearch.java b/constructorio-client/src/main/java/io/constructor/client/models/RelatedSearch.java index aba93496..b367bee6 100644 --- a/constructorio-client/src/main/java/io/constructor/client/models/RelatedSearch.java +++ b/constructorio-client/src/main/java/io/constructor/client/models/RelatedSearch.java @@ -2,9 +2,7 @@ import com.google.gson.annotations.SerializedName; -/** - * Constructor.io Related Search - uses Gson/Reflection to load data in - */ +/** Constructor.io Related Search - uses Gson/Reflection to load data in */ public class RelatedSearch { @SerializedName("query") From 9740599e13c1dcf1605e49a4b9659b3d9f0bbea0 Mon Sep 17 00:00:00 2001 From: Tarek Ahmed Date: Mon, 16 Mar 2026 21:28:12 +0200 Subject: [PATCH 3/3] Fix other tests --- .../client/ConstructorIOTaskTest.java | 12 ++++-------- .../client/ConstructorIOTasksTest.java | 12 ++++-------- .../constructor/client/SearchResponseTest.java | 16 ++++++++++++++++ 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/constructorio-client/src/test/java/io/constructor/client/ConstructorIOTaskTest.java b/constructorio-client/src/test/java/io/constructor/client/ConstructorIOTaskTest.java index 874ec34a..311840b8 100644 --- a/constructorio-client/src/test/java/io/constructor/client/ConstructorIOTaskTest.java +++ b/constructorio-client/src/test/java/io/constructor/client/ConstructorIOTaskTest.java @@ -77,9 +77,7 @@ public void TaskRequestWithInvalidTokenShouldError() throws Exception { TaskRequest request = new TaskRequest(String.valueOf(task_id)); thrown.expect(ConstructorException.class); - thrown.expectMessage( - "[HTTP 401] Invalid auth_token. If you've forgotten your token, you can generate a" - + " new one at app.constructor.io/dashboard"); + thrown.expectMessage(StringContains.containsString("[HTTP 401] Unauthorized")); Task response = constructor.task(request); } @@ -89,9 +87,7 @@ public void TaskAsJSONRequestWithInvalidTokenShouldError() throws Exception { TaskRequest request = new TaskRequest(String.valueOf(task_id)); thrown.expect(ConstructorException.class); - thrown.expectMessage( - "[HTTP 401] Invalid auth_token. If you've forgotten your token, you can generate a" - + " new one at app.constructor.io/dashboard"); + thrown.expectMessage(StringContains.containsString("[HTTP 401] Unauthorized")); String response = constructor.taskAsJson(request); } @@ -103,7 +99,7 @@ public void TaskRequestWithInvalidApiKeyShouldError() throws Exception { thrown.expect(ConstructorException.class); thrown.expectMessage( StringContains.containsString( - "[HTTP 401] You have supplied an invalid `key` or `autocomplete_key`.")); + "[HTTP 400] You have supplied an invalid `key` or `autocomplete_key`.")); Task response = constructor.task(request); } @@ -115,7 +111,7 @@ public void TaskAsJSONRequestWithInvalidApiKeyShouldError() throws Exception { thrown.expect(ConstructorException.class); thrown.expectMessage( StringContains.containsString( - "[HTTP 401] You have supplied an invalid `key` or `autocomplete_key`.")); + "[HTTP 400] You have supplied an invalid `key` or `autocomplete_key`.")); String response = constructor.taskAsJson(request); } } diff --git a/constructorio-client/src/test/java/io/constructor/client/ConstructorIOTasksTest.java b/constructorio-client/src/test/java/io/constructor/client/ConstructorIOTasksTest.java index 8ceedb1c..5d3cb6e2 100644 --- a/constructorio-client/src/test/java/io/constructor/client/ConstructorIOTasksTest.java +++ b/constructorio-client/src/test/java/io/constructor/client/ConstructorIOTasksTest.java @@ -207,7 +207,7 @@ public void AllTasksShouldReturnErrorWithInvalidApiKey() throws Exception { thrown.expect(ConstructorException.class); thrown.expectMessage( - "[HTTP 401] You have supplied an invalid `key` or `autocomplete_key`. You can find" + "[HTTP 400] You have supplied an invalid `key` or `autocomplete_key`. You can find" + " your key at app.constructor.io/dashboard/accounts/api_integration."); AllTasksResponse response = constructor.allTasks(request); } @@ -218,9 +218,7 @@ public void AllTasksShouldReturnErrorWithInvalidApiToken() throws Exception { AllTasksRequest request = new AllTasksRequest(); thrown.expect(ConstructorException.class); - thrown.expectMessage( - "[HTTP 401] Invalid auth_token. If you've forgotten your token, you can generate a" - + " new one at app.constructor.io/dashboard"); + thrown.expectMessage(StringContains.containsString("[HTTP 401] Unauthorized")); AllTasksResponse response = constructor.allTasks(request); } @@ -232,7 +230,7 @@ public void AllTasksAsJSONShouldReturnErrorWithInvalidApiKey() throws Exception thrown.expect(ConstructorException.class); thrown.expectMessage( StringContains.containsString( - "[HTTP 401] You have supplied an invalid `key` or `autocomplete_key`.")); + "[HTTP 400] You have supplied an invalid `key` or `autocomplete_key`.")); String response = constructor.allTasksAsJson(request); } @@ -242,9 +240,7 @@ public void AllTasksAsJSONShouldReturnErrorWithInvalidApiToken() throws Exceptio AllTasksRequest request = new AllTasksRequest(); thrown.expect(ConstructorException.class); - thrown.expectMessage( - "[HTTP 401] Invalid auth_token. If you've forgotten your token, you can generate a" - + " new one at app.constructor.io/dashboard"); + thrown.expectMessage(StringContains.containsString("[HTTP 401] Unauthorized")); String response = constructor.allTasksAsJson(request); } } diff --git a/constructorio-client/src/test/java/io/constructor/client/SearchResponseTest.java b/constructorio-client/src/test/java/io/constructor/client/SearchResponseTest.java index c8c885d5..d7d3d111 100644 --- a/constructorio-client/src/test/java/io/constructor/client/SearchResponseTest.java +++ b/constructorio-client/src/test/java/io/constructor/client/SearchResponseTest.java @@ -379,5 +379,21 @@ public void createSearchResponseShouldReturnAResultWithRelatedBrowsePages() thro "search result related browse page [image url] exists", response.getResponse().getRelatedBrowsePages().get(0).getImageUrl(), "https://example.com/electronics.jpg"); + assertEquals( + "search result related browse page [filter name] exists", + response.getResponse().getRelatedBrowsePages().get(1).getFilterName(), + "Brand"); + assertEquals( + "search result related browse page [filter value] exists", + response.getResponse().getRelatedBrowsePages().get(1).getFilterValue(), + "sample_brand"); + assertEquals( + "search result related browse page [display name] exists", + response.getResponse().getRelatedBrowsePages().get(1).getDisplayName(), + "Sample Brand Products"); + assertEquals( + "search result related browse page [image url] exists", + response.getResponse().getRelatedBrowsePages().get(1).getImageUrl(), + "https://example.com/sample_brand.jpg"); } }