diff --git a/README.md b/README.md index 8714c8e0..cd2aaaf4 100644 --- a/README.md +++ b/README.md @@ -401,6 +401,95 @@ Task response = constructor.task(request); String response = constructor.taskAsJSON(request); ``` +# Managing Sort Options + +Sort options allow you to define custom sorting strategies for search and browse results. You can create, update, delete, and retrieve sort options for your catalog. + +## Creating a Sort Option + +To create a new sort option, you will need to create a `SortOption` object with the required fields and wrap it in a `SortOptionRequest`. + +```java +// Create a SortOption with the required fields +SortOption sortOption = new SortOption("price", SortOption.SortOrder.ascending); +sortOption.setDisplayName("Price"); +sortOption.setPathInMetadata("price_min"); +sortOption.setHidden(false); + +// Create a SortOptionRequest with the sort option and section +SortOptionRequest request = new SortOptionRequest(sortOption, "Products"); + +// Create the sort option +String response = constructor.createSortOption(request); +``` + +## Updating a Sort Option + +To update an existing sort option (or create it if it doesn't exist), use the `updateSortOption` method. The sort option is identified by the combination of `sortBy` and `sortOrder`. + +```java +// Create a SortOption with updated fields +SortOption sortOption = new SortOption("price", SortOption.SortOrder.ascending); +sortOption.setDisplayName("Price (Low to High)"); +sortOption.setPathInMetadata("price_min"); +sortOption.setHidden(false); +sortOption.setPosition(1); + +// Create a SortOptionRequest +SortOptionRequest request = new SortOptionRequest(sortOption, "Products"); + +// Update the sort option +String response = constructor.updateSortOption(request); +``` + +## Deleting Sort Options + +To delete a sort option, you need to specify the `sortBy` and `sortOrder` fields that identify it. + +```java +// Delete a single sort option by sortBy and sortOrder +String response = constructor.deleteSortOption("price", SortOption.SortOrder.ascending, "Products"); + +// Or use the default section "Products" +String response = constructor.deleteSortOption("price", SortOption.SortOrder.ascending); + +// To delete multiple sort options at once +SortOption[] sortOptions = new SortOption[] { + new SortOption("price", SortOption.SortOrder.ascending), + new SortOption("relevance", SortOption.SortOrder.descending) +}; +String response = constructor.deleteSortOptions(sortOptions, "Products"); +``` + +## Retrieving Sort Options + +To retrieve all sort options for a section, you can use the `retrieveSortOptions` method. You can optionally filter by a specific `sortBy` field and control pagination. + +```java +// Retrieve all sort options for the default "Products" section +SortOptionsResponse response = constructor.retrieveSortOptions(); + +// Retrieve sort options filtered by sortBy field +SortOptionsResponse response = constructor.retrieveSortOptions("price"); + +// Advanced retrieval with pagination and filters +SortOptionGetRequest request = new SortOptionGetRequest(); +request.setSection("Products"); +request.setSortBy("price"); +request.setPage(1); +request.setResultsPerPage(20); + +SortOptionsResponse response = constructor.retrieveSortOptions(request); + +// Access the results +int totalCount = response.getTotalCount(); +List sortOptions = response.getSortOptions(); + +for (SortOption option : sortOptions) { + System.out.println(option.getDisplayName() + " - " + option.getSortBy() + " " + option.getSortOrder()); +} +``` + # Testing Download the repository and run the following commands from `./constructorio-client` diff --git a/constructorio-client/src/main/java/io/constructor/client/ConstructorIO.java b/constructorio-client/src/main/java/io/constructor/client/ConstructorIO.java index b71d8cba..8b6aec5a 100644 --- a/constructorio-client/src/main/java/io/constructor/client/ConstructorIO.java +++ b/constructorio-client/src/main/java/io/constructor/client/ConstructorIO.java @@ -2,6 +2,7 @@ import com.google.gson.Gson; import io.constructor.client.models.*; +import io.constructor.client.models.SortOption.SortOrder; import java.io.File; import java.io.IOException; import java.io.UnsupportedEncodingException; @@ -114,8 +115,8 @@ public static OkHttpClient getHttpClient() { /** * Creates a constructor.io Client. * - * @param apiToken API Token, gotten from your Constructor.io Dashboard, and kept secret. + * @param apiToken API Token, gotten from your Constructor.io Dashboard, and kept secret. * @param apiKey API Key, used publicly in your in-site javascript client. * @param isHTTPS true to use HTTPS, false to use HTTP. It is highly recommended that you use * HTTPS. @@ -147,8 +148,8 @@ public ConstructorIO( /** * Creates a constructor.io Client. * - * @param apiToken API Token, gotten from your Constructor.io Dashboard, and kept secret. + * @param apiToken API Token, gotten from your Constructor.io Dashboard, and kept secret. * @param apiKey API Key, used publicly in your in-site javascript client. * @param constructorToken The token provided by Constructor to identify your company's traffic * if proxying requests for results @@ -190,10 +191,19 @@ public ConstructorIO(String apiToken, String apiKey, boolean isHTTPS, String hos /* * Creates a constructor.io Client. * - * @param apiToken API Token, gotten from your Constructor.io Dashboard, and kept secret. + * @param apiToken API Token, gotten from your Constructor.io Dashboard, and + * kept secret. + * * @param apiKey API Key, used publicly in your in-site javascript client. - * @param isHTTPS true to use HTTPS, false to use HTTP. It is highly recommended that you use HTTPS. - * @param host The host of the autocomplete service that you are using. It is recommended that you let this value be null, in which case the host defaults to the Constructor.io autocomplete service at ac.cnstrc.com. + * + * @param isHTTPS true to use HTTPS, false to use HTTP. It is highly recommended + * that you use HTTPS. + * + * @param host The host of the autocomplete service that you are using. It is + * recommended that you let this value be null, in which case the host defaults + * to the Constructor.io autocomplete service at ac.cnstrc.com. + * * @param port The port to connect to */ public ConstructorIO(String apiToken, String apiKey, boolean isHTTPS, String host, int port) { @@ -2234,7 +2244,8 @@ protected static void moveMetadataOutOfResultData(JSONArray results) { JSONObject resultData = result.getJSONObject("data"); JSONObject metadata = new JSONObject(); - // Recursive call to move unspecified properties in result variations to its metadata + // Recursive call to move unspecified properties in result variations to its + // metadata // object if (!result.isNull("variations")) { JSONArray variations = result.getJSONArray("variations"); @@ -2295,7 +2306,8 @@ protected static JSONArray transformItemsAPIV2Response(JSONArray results) { // Add metadata to result data object result.put("metadata", metadata); - // Suggested score is already at the top level but its name needs to be converted to + // Suggested score is already at the top level but its name needs to be + // converted to // camelcase if (result.has("suggested_score")) { result.put("suggestedScore", result.get("suggested_score")); @@ -3107,4 +3119,243 @@ public String deleteFacetOptionConfiguration( facetOptionConfigurationRequest.getFacetOptionConfiguration().getValue(), facetOptionConfigurationRequest.getSection()); } + + /** + * Creates a sort option + * + * @param sortOptionRequest the sort option request + * @return returns the created sort option as JSON string + * @throws ConstructorException if the request is invalid + */ + public String createSortOption(SortOptionRequest sortOptionRequest) + throws ConstructorException { + try { + HttpUrl url = this.makeUrl(Arrays.asList("v1", "sort_option")); + url = + url.newBuilder() + .addQueryParameter("section", sortOptionRequest.getSection()) + .build(); + + String params = new Gson().toJson(sortOptionRequest.getSortOption()); + RequestBody body = + RequestBody.create(params, MediaType.parse("application/json; charset=utf-8")); + Request request = this.makeAuthorizedRequestBuilder().url(url).post(body).build(); + + Response response = client.newCall(request).execute(); + + return getResponseBody(response); + } catch (Exception exception) { + throw new ConstructorException(exception); + } + } + + /** + * Updates a sort option (creates or replaces) + * + * @param sortOptionRequest the sort option request + * @return returns the updated sort option as JSON string + * @throws ConstructorException if the request is invalid + */ + public String updateSortOption(SortOptionRequest sortOptionRequest) + throws ConstructorException { + try { + HttpUrl url = + this.makeUrl( + Arrays.asList( + "v1", + "sort_option", + sortOptionRequest.getSortOption().getSortBy(), + sortOptionRequest.getSortOption().getSortOrder().toString())); + url = + url.newBuilder() + .addQueryParameter("section", sortOptionRequest.getSection()) + .build(); + + Map bodyParams = new HashMap(); + if (sortOptionRequest.getSortOption().getDisplayName() != null) { + bodyParams.put("display_name", sortOptionRequest.getSortOption().getDisplayName()); + } + if (sortOptionRequest.getSortOption().getPathInMetadata() != null) { + bodyParams.put( + "path_in_metadata", sortOptionRequest.getSortOption().getPathInMetadata()); + } + if (sortOptionRequest.getSortOption().getPosition() != null) { + bodyParams.put("position", sortOptionRequest.getSortOption().getPosition()); + } + if (sortOptionRequest.getSortOption().getHidden() != null) { + bodyParams.put("hidden", sortOptionRequest.getSortOption().getHidden()); + } + + String params = new Gson().toJson(bodyParams); + RequestBody body = + RequestBody.create(params, MediaType.parse("application/json; charset=utf-8")); + Request request = this.makeAuthorizedRequestBuilder().url(url).put(body).build(); + + Response response = client.newCall(request).execute(); + + return getResponseBody(response); + } catch (Exception exception) { + throw new ConstructorException(exception); + } + } + + /** + * Deletes sort options + * + * @param sortOptions array of sortOptions to delete. Only the sortBy and sortOrder fields are + * required + * @param section the index section to delete the sort option from + * @return returns the deleted sort options as JSON string + * @throws ConstructorException if the request is invalid + */ + public String deleteSortOptions(SortOption[] sortOptions, String section) + throws ConstructorException { + if (sortOptions == null || sortOptions.length < 1) { + throw new IllegalArgumentException("must specify sort options to detele"); + } + + try { + HttpUrl url = + this.makeUrl(Arrays.asList("v1", "sort_options")) + .newBuilder() + .addQueryParameter("section", section) + .build(); + + Map bodyParams = new HashMap(); + bodyParams.put("sort_options", Arrays.asList(sortOptions)); + + String jsonParams = new Gson().toJson(bodyParams); + RequestBody body = + RequestBody.create( + jsonParams, MediaType.parse("application/json; charset=utf-8")); + Request request = this.makeAuthorizedRequestBuilder().url(url).delete(body).build(); + + Response response = client.newCall(request).execute(); + + return getResponseBody(response); + } catch (Exception exception) { + throw new ConstructorException(exception); + } + } + + /** + * Deletes sort options with default section "Products" + * + * @param sortOptions array of sortOptions to delete. Only the sortBy and sortOrder fields are + * required + * @return returns the deleted sort options as JSON string + * @throws ConstructorException if the request is invalid + */ + public String deleteSortOptions(SortOption[] sortOptions) throws ConstructorException { + return deleteSortOptions(sortOptions, "Products"); + } + + /** + * Deletes a single sort option + * + * @param sortBy the sort by field + * @param sortOrder the sort order (ascending or descending) + * @param section the index section to delete the sort option from + * @return returns the deleted sort options as JSON string + * @throws ConstructorException if the request is invalid + */ + public String deleteSortOption(String sortBy, SortOrder sortOrder, String section) + throws ConstructorException { + if (sortBy == null || sortBy.trim().isEmpty()) { + throw new IllegalArgumentException("sortBy is required"); + } + if (sortOrder == null) { + throw new IllegalArgumentException("sortOrder is required"); + } + + return deleteSortOptions(new SortOption[] {new SortOption(sortBy, sortOrder)}, section); + } + + /** + * Deletes a single sort option with default section "Products" + * + * @param sortBy the sort by field + * @param sortOrder the sort order (ascending or descending) + * @return returns the deleted sort options as JSON string + * @throws ConstructorException if the request is invalid + */ + public String deleteSortOption(String sortBy, SortOrder sortOrder) throws ConstructorException { + return deleteSortOption(sortBy, sortOrder, "Products"); + } + + /** + * Retrieves all sort options + * + * @param sortOptionGetRequest the sort options get request + * @return returns the sort options response + * @throws ConstructorException if the request is invalid + */ + public SortOptionsResponse retrieveSortOptions(SortOptionGetRequest sortOptionGetRequest) + throws ConstructorException { + + try { + HttpUrl url = this.makeUrl(Arrays.asList("v1", "sort_options")); + HttpUrl.Builder urlBuilder = url.newBuilder(); + + String sortBy = sortOptionGetRequest.getSortBy(); + if (sortBy != null && !sortBy.trim().isEmpty()) { + urlBuilder.addQueryParameter("sort_by", sortBy); + } + + String section = sortOptionGetRequest.getSection(); + if (section != null && !section.trim().isEmpty()) { + urlBuilder.addQueryParameter("section", section); + } + + Integer page = sortOptionGetRequest.getPage(); + if (page != null && page > 0) { + urlBuilder.addQueryParameter("page", page.toString()); + } + + Integer resultsPerPage = sortOptionGetRequest.getResultsPerPage(); + if (resultsPerPage != null && resultsPerPage > 0) { + urlBuilder.addQueryParameter("num_results_per_page", resultsPerPage.toString()); + } + + Integer offset = sortOptionGetRequest.getOffset(); + if (offset != null && offset > 0 && page == null) { + urlBuilder.addQueryParameter("offset", offset.toString()); + } + + url = urlBuilder.build(); + + Request request = this.makeAuthorizedRequestBuilder().url(url).get().build(); + + Response response = clientWithRetry.newCall(request).execute(); + String body = getResponseBody(response); + + return new Gson().fromJson(body, SortOptionsResponse.class); + } catch (Exception exception) { + throw new ConstructorException(exception); + } + } + + /** + * Retrieves all sort options for a specific sortBy with default section "Products" + * + * @param sortBy optional filter by sort by field (can be null) + * @return returns the sort options response + * @throws ConstructorException if the request is invalid + */ + public SortOptionsResponse retrieveSortOptions(String sortBy) throws ConstructorException { + SortOptionGetRequest getRequestParams = new SortOptionGetRequest(); + getRequestParams.setSortBy(sortBy); + + return retrieveSortOptions(getRequestParams); + } + + /** + * Retrieves all sort options with default section "Products" and no filter + * + * @return returns the sort options response + * @throws ConstructorException if the request is invalid + */ + public SortOptionsResponse retrieveSortOptions() throws ConstructorException { + return retrieveSortOptions(new SortOptionGetRequest()); + } } diff --git a/constructorio-client/src/main/java/io/constructor/client/SortOptionGetRequest.java b/constructorio-client/src/main/java/io/constructor/client/SortOptionGetRequest.java new file mode 100644 index 00000000..8364eda9 --- /dev/null +++ b/constructorio-client/src/main/java/io/constructor/client/SortOptionGetRequest.java @@ -0,0 +1,80 @@ +package io.constructor.client; + +/** Constructor.io Sort Option Get Request */ +public class SortOptionGetRequest { + private String section; + private Integer page; + private Integer resultsPerPage; + private Integer offset; + private String sortBy; + + /** + * @param sortBy the optional filter by sortBy field (can be null) + */ + public void setSortBy(String sortBy) { + this.sortBy = sortBy; + } + + /** + * @return the optional filter by sortBy field (can be null) + */ + public String getSortBy() { + return sortBy; + } + + /** + * @param page the page of results to return + */ + public void setPage(int page) { + this.page = page; + } + + /** + * @return the page of results to return + */ + public Integer getPage() { + return page; + } + + /** + * @return the resultsPerPage + */ + public Integer getResultsPerPage() { + return resultsPerPage; + } + + /** + * @param resultsPerPage the resultsPerPage to set + */ + public void setResultsPerPage(int resultsPerPage) { + this.resultsPerPage = resultsPerPage; + } + + /** + * @param offset the offset of results to return (can't be used with page) + */ + public void setOffset(int offset) { + this.offset = offset; + } + + /** + * @return the offset of results to return + */ + public Integer getOffset() { + return offset; + } + + /** + * @return the section + */ + public String getSection() { + return section; + } + + /** + * @param section the section to set + */ + public void setSection(String section) { + this.section = section; + } +} diff --git a/constructorio-client/src/main/java/io/constructor/client/SortOptionRequest.java b/constructorio-client/src/main/java/io/constructor/client/SortOptionRequest.java new file mode 100644 index 00000000..233402ef --- /dev/null +++ b/constructorio-client/src/main/java/io/constructor/client/SortOptionRequest.java @@ -0,0 +1,64 @@ +package io.constructor.client; + +import io.constructor.client.models.SortOption; + +/** Constructor.io Sort Option Request */ +public class SortOptionRequest { + private SortOption sortOption; + private String section; + + /** + * Creates a sort option request + * + * @param sortOption the sort option to be created/updated + * @param section the section to which the sort option belongs + */ + public SortOptionRequest(SortOption sortOption, String section) { + if (sortOption == null) { + throw new IllegalArgumentException("sortOption is required"); + } + if (section == null) { + throw new IllegalArgumentException("section is required"); + } + + this.sortOption = sortOption; + this.section = section; + } + + /** + * Creates a sort option request with default section "Products" + * + * @param sortOption the sort option to be created/updated + */ + public SortOptionRequest(SortOption sortOption) { + this(sortOption, "Products"); + } + + /** + * @return the sort option + */ + public SortOption getSortOption() { + return sortOption; + } + + /** + * @param sortOption the sort option to set + */ + public void setSortOption(SortOption sortOption) { + this.sortOption = sortOption; + } + + /** + * @return the section + */ + public String getSection() { + return section; + } + + /** + * @param section the section to set + */ + public void setSection(String section) { + this.section = section; + } +} diff --git a/constructorio-client/src/main/java/io/constructor/client/models/SortOption.java b/constructorio-client/src/main/java/io/constructor/client/models/SortOption.java new file mode 100644 index 00000000..34295cc5 --- /dev/null +++ b/constructorio-client/src/main/java/io/constructor/client/models/SortOption.java @@ -0,0 +1,140 @@ +package io.constructor.client.models; + +import com.google.gson.annotations.SerializedName; + +/** Constructor.io Sort Option ... uses Gson/Reflection to load data in */ +public class SortOption { + + public enum SortOrder { + @SerializedName("ascending") + ascending, + @SerializedName("descending") + descending, + } + + @SerializedName("display_name") + private String displayName; + + @SerializedName("sort_by") + private String sortBy; + + @SerializedName("sort_order") + private SortOrder sortOrder; + + @SerializedName("path_in_metadata") + private String pathInMetadata; + + @SerializedName("position") + private Integer position; + + @SerializedName("hidden") + private Boolean hidden; + + /** No-arg constructor for Gson deserialization only */ + private SortOption() {} + + public SortOption(String sortBy, SortOrder sortOrder) { + if (sortBy == null) { + throw new IllegalArgumentException("sortBy is required"); + } + + if (sortOrder == null) { + throw new IllegalArgumentException("sortOrder is required"); + } + + this.sortBy = sortBy; + this.sortOrder = sortOrder; + } + + /** + * @return the display name + */ + public String getDisplayName() { + return displayName; + } + + /** + * @param displayName the display name to set + */ + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + /** + * @return the sort by field + */ + public String getSortBy() { + return sortBy; + } + + /** + * @param sortBy the sort by field to set + */ + public void setSortBy(String sortBy) { + if (sortBy == null) { + throw new IllegalArgumentException("sortBy is required"); + } + + this.sortBy = sortBy; + } + + /** + * @return the sort order (ascending or descending) + */ + public SortOrder getSortOrder() { + return sortOrder; + } + + /** + * @param sortOrder the sort order to set (ascending or descending) + */ + public void setSortOrder(SortOrder sortOrder) { + if (sortOrder == null) { + throw new IllegalArgumentException("sortOrder is required"); + } + + this.sortOrder = sortOrder; + } + + /** + * @return the path in metadata + */ + public String getPathInMetadata() { + return pathInMetadata; + } + + /** + * @param pathInMetadata the path in metadata to set + */ + public void setPathInMetadata(String pathInMetadata) { + this.pathInMetadata = pathInMetadata; + } + + /** + * @return the position + */ + public Integer getPosition() { + return position; + } + + /** + * @param position the position to set + */ + public void setPosition(Integer position) { + this.position = position; + } + + /** + * @return whether the sort option is hidden + */ + public Boolean getHidden() { + return hidden; + } + + /** + * @param hidden whether the sort option is hidden + */ + public void setHidden(Boolean hidden) { + this.hidden = hidden; + } +} diff --git a/constructorio-client/src/main/java/io/constructor/client/models/SortOptionsResponse.java b/constructorio-client/src/main/java/io/constructor/client/models/SortOptionsResponse.java new file mode 100644 index 00000000..b4f6b6e0 --- /dev/null +++ b/constructorio-client/src/main/java/io/constructor/client/models/SortOptionsResponse.java @@ -0,0 +1,42 @@ +package io.constructor.client.models; + +import com.google.gson.annotations.SerializedName; +import java.util.List; + +/** Constructor.io Sort Options Response ... uses Gson/Reflection to load data in */ +public class SortOptionsResponse { + + @SerializedName("total_count") + private int totalCount; + + @SerializedName("sort_options") + private List sortOptions; + + /** + * @return the total count + */ + public int getTotalCount() { + return totalCount; + } + + /** + * @param totalCount the total count to set + */ + public void setTotalCount(int totalCount) { + this.totalCount = totalCount; + } + + /** + * @return the sort options + */ + public List getSortOptions() { + return sortOptions; + } + + /** + * @param sortOptions the sort options to set + */ + public void setSortOptions(List sortOptions) { + this.sortOptions = sortOptions; + } +} diff --git a/constructorio-client/src/test/java/io/constructor/client/ConstructorIOSortOptionsTest.java b/constructorio-client/src/test/java/io/constructor/client/ConstructorIOSortOptionsTest.java new file mode 100644 index 00000000..733f836c --- /dev/null +++ b/constructorio-client/src/test/java/io/constructor/client/ConstructorIOSortOptionsTest.java @@ -0,0 +1,256 @@ +package io.constructor.client; + +import static org.junit.Assert.*; + +import io.constructor.client.models.SortOption; +import io.constructor.client.models.SortOption.SortOrder; +import io.constructor.client.models.SortOptionsResponse; +import java.util.ArrayList; +import org.json.JSONObject; +import org.junit.AfterClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +public class ConstructorIOSortOptionsTest { + + private static String token = System.getenv("TEST_API_TOKEN"); + private static String apiKey = System.getenv("TEST_CATALOG_API_KEY"); + private static ArrayList sortOptionsToCleanup = new ArrayList<>(); + + private void addSortOptionToCleanupArray(String sortBy, String sortOrder, String section) { + if (section == null) { + section = "Products"; + } + sortOptionsToCleanup.add(sortBy + "|" + sortOrder + "|" + section); + } + + private void addSortOptionToCleanupArray(String sortBy, String sortOrder) { + addSortOptionToCleanupArray(sortBy, sortOrder, "Products"); + } + + @AfterClass + public static void cleanupSortOptions() throws ConstructorException { + ConstructorIO constructor = new ConstructorIO(token, apiKey, true, null); + + for (String sortOptionKey : sortOptionsToCleanup) { + String[] parts = sortOptionKey.split("\\|"); + String sortBy = parts[0]; + String sortOrder = parts[1]; + String section = parts[2]; + + try { + constructor.deleteSortOption(sortBy, SortOrder.valueOf(sortOrder), section); + } catch (ConstructorException e) { + System.err.println( + "Warning: Failed to clean up sort option: " + sortBy + " " + sortOrder); + } + } + } + + @Rule public ExpectedException thrown = ExpectedException.none(); + + @Test + public void createSortOptionShouldReturn() throws Exception { + ConstructorIO constructor = new ConstructorIO(token, apiKey, true, null); + + SortOption sortOption = new SortOption("test_price", SortOrder.ascending); + sortOption.setDisplayName("Test Price Sort"); + sortOption.setPathInMetadata("test_price"); + sortOption.setHidden(false); + + SortOptionRequest request = new SortOptionRequest(sortOption, "Products"); + + String response = constructor.createSortOption(request); + JSONObject jsonObj = new JSONObject(response); + + assertEquals(jsonObj.get("display_name"), "Test Price Sort"); + assertEquals(jsonObj.get("sort_by"), "test_price"); + assertEquals(jsonObj.get("sort_order"), "ascending"); + assertEquals(jsonObj.get("path_in_metadata"), "test_price"); + assertEquals(jsonObj.get("hidden"), false); + + addSortOptionToCleanupArray("test_price", "ascending"); + } + + @Test(expected = ConstructorException.class) + public void testCreateSortOptionWithNullRequest() throws Exception { + ConstructorIO constructor = new ConstructorIO(token, apiKey, true, null); + constructor.createSortOption(null); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateSortOptionWithNullSortOption() throws Exception { + ConstructorIO constructor = new ConstructorIO(token, apiKey, true, null); + SortOptionRequest request = new SortOptionRequest(null, "Products"); + constructor.createSortOption(request); + } + + @Test + public void testCreateSortOptionWithDifferentSection() throws Exception { + ConstructorIO constructor = new ConstructorIO(token, apiKey, true, null); + + SortOption sortOption = new SortOption("custom_field", SortOrder.descending); + sortOption.setDisplayName("Custom Section Sort"); + sortOption.setPathInMetadata("custom_field"); + sortOption.setHidden(true); + + SortOptionRequest request = new SortOptionRequest(sortOption, "Search Suggestions"); + + String response = constructor.createSortOption(request); + JSONObject jsonObj = new JSONObject(response); + + assertEquals("custom_field", jsonObj.getString("sort_by")); + assertEquals("descending", jsonObj.getString("sort_order")); + + addSortOptionToCleanupArray("custom_field", "descending", "Search Suggestions"); + } + + @Test + public void testUpdateSortOption() throws Exception { + ConstructorIO constructor = new ConstructorIO(token, apiKey, true, null); + + // Create a sort option first + SortOption createOption = new SortOption("update_test", SortOrder.ascending); + createOption.setDisplayName("Original Name"); + createOption.setPathInMetadata("update_test"); + createOption.setHidden(false); + + constructor.createSortOption(new SortOptionRequest(createOption, "Products")); + + // Update the sort option + SortOption updateOption = new SortOption("update_test", SortOrder.ascending); + updateOption.setDisplayName("Updated Name"); + updateOption.setPathInMetadata("update_test_v2"); + updateOption.setHidden(true); + updateOption.setPosition(5); + + SortOptionRequest updateRequest = new SortOptionRequest(updateOption, "Products"); + String updateResponse = constructor.updateSortOption(updateRequest); + JSONObject jsonObj = new JSONObject(updateResponse); + + assertEquals("Updated Name", jsonObj.getString("display_name")); + assertEquals("update_test_v2", jsonObj.getString("path_in_metadata")); + assertEquals(true, jsonObj.getBoolean("hidden")); + assertEquals(5, jsonObj.getInt("position")); + + addSortOptionToCleanupArray("update_test", "ascending"); + } + + @Test + public void testDeleteSortOptionWithSortByAndSortOrder() throws Exception { + ConstructorIO constructor = new ConstructorIO(token, apiKey, true, null); + + // Create a sort option first + SortOption sortOption = new SortOption("delete_test", SortOrder.ascending); + sortOption.setDisplayName("Delete Test"); + sortOption.setPathInMetadata("delete_test"); + + constructor.createSortOption(new SortOptionRequest(sortOption, "Products")); + + // Delete the sort option + // DELETE endpoint returns 204 with no body + String deleteResponse = + constructor.deleteSortOption("delete_test", SortOrder.ascending, "Products"); + + // Verify that the response is empty (204 No Content) + assertTrue( + "DELETE should return empty response", + deleteResponse == null || deleteResponse.trim().isEmpty()); + } + + @Test + public void testDeleteSortOptionWithDefaultSection() throws Exception { + ConstructorIO constructor = new ConstructorIO(token, apiKey, true, null); + + // Create a sort option first + SortOption sortOption = new SortOption("delete_default", SortOrder.descending); + sortOption.setDisplayName("Delete Default Section"); + sortOption.setPathInMetadata("delete_default"); + + constructor.createSortOption(new SortOptionRequest(sortOption, "Products")); + + // Delete the sort option using default section + // DELETE endpoint returns 204 with no body + String deleteResponse = + constructor.deleteSortOption("delete_default", SortOrder.descending); + + // Verify that the response is empty (204 No Content) + assertTrue( + "DELETE should return empty response", + deleteResponse == null || deleteResponse.trim().isEmpty()); + } + + @Test(expected = IllegalArgumentException.class) + public void testDeleteSortOptionWithEmptySortBy() throws Exception { + ConstructorIO constructor = new ConstructorIO(token, apiKey, true, null); + constructor.deleteSortOption("", SortOrder.ascending, "Products"); + } + + @Test(expected = IllegalArgumentException.class) + public void testDeleteSortOptionWithNullSortOrder() throws Exception { + ConstructorIO constructor = new ConstructorIO(token, apiKey, true, null); + constructor.deleteSortOption("test", null, "Products"); + } + + @Test + public void testRetrieveSortOptions() throws Exception { + ConstructorIO constructor = new ConstructorIO(token, apiKey, true, null); + + SortOptionsResponse response = constructor.retrieveSortOptions(); + + assertNotNull(response); + assertNotNull(response.getSortOptions()); + assertTrue(response.getTotalCount() >= 0); + } + + @Test + public void testRetrieveSortOptionsWithSection() throws Exception { + ConstructorIO constructor = new ConstructorIO(token, apiKey, true, null); + + SortOptionGetRequest request = new SortOptionGetRequest(); + request.setSection("Products"); + + SortOptionsResponse response = constructor.retrieveSortOptions(request); + + assertNotNull(response); + assertNotNull(response.getSortOptions()); + assertTrue(response.getTotalCount() >= 0); + } + + @Test + public void testRetrieveSortOptionsWithFilter() throws Exception { + ConstructorIO constructor = new ConstructorIO(token, apiKey, true, null); + + // Create a sort option to ensure there's something to retrieve + SortOption sortOption = new SortOption("retrieve_filter_test", SortOrder.ascending); + sortOption.setDisplayName("Retrieve Filter Test"); + sortOption.setPathInMetadata("retrieve_filter_test"); + + constructor.createSortOption(new SortOptionRequest(sortOption, "Products")); + addSortOptionToCleanupArray("retrieve_filter_test", "ascending"); + + // Retrieve with filter + SortOptionsResponse response = constructor.retrieveSortOptions("retrieve_filter_test"); + + assertNotNull(response); + assertNotNull(response.getSortOptions()); + + // If there are results, verify they match the filter + if (response.getTotalCount() > 0) { + for (SortOption option : response.getSortOptions()) { + assertEquals("retrieve_filter_test", option.getSortBy()); + } + } + } + + @Test + public void testSortOptionDefaultValues() { + SortOption sortOption = new SortOption("price", SortOrder.ascending); + assertNull("Position should default to null", sortOption.getPosition()); + assertNull("Hidden should default to null", sortOption.getHidden()); + assertNull("Display name should default to null", sortOption.getDisplayName()); + assertEquals("Sort by should be set", "price", sortOption.getSortBy()); + assertEquals("Sort order should be set", SortOrder.ascending, sortOption.getSortOrder()); + } +} diff --git a/constructorio-client/src/test/java/io/constructor/client/SortOptionRequestTest.java b/constructorio-client/src/test/java/io/constructor/client/SortOptionRequestTest.java new file mode 100644 index 00000000..004873a0 --- /dev/null +++ b/constructorio-client/src/test/java/io/constructor/client/SortOptionRequestTest.java @@ -0,0 +1,69 @@ +package io.constructor.client; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import io.constructor.client.models.SortOption; +import io.constructor.client.models.SortOption.SortOrder; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +public class SortOptionRequestTest { + @Rule public ExpectedException thrown = ExpectedException.none(); + + @Test + public void newSortOptionRequestShouldReturnWithDefaultSection() { + SortOption sortOption = new SortOption("price", SortOrder.ascending); + sortOption.setDisplayName("Price"); + + SortOptionRequest request = new SortOptionRequest(sortOption); + assertNotNull(request); + assertEquals(request.getSection(), "Products"); + assertEquals(request.getSortOption(), sortOption); + } + + @Test + public void newSortOptionRequestShouldReturnWithCustomSection() { + SortOption sortOption = new SortOption("price", SortOrder.ascending); + sortOption.setDisplayName("Price"); + + SortOptionRequest request = new SortOptionRequest(sortOption, "Search Suggestions"); + assertNotNull(request); + assertEquals(request.getSection(), "Search Suggestions"); + assertEquals(request.getSortOption(), sortOption); + } + + @Test + public void sortOptionRequestSettersWork() { + SortOption sortOption1 = new SortOption("price", SortOrder.ascending); + sortOption1.setDisplayName("Price"); + + SortOption sortOption2 = new SortOption("name", SortOrder.descending); + sortOption2.setDisplayName("Name"); + + SortOptionRequest request = new SortOptionRequest(sortOption1); + request.setSortOption(sortOption2); + request.setSection("Custom Section"); + + assertEquals(request.getSortOption(), sortOption2); + assertEquals(request.getSection(), "Custom Section"); + } + + @Test + public void newSortOptionRequestShouldThrowExceptionWithNullSortOption() { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("sortOption is required"); + new SortOptionRequest(null); + } + + @Test + public void newSortOptionRequestShouldThrowExceptionWithNullSection() { + SortOption sortOption = new SortOption("price", SortOrder.ascending); + sortOption.setDisplayName("Price"); + + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("section is required"); + new SortOptionRequest(sortOption, null); + } +} diff --git a/constructorio-client/src/test/java/io/constructor/client/SortOptionTest.java b/constructorio-client/src/test/java/io/constructor/client/SortOptionTest.java new file mode 100644 index 00000000..94f8be93 --- /dev/null +++ b/constructorio-client/src/test/java/io/constructor/client/SortOptionTest.java @@ -0,0 +1,84 @@ +package io.constructor.client; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import com.google.gson.Gson; +import io.constructor.client.models.SortOption; +import io.constructor.client.models.SortOption.SortOrder; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +public class SortOptionTest { + @Rule public ExpectedException thrown = ExpectedException.none(); + + @Test + public void sortOptionDeserialization() throws Exception { + String json = + "{" + + "\"display_name\": \"Price\"," + + "\"path_in_metadata\": \"price_min\"," + + "\"position\": null," + + "\"hidden\": true," + + "\"sort_by\": \"price\"," + + "\"sort_order\": \"ascending\"" + + "}"; + + SortOption sortOption = new Gson().fromJson(json, SortOption.class); + assertEquals(sortOption.getDisplayName(), "Price"); + assertEquals(sortOption.getPathInMetadata(), "price_min"); + assertNull(sortOption.getPosition()); + assertEquals(sortOption.getHidden(), Boolean.TRUE); + assertEquals(sortOption.getSortBy(), "price"); + assertEquals(sortOption.getSortOrder(), SortOrder.ascending); + } + + @Test + public void sortOptionSerialization() throws Exception { + SortOption sortOption = new SortOption("price", SortOrder.descending); + sortOption.setDisplayName("Price"); + sortOption.setPathInMetadata("price_min"); + sortOption.setPosition(1); + sortOption.setHidden(true); + + String json = new Gson().toJson(sortOption); + + // Verify the JSON contains the expected fields + SortOption deserialized = new Gson().fromJson(json, SortOption.class); + assertEquals(deserialized.getDisplayName(), "Price"); + assertEquals(deserialized.getPathInMetadata(), "price_min"); + assertEquals(deserialized.getPosition(), Integer.valueOf(1)); + assertEquals(deserialized.getHidden(), Boolean.TRUE); + assertEquals(deserialized.getSortBy(), "price"); + assertEquals(deserialized.getSortOrder(), SortOrder.descending); + } + + @Test + public void sortOptionDefaultValues() { + SortOption sortOption = new SortOption("price", SortOrder.ascending); + assertNull("Display name should default to null", sortOption.getDisplayName()); + assertNull("Path in metadata should default to null", sortOption.getPathInMetadata()); + assertNull("Position should default to null", sortOption.getPosition()); + assertNull("Hidden should default to null", sortOption.getHidden()); + assertEquals("Sort by should be set", "price", sortOption.getSortBy()); + assertEquals("Sort order should be set", SortOrder.ascending, sortOption.getSortOrder()); + } + + @Test + public void sortOptionWithNullPosition() throws Exception { + String json = + "{" + + "\"display_name\": \"Name\"," + + "\"sort_by\": \"name\"," + + "\"sort_order\": \"ascending\"," + + "\"position\": null," + + "\"hidden\": false" + + "}"; + + SortOption sortOption = new Gson().fromJson(json, SortOption.class); + assertEquals(sortOption.getDisplayName(), "Name"); + assertNull(sortOption.getPosition()); + assertEquals(sortOption.getHidden(), Boolean.FALSE); + } +} diff --git a/constructorio-client/src/test/java/io/constructor/client/SortOptionsResponseTest.java b/constructorio-client/src/test/java/io/constructor/client/SortOptionsResponseTest.java new file mode 100644 index 00000000..5c6ea684 --- /dev/null +++ b/constructorio-client/src/test/java/io/constructor/client/SortOptionsResponseTest.java @@ -0,0 +1,94 @@ +package io.constructor.client; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import com.google.gson.Gson; +import io.constructor.client.models.SortOption; +import io.constructor.client.models.SortOptionsResponse; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +public class SortOptionsResponseTest { + @Rule public ExpectedException thrown = ExpectedException.none(); + + @Test + public void sortOptionsResponseDeserialization() throws Exception { + String json = + "{" + + "\"total_count\": 2," + + "\"sort_options\": [" + + " {" + + " \"display_name\": \"Price\"," + + " \"path_in_metadata\": \"price_min\"," + + " \"position\": null," + + " \"hidden\": true," + + " \"sort_by\": \"price\"," + + " \"sort_order\": \"ascending\"" + + " }," + + " {" + + " \"display_name\": \"Price\"," + + " \"path_in_metadata\": \"price_min\"," + + " \"position\": 1," + + " \"hidden\": false," + + " \"sort_by\": \"price\"," + + " \"sort_order\": \"descending\"" + + " }" + + "]" + + "}"; + + SortOptionsResponse response = new Gson().fromJson(json, SortOptionsResponse.class); + assertNotNull(response); + assertEquals(response.getTotalCount(), 2); + assertNotNull(response.getSortOptions()); + assertEquals(response.getSortOptions().size(), 2); + + // Check first sort option + SortOption first = response.getSortOptions().get(0); + assertEquals(first.getDisplayName(), "Price"); + assertEquals(first.getPathInMetadata(), "price_min"); + assertNull(first.getPosition()); + assertEquals(first.getHidden(), Boolean.TRUE); + assertEquals(first.getSortBy(), "price"); + assertEquals(first.getSortOrder(), SortOption.SortOrder.ascending); + + // Check second sort option + SortOption second = response.getSortOptions().get(1); + assertEquals(second.getDisplayName(), "Price"); + assertEquals(second.getPathInMetadata(), "price_min"); + assertEquals(second.getPosition(), Integer.valueOf(1)); + assertEquals(second.getHidden(), Boolean.FALSE); + assertEquals(second.getSortBy(), "price"); + assertEquals(second.getSortOrder(), SortOption.SortOrder.descending); + } + + @Test + public void sortOptionsResponseWithEmptyList() throws Exception { + String json = "{" + "\"total_count\": 0," + "\"sort_options\": []" + "}"; + + SortOptionsResponse response = new Gson().fromJson(json, SortOptionsResponse.class); + assertNotNull(response); + assertEquals(response.getTotalCount(), 0); + assertNotNull(response.getSortOptions()); + assertEquals(response.getSortOptions().size(), 0); + } + + @Test + public void sortOptionsResponseSettersWork() { + SortOptionsResponse response = new SortOptionsResponse(); + response.setTotalCount(5); + + java.util.ArrayList sortOptions = new java.util.ArrayList(); + SortOption sortOption = new SortOption("price", SortOption.SortOrder.ascending); + sortOption.setDisplayName("Test"); + sortOptions.add(sortOption); + + response.setSortOptions(sortOptions); + + assertEquals(response.getTotalCount(), 5); + assertEquals(response.getSortOptions().size(), 1); + assertEquals(response.getSortOptions().get(0).getDisplayName(), "Test"); + } +}