From d27c92026076cb08fd71cbca1fb4832b55c05cb0 Mon Sep 17 00:00:00 2001 From: mudaafi Date: Fri, 17 Apr 2026 02:55:24 +0800 Subject: [PATCH 1/5] add feature --- .../io/constructor/client/CatalogRequest.java | 43 +- .../io/constructor/client/ConstructorIO.java | 285 ++++++----- .../client/CatalogRequestTest.java | 51 ++ .../ConstructorIONotificationEmailTest.java | 441 ++++++++++++++++++ 4 files changed, 699 insertions(+), 121 deletions(-) create mode 100644 constructorio-client/src/test/java/io/constructor/client/ConstructorIONotificationEmailTest.java diff --git a/constructorio-client/src/main/java/io/constructor/client/CatalogRequest.java b/constructorio-client/src/main/java/io/constructor/client/CatalogRequest.java index a91c1ce4..0dcc35b0 100644 --- a/constructorio-client/src/main/java/io/constructor/client/CatalogRequest.java +++ b/constructorio-client/src/main/java/io/constructor/client/CatalogRequest.java @@ -1,6 +1,8 @@ package io.constructor.client; import java.io.File; +import java.util.ArrayList; +import java.util.List; import java.util.Map; /** Constructor.io Catalog Request */ @@ -13,7 +15,7 @@ public enum OnMissing { private Map files; private String section; - private String notificationEmail; + private List notificationEmails; private OnMissing onMissing; private Boolean force; @@ -34,6 +36,7 @@ public CatalogRequest(Map files, String section) { this.files = files; this.section = section; this.onMissing = OnMissing.FAIL; + this.notificationEmails = new ArrayList(); } /** @@ -82,18 +85,46 @@ public String getSection() { } /** - * @param email the email address where you'd like to receive a notifcation in case the task - * fails + * @param email the email address to receive a notification in case the task fails */ public void setNotificationEmail(String email) { - this.notificationEmail = email; + this.notificationEmails.clear(); + if (email != null) { + this.notificationEmails.add(email); + } } /** - * @return the notification email + * @param emails list of email addresses where you'd like to receive notifications in case the + * task fails + */ + public void setNotificationEmail(List emails) { + this.notificationEmails.clear(); + if (emails != null) { + this.notificationEmails.addAll(emails); + } + } + + /** + * @return the first notification email, or null if none set + * @deprecated Use {@link #getNotificationEmails()} instead to support multiple emails. */ + @Deprecated public String getNotificationEmail() { - return notificationEmail; + if (notificationEmails != null && !notificationEmails.isEmpty()) { + return notificationEmails.get(0); + } + return null; + } + + /** + * @return the list of notification emails, or null if none set + */ + public List getNotificationEmails() { + if (notificationEmails.isEmpty()) { + return null; + } + return notificationEmails; } /** 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 210a5c19..5031439b 100644 --- a/constructorio-client/src/main/java/io/constructor/client/ConstructorIO.java +++ b/constructorio-client/src/main/java/io/constructor/client/ConstructorIO.java @@ -242,13 +242,13 @@ public boolean verify() throws ConstructorException { * @param section the section of the index that you're adding the items to. * @param force whether or not the system should process the request even if it will invalidate * a large number of existing items. - * @param notificationEmail An email address where you'd like to receive an email notification - * in case the task fails. + * @param notificationEmails A list of email addresses to receive an email notification if the + * task fails. * @return a string of JSON * @throws ConstructorException if the request is invalid. */ public String createOrReplaceItems( - ConstructorItem[] items, String section, Boolean force, String notificationEmail) + ConstructorItem[] items, String section, Boolean force, List notificationEmails) throws ConstructorException { try { HttpUrl url = this.makeUrl(Arrays.asList("v2", "items")); @@ -258,11 +258,12 @@ public String createOrReplaceItems( .addQueryParameter("section", section) .build(); - if (notificationEmail != null) { - url = - url.newBuilder() - .addQueryParameter("notification_email", notificationEmail) - .build(); + if (notificationEmails != null) { + HttpUrl.Builder emailBuilder = url.newBuilder(); + for (String email : notificationEmails) { + emailBuilder.addQueryParameter("notification_email", email); + } + url = emailBuilder.build(); } Map data = new HashMap(); @@ -284,33 +285,40 @@ public String createOrReplaceItems( } public String createOrReplaceItems(ConstructorItem[] items) throws ConstructorException { - return createOrReplaceItems(items, "Products", false, null); + return createOrReplaceItems(items, "Products", false, (List) null); } public String createOrReplaceItems(ConstructorItem[] items, String section) throws ConstructorException { - return createOrReplaceItems(items, section, false, null); + return createOrReplaceItems(items, section, false, (List) null); } public String createOrReplaceItems(ConstructorItem[] items, String section, Boolean force) throws ConstructorException { - return createOrReplaceItems(items, section, force, null); + return createOrReplaceItems(items, section, force, (List) null); + } + + public String createOrReplaceItems( + ConstructorItem[] items, String section, Boolean force, String notificationEmail) + throws ConstructorException { + List emails = notificationEmail != null ? Arrays.asList(notificationEmail) : null; + return createOrReplaceItems(items, section, force, emails); } /** - * Deleted multiple items from your index (limit of 1,000 items) + * Deletes multiple items from your index (limit of 1,000 items) * * @param items the items that you are deleting * @param section the section of the index that you're removing the items from. * @param force whether or not the system should process the request even if it will invalidate * a large number of existing items. - * @param notificationEmail An email address where you'd like to receive an email notification - * in case the task fails. + * @param notificationEmails A list of email addresses to receive an email notification if the + * task fails. * @return a string of JSON * @throws ConstructorException if the request is invalid */ public String deleteItems( - ConstructorItem[] items, String section, Boolean force, String notificationEmail) + ConstructorItem[] items, String section, Boolean force, List notificationEmails) throws ConstructorException { try { HttpUrl url = this.makeUrl(Arrays.asList("v2", "items")); @@ -320,11 +328,12 @@ public String deleteItems( .addQueryParameter("section", section) .build(); - if (notificationEmail != null) { - url = - url.newBuilder() - .addQueryParameter("notification_email", notificationEmail) - .build(); + if (notificationEmails != null) { + HttpUrl.Builder emailBuilder = url.newBuilder(); + for (String email : notificationEmails) { + emailBuilder.addQueryParameter("notification_email", email); + } + url = emailBuilder.build(); } Map data = new HashMap(); @@ -350,16 +359,23 @@ public String deleteItems( } public String deleteItems(ConstructorItem[] items) throws ConstructorException { - return deleteItems(items, "Products", false, null); + return deleteItems(items, "Products", false, (List) null); } public String deleteItems(ConstructorItem[] items, String section) throws ConstructorException { - return deleteItems(items, section, false, null); + return deleteItems(items, section, false, (List) null); } public String deleteItems(ConstructorItem[] items, String section, Boolean force) throws ConstructorException { - return deleteItems(items, section, force, null); + return deleteItems(items, section, force, (List) null); + } + + public String deleteItems( + ConstructorItem[] items, String section, Boolean force, String notificationEmail) + throws ConstructorException { + List emails = notificationEmail != null ? Arrays.asList(notificationEmail) : null; + return deleteItems(items, section, force, emails); } /** @@ -435,8 +451,8 @@ public String retrieveItemsAsJson(ItemsRequest req) throws ConstructorException * @param section the section of the autocomplete that you're removing the items from. * @param force whether or not the system should process the request even if it will invalidate * a large number of existing variations. - * @param notificationEmail An email address where you'd like to receive an email notification - * in case the task fails. + * @param notificationEmails A list of email addresses to receive an email notification if the + * task fails. * @return a string of JSON * @throws ConstructorException if the request is invalid */ @@ -444,7 +460,7 @@ public String deleteVariations( ConstructorVariation[] variations, String section, Boolean force, - String notificationEmail) + List notificationEmails) throws ConstructorException { try { HttpUrl url = this.makeUrl(Arrays.asList("v2", "variations")); @@ -454,11 +470,12 @@ public String deleteVariations( .addQueryParameter("section", section) .build(); - if (notificationEmail != null) { - url = - url.newBuilder() - .addQueryParameter("notification_email", notificationEmail) - .build(); + if (notificationEmails != null) { + HttpUrl.Builder emailBuilder = url.newBuilder(); + for (String email : notificationEmails) { + emailBuilder.addQueryParameter("notification_email", email); + } + url = emailBuilder.build(); } Map data = new HashMap(); @@ -483,17 +500,27 @@ public String deleteVariations( } public String deleteVariations(ConstructorVariation[] variations) throws ConstructorException { - return deleteVariations(variations, "Products", false, null); + return deleteVariations(variations, "Products", false, (List) null); } public String deleteVariations(ConstructorVariation[] variations, String section) throws ConstructorException { - return deleteVariations(variations, section, false, null); + return deleteVariations(variations, section, false, (List) null); } public String deleteVariations(ConstructorVariation[] variations, String section, Boolean force) throws ConstructorException { - return deleteVariations(variations, section, force, null); + return deleteVariations(variations, section, force, (List) null); + } + + public String deleteVariations( + ConstructorVariation[] variations, + String section, + Boolean force, + String notificationEmail) + throws ConstructorException { + List emails = notificationEmail != null ? Arrays.asList(notificationEmail) : null; + return deleteVariations(variations, section, force, emails); } /** @@ -503,12 +530,12 @@ public String deleteVariations(ConstructorVariation[] variations, String section * @param section the section of the autocomplete that you're modifying the item for. * @param force whether or not the system should process the request even if it will invalidate * a large number of existing items. + * @param notificationEmails A list of email addresses to receive an email notification if the + * task fails. * @param onMissing Either "FAIL", "IGNORE", "CREATE", indicating how the system will handle * updating items that don't exist. "FAIL" fails the ingestion if there are items that don't * exist. "IGNORE" ignores items that don't exist. "CREATE" creates items that don't exist. * Defaults to "FAIL". - * @param notificationEmail An email address where you'd like to receive an email notification - * in case the task fails. * @return a string of JSON * @throws ConstructorException if the request is invalid. */ @@ -516,7 +543,7 @@ public String updateItems( ConstructorItem[] items, String section, Boolean force, - String notificationEmail, + List notificationEmails, CatalogRequest.OnMissing onMissing) throws ConstructorException { try { @@ -527,11 +554,12 @@ public String updateItems( .addQueryParameter("section", section) .build(); - if (notificationEmail != null) { - url = - url.newBuilder() - .addQueryParameter("notification_email", notificationEmail) - .build(); + if (notificationEmails != null) { + HttpUrl.Builder emailBuilder = url.newBuilder(); + for (String email : notificationEmails) { + emailBuilder.addQueryParameter("notification_email", email); + } + url = emailBuilder.build(); } if (onMissing != null && onMissing != CatalogRequest.OnMissing.FAIL) { @@ -556,35 +584,40 @@ public String updateItems( } } - /** - * Updates items from your index. - * - * @param items the items that you're updating - * @param section the section of the autocomplete that you're modifying the item for. - * @param force whether or not the system should process the request even if it will invalidate - * a large number of existing items. - * @param notificationEmail An email address where you'd like to receive an email notification - * in case the task fails. - * @return a string of JSON - * @throws ConstructorException if the request is invalid. - */ + public String updateItems(ConstructorItem[] items) throws ConstructorException { + return updateItems(items, "Products", false, (List) null, null); + } + + public String updateItems(ConstructorItem[] items, String section) throws ConstructorException { + return updateItems(items, section, false, (List) null, null); + } + + public String updateItems(ConstructorItem[] items, String section, Boolean force) + throws ConstructorException { + return updateItems(items, section, force, (List) null, null); + } + public String updateItems( ConstructorItem[] items, String section, Boolean force, String notificationEmail) throws ConstructorException { return updateItems(items, section, force, notificationEmail, null); } - public String updateItems(ConstructorItem[] items) throws ConstructorException { - return updateItems(items, "Products", false, null, null); - } - - public String updateItems(ConstructorItem[] items, String section) throws ConstructorException { - return updateItems(items, section, false, null, null); + public String updateItems( + ConstructorItem[] items, String section, Boolean force, List notificationEmails) + throws ConstructorException { + return updateItems(items, section, force, notificationEmails, null); } - public String updateItems(ConstructorItem[] items, String section, Boolean force) + public String updateItems( + ConstructorItem[] items, + String section, + Boolean force, + String notificationEmail, + CatalogRequest.OnMissing onMissing) throws ConstructorException { - return updateItems(items, section, force, null, null); + List emails = notificationEmail != null ? Arrays.asList(notificationEmail) : null; + return updateItems(items, section, force, emails, onMissing); } /** @@ -594,8 +627,8 @@ public String updateItems(ConstructorItem[] items, String section, Boolean force * @param section the section of the autocomplete that you're modifying the item for. * @param force whether or not the system should process the request even if it will invalidate * a large number of existing variations. - * @param notificationEmail An email address where you'd like to receive an email notification - * in case the task fails. + * @param notificationEmails A list of email addresses to receive an email notification if the + * task fails. * @param onMissing Either "FAIL", "IGNORE", "CREATE", indicating how the system will handle * updating variations that don't exist. "FAIL" fails the ingestion if there are items that * don't exist. "IGNORE" ignores variations that don't exist. "CREATE" creates items that @@ -607,7 +640,7 @@ public String updateVariations( ConstructorVariation[] variations, String section, Boolean force, - String notificationEmail, + List notificationEmails, CatalogRequest.OnMissing onMissing) throws ConstructorException { try { @@ -618,11 +651,12 @@ public String updateVariations( .addQueryParameter("section", section) .build(); - if (notificationEmail != null) { - url = - url.newBuilder() - .addQueryParameter("notification_email", notificationEmail) - .build(); + if (notificationEmails != null) { + HttpUrl.Builder emailBuilder = url.newBuilder(); + for (String email : notificationEmails) { + emailBuilder.addQueryParameter("notification_email", email); + } + url = emailBuilder.build(); } if (onMissing != null && onMissing != CatalogRequest.OnMissing.FAIL) { url = url.newBuilder().addQueryParameter("on_missing", onMissing.name()).build(); @@ -646,43 +680,47 @@ public String updateVariations( } } - /** - * Update variations from your index. - * - * @param variations the variations that you're updating. - * @param section the section of the autocomplete that you're modifying the item for. - * @param force whether or not the system should process the request even if it will invalidate - * a large number of existing variations. - * @param notificationEmail An email address where you'd like to receive an email notification - * in case the task fails. - * @return a string of JSON - * @throws ConstructorException if the request is invalid. - */ + public String updateVariations(ConstructorVariation[] variations) throws ConstructorException { + return updateVariations(variations, "Products", false, (List) null, null); + } + + public String updateVariations(ConstructorVariation[] variations, String section) + throws ConstructorException { + return updateVariations(variations, section, false, (List) null, null); + } + + public String updateVariations(ConstructorVariation[] variations, String section, Boolean force) + throws ConstructorException { + return updateVariations(variations, section, force, (List) null, null); + } + public String updateVariations( ConstructorVariation[] variations, String section, Boolean force, String notificationEmail) throws ConstructorException { - try { - return updateVariations(variations, section, force, notificationEmail, null); - } catch (Exception exception) { - throw new ConstructorException(exception); - } - } - - public String updateVariations(ConstructorVariation[] variations) throws ConstructorException { - return updateVariations(variations, "Products", false, null, null); + return updateVariations(variations, section, force, notificationEmail, null); } - public String updateVariations(ConstructorVariation[] variations, String section) + public String updateVariations( + ConstructorVariation[] variations, + String section, + Boolean force, + List notificationEmails) throws ConstructorException { - return updateVariations(variations, section, false, null, null); + return updateVariations(variations, section, force, notificationEmails, null); } - public String updateVariations(ConstructorVariation[] variations, String section, Boolean force) + public String updateVariations( + ConstructorVariation[] variations, + String section, + Boolean force, + String notificationEmail, + CatalogRequest.OnMissing onMissing) throws ConstructorException { - return updateVariations(variations, section, force, null, null); + List emails = notificationEmail != null ? Arrays.asList(notificationEmail) : null; + return updateVariations(variations, section, force, emails, onMissing); } /** @@ -692,8 +730,8 @@ public String updateVariations(ConstructorVariation[] variations, String section * @param section the section of the autocomplete that you're adding the items to. * @param force whether or not the system should process the request even if it will invalidate * a large number of existing variations. - * @param notificationEmail An email address where you'd like to receive an email notification - * in case the task fails. + * @param notificationEmails A list of email addresses to receive an email notification if the + * notification in case the task fails. * @return a string of JSON * @throws ConstructorException if the request is invalid. */ @@ -701,7 +739,7 @@ public String createOrReplaceVariations( ConstructorVariation[] variations, String section, Boolean force, - String notificationEmail) + List notificationEmails) throws ConstructorException { try { HttpUrl url = this.makeUrl(Arrays.asList("v2", "variations")); @@ -711,11 +749,12 @@ public String createOrReplaceVariations( .addQueryParameter("section", section) .build(); - if (notificationEmail != null) { - url = - url.newBuilder() - .addQueryParameter("notification_email", notificationEmail) - .build(); + if (notificationEmails != null) { + HttpUrl.Builder emailBuilder = url.newBuilder(); + for (String email : notificationEmails) { + emailBuilder.addQueryParameter("notification_email", email); + } + url = emailBuilder.build(); } Map data = new HashMap(); @@ -738,18 +777,28 @@ public String createOrReplaceVariations( public String createOrReplaceVariations(ConstructorVariation[] variations) throws ConstructorException { - return createOrReplaceVariations(variations, "Products", false, null); + return createOrReplaceVariations(variations, "Products", false, (List) null); } public String createOrReplaceVariations(ConstructorVariation[] variations, String section) throws ConstructorException { - return createOrReplaceVariations(variations, section, false, null); + return createOrReplaceVariations(variations, section, false, (List) null); } public String createOrReplaceVariations( ConstructorVariation[] variations, String section, Boolean force) throws ConstructorException { - return createOrReplaceVariations(variations, section, force, null); + return createOrReplaceVariations(variations, section, force, (List) null); + } + + public String createOrReplaceVariations( + ConstructorVariation[] variations, + String section, + Boolean force, + String notificationEmail) + throws ConstructorException { + List emails = notificationEmail != null ? Arrays.asList(notificationEmail) : null; + return createOrReplaceVariations(variations, section, force, emails); } /** @@ -2331,12 +2380,14 @@ public String replaceCatalog(CatalogRequest req) throws ConstructorException { url.newBuilder().addQueryParameter("section", req.getSection()); MultipartBody.Builder multipartBuilder = new MultipartBody.Builder().setType(MultipartBody.FORM); - String notificationEmail = req.getNotificationEmail(); + List notificationEmails = req.getNotificationEmails(); Boolean force = req.getForce(); Map files = req.getFiles(); - if (notificationEmail != null) { - urlBuilder.addQueryParameter("notification_email", notificationEmail); + if (notificationEmails != null) { + for (String email : notificationEmails) { + urlBuilder.addQueryParameter("notification_email", email); + } } if (force != null) { urlBuilder.addQueryParameter("force", Boolean.toString(force)); @@ -2388,12 +2439,14 @@ public String updateCatalog(CatalogRequest req) throws ConstructorException { url.newBuilder().addQueryParameter("section", req.getSection()); MultipartBody.Builder multipartBuilder = new MultipartBody.Builder().setType(MultipartBody.FORM); - String notificationEmail = req.getNotificationEmail(); + List notificationEmails = req.getNotificationEmails(); Boolean force = req.getForce(); Map files = req.getFiles(); - if (notificationEmail != null) { - urlBuilder.addQueryParameter("notification_email", notificationEmail); + if (notificationEmails != null) { + for (String email : notificationEmails) { + urlBuilder.addQueryParameter("notification_email", email); + } } if (force != null) { urlBuilder.addQueryParameter("force", Boolean.toString(force)); @@ -2446,12 +2499,14 @@ public String patchCatalog(CatalogRequest req) throws ConstructorException { url.newBuilder().addQueryParameter("section", req.getSection()); MultipartBody.Builder multipartBuilder = new MultipartBody.Builder().setType(MultipartBody.FORM); - String notificationEmail = req.getNotificationEmail(); + List notificationEmails = req.getNotificationEmails(); Boolean force = req.getForce(); Map files = req.getFiles(); - if (notificationEmail != null) { - urlBuilder.addQueryParameter("notification_email", notificationEmail); + if (notificationEmails != null) { + for (String email : notificationEmails) { + urlBuilder.addQueryParameter("notification_email", email); + } } if (force != null) { urlBuilder.addQueryParameter("force", Boolean.toString(force)); diff --git a/constructorio-client/src/test/java/io/constructor/client/CatalogRequestTest.java b/constructorio-client/src/test/java/io/constructor/client/CatalogRequestTest.java index 01f145ff..c4383593 100644 --- a/constructorio-client/src/test/java/io/constructor/client/CatalogRequestTest.java +++ b/constructorio-client/src/test/java/io/constructor/client/CatalogRequestTest.java @@ -1,9 +1,12 @@ package io.constructor.client; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import java.io.File; +import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.junit.Rule; import org.junit.Test; @@ -45,6 +48,16 @@ public void newShouldReturnDefaultProperties() throws Exception { assertEquals(request.getSection(), "Products"); } + @Test + public void getNotificationEmailShouldReturnNullByDefault() throws Exception { + Map files = new HashMap(); + files.put("items", new File("src/test/resources/csv/items.csv")); + CatalogRequest request = new CatalogRequest(files, "Products"); + + assertNull(request.getNotificationEmail()); + assertNull(request.getNotificationEmails()); + } + @Test public void settersShouldSet() throws Exception { Map files = new HashMap(); @@ -65,4 +78,42 @@ public void settersShouldSet() throws Exception { assertEquals(request.getNotificationEmail(), "test@constructor.io"); assertEquals(request.getForce(), true); } + + @Test + public void setNotificationEmailShouldSet() throws Exception { + Map files = new HashMap(); + files.put("items", new File("src/test/resources/csv/items.csv")); + CatalogRequest request = new CatalogRequest(files, "Products"); + + List emails = Arrays.asList("a@constructor.io", "b@constructor.io"); + request.setNotificationEmail(emails); + + assertEquals(request.getNotificationEmails(), emails); + assertEquals(request.getNotificationEmail(), "a@constructor.io"); + } + + @Test + public void setNotificationEmailShouldSetListWithOneElement() throws Exception { + Map files = new HashMap(); + files.put("items", new File("src/test/resources/csv/items.csv")); + CatalogRequest request = new CatalogRequest(files, "Products"); + + request.setNotificationEmail("test@constructor.io"); + + assertEquals(request.getNotificationEmails(), Arrays.asList("test@constructor.io")); + assertEquals(request.getNotificationEmail(), "test@constructor.io"); + } + + @Test + public void setNotificationEmailNullShouldClearList() throws Exception { + Map files = new HashMap(); + files.put("items", new File("src/test/resources/csv/items.csv")); + CatalogRequest request = new CatalogRequest(files, "Products"); + + request.setNotificationEmail("test@constructor.io"); + request.setNotificationEmail((String) null); + + assertNull(request.getNotificationEmails()); + assertNull(request.getNotificationEmail()); + } } diff --git a/constructorio-client/src/test/java/io/constructor/client/ConstructorIONotificationEmailTest.java b/constructorio-client/src/test/java/io/constructor/client/ConstructorIONotificationEmailTest.java new file mode 100644 index 00000000..e82e26bf --- /dev/null +++ b/constructorio-client/src/test/java/io/constructor/client/ConstructorIONotificationEmailTest.java @@ -0,0 +1,441 @@ +package io.constructor.client; + +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.FileWriter; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; + +public class ConstructorIONotificationEmailTest { + + private static MockWebServer mockServer; + private String apiKey = "test-api-key"; + private static final String MOCK_RESPONSE_BODY = + "{\"task_id\": 1, \"task_status_path\": \"/task/1\"}"; + + @Rule public ExpectedException thrown = ExpectedException.none(); + @Rule public TemporaryFolder tempFolder = new TemporaryFolder(); + + @BeforeClass + public static void setup() throws Exception { + mockServer = new MockWebServer(); + mockServer.start(); + } + + @AfterClass + public static void teardown() throws Exception { + mockServer.shutdown(); + } + + private ConstructorIO createConstructorIO() { + return new ConstructorIO("test-token", apiKey, false, "127.0.0.1", mockServer.getPort()); + } + + private int countOccurrences(String str, String sub) { + int count = 0; + int idx = 0; + while ((idx = str.indexOf(sub, idx)) != -1) { + count++; + idx += sub.length(); + } + return count; + } + + // --- createOrReplaceItems --- + + @Test + public void createOrReplaceItemsWithSingleEmailShouldIncludeEmailInUrl() throws Exception { + mockServer.enqueue(new MockResponse().setResponseCode(200).setBody(MOCK_RESPONSE_BODY)); + ConstructorIO constructor = createConstructorIO(); + ConstructorItem[] items = {Utils.createProductItem()}; + + constructor.createOrReplaceItems(items, "Products", false, "test@constructor.io"); + + RecordedRequest request = mockServer.takeRequest(); + String path = request.getPath(); + assertTrue( + "URL should contain notification_email", + path.contains("notification_email=test%40constructor.io")); + } + + @Test + public void createOrReplaceItemsWithMultipleEmailsShouldIncludeAllEmailsInUrl() + throws Exception { + mockServer.enqueue(new MockResponse().setResponseCode(200).setBody(MOCK_RESPONSE_BODY)); + ConstructorIO constructor = createConstructorIO(); + ConstructorItem[] items = {Utils.createProductItem()}; + List emails = Arrays.asList("a@constructor.io", "b@constructor.io"); + + constructor.createOrReplaceItems(items, "Products", false, emails); + + RecordedRequest request = mockServer.takeRequest(); + String path = request.getPath(); + assertTrue( + "URL should contain first notification_email", + path.contains("notification_email=a%40constructor.io")); + assertTrue( + "URL should contain second notification_email", + path.contains("notification_email=b%40constructor.io")); + assertTrue( + "URL should contain two notification_email params", + countOccurrences(path, "notification_email=") == 2); + } + + // --- deleteItems --- + + @Test + public void deleteItemsWithSingleEmailShouldIncludeEmailInUrl() throws Exception { + mockServer.enqueue(new MockResponse().setResponseCode(200).setBody(MOCK_RESPONSE_BODY)); + ConstructorIO constructor = createConstructorIO(); + ConstructorItem[] items = {Utils.createProductItem()}; + + constructor.deleteItems(items, "Products", false, "test@constructor.io"); + + RecordedRequest request = mockServer.takeRequest(); + String path = request.getPath(); + assertTrue( + "URL should contain notification_email", + path.contains("notification_email=test%40constructor.io")); + } + + @Test + public void deleteItemsWithMultipleEmailsShouldIncludeAllEmailsInUrl() throws Exception { + mockServer.enqueue(new MockResponse().setResponseCode(200).setBody(MOCK_RESPONSE_BODY)); + ConstructorIO constructor = createConstructorIO(); + ConstructorItem[] items = {Utils.createProductItem()}; + List emails = Arrays.asList("a@constructor.io", "b@constructor.io"); + + constructor.deleteItems(items, "Products", false, emails); + + RecordedRequest request = mockServer.takeRequest(); + String path = request.getPath(); + assertTrue( + "URL should contain first notification_email", + path.contains("notification_email=a%40constructor.io")); + assertTrue( + "URL should contain second notification_email", + path.contains("notification_email=b%40constructor.io")); + assertTrue( + "URL should contain two notification_email params", + countOccurrences(path, "notification_email=") == 2); + } + + // --- updateItems --- + + @Test + public void updateItemsWithSingleEmailShouldIncludeEmailInUrl() throws Exception { + mockServer.enqueue(new MockResponse().setResponseCode(200).setBody(MOCK_RESPONSE_BODY)); + ConstructorIO constructor = createConstructorIO(); + ConstructorItem[] items = {Utils.createProductItem()}; + + constructor.updateItems(items, "Products", false, "test@constructor.io"); + + RecordedRequest request = mockServer.takeRequest(); + String path = request.getPath(); + assertTrue( + "URL should contain notification_email", + path.contains("notification_email=test%40constructor.io")); + } + + @Test + public void updateItemsWithMultipleEmailsShouldIncludeAllEmailsInUrl() throws Exception { + mockServer.enqueue(new MockResponse().setResponseCode(200).setBody(MOCK_RESPONSE_BODY)); + ConstructorIO constructor = createConstructorIO(); + ConstructorItem[] items = {Utils.createProductItem()}; + List emails = Arrays.asList("a@constructor.io", "b@constructor.io"); + + constructor.updateItems(items, "Products", false, emails); + + RecordedRequest request = mockServer.takeRequest(); + String path = request.getPath(); + assertTrue( + "URL should contain first notification_email", + path.contains("notification_email=a%40constructor.io")); + assertTrue( + "URL should contain second notification_email", + path.contains("notification_email=b%40constructor.io")); + assertTrue( + "URL should contain two notification_email params", + countOccurrences(path, "notification_email=") == 2); + } + + // --- createOrReplaceVariations --- + + @Test + public void createOrReplaceVariationsWithSingleEmailShouldIncludeEmailInUrl() throws Exception { + mockServer.enqueue(new MockResponse().setResponseCode(200).setBody(MOCK_RESPONSE_BODY)); + ConstructorIO constructor = createConstructorIO(); + ConstructorVariation[] variations = {Utils.createProductVariation("item-1")}; + + constructor.createOrReplaceVariations(variations, "Products", false, "test@constructor.io"); + + RecordedRequest request = mockServer.takeRequest(); + String path = request.getPath(); + assertTrue( + "URL should contain notification_email", + path.contains("notification_email=test%40constructor.io")); + } + + @Test + public void createOrReplaceVariationsWithMultipleEmailsShouldIncludeAllEmailsInUrl() + throws Exception { + mockServer.enqueue(new MockResponse().setResponseCode(200).setBody(MOCK_RESPONSE_BODY)); + ConstructorIO constructor = createConstructorIO(); + ConstructorVariation[] variations = {Utils.createProductVariation("item-1")}; + List emails = Arrays.asList("a@constructor.io", "b@constructor.io"); + + constructor.createOrReplaceVariations(variations, "Products", false, emails); + + RecordedRequest request = mockServer.takeRequest(); + String path = request.getPath(); + assertTrue( + "URL should contain first notification_email", + path.contains("notification_email=a%40constructor.io")); + assertTrue( + "URL should contain second notification_email", + path.contains("notification_email=b%40constructor.io")); + assertTrue( + "URL should contain two notification_email params", + countOccurrences(path, "notification_email=") == 2); + } + + // --- deleteVariations --- + + @Test + public void deleteVariationsWithSingleEmailShouldIncludeEmailInUrl() throws Exception { + mockServer.enqueue(new MockResponse().setResponseCode(200).setBody(MOCK_RESPONSE_BODY)); + ConstructorIO constructor = createConstructorIO(); + ConstructorVariation[] variations = {Utils.createProductVariation("item-1")}; + + constructor.deleteVariations(variations, "Products", false, "test@constructor.io"); + + RecordedRequest request = mockServer.takeRequest(); + String path = request.getPath(); + assertTrue( + "URL should contain notification_email", + path.contains("notification_email=test%40constructor.io")); + } + + @Test + public void deleteVariationsWithMultipleEmailsShouldIncludeAllEmailsInUrl() throws Exception { + mockServer.enqueue(new MockResponse().setResponseCode(200).setBody(MOCK_RESPONSE_BODY)); + ConstructorIO constructor = createConstructorIO(); + ConstructorVariation[] variations = {Utils.createProductVariation("item-1")}; + List emails = Arrays.asList("a@constructor.io", "b@constructor.io"); + + constructor.deleteVariations(variations, "Products", false, emails); + + RecordedRequest request = mockServer.takeRequest(); + String path = request.getPath(); + assertTrue( + "URL should contain first notification_email", + path.contains("notification_email=a%40constructor.io")); + assertTrue( + "URL should contain second notification_email", + path.contains("notification_email=b%40constructor.io")); + assertTrue( + "URL should contain two notification_email params", + countOccurrences(path, "notification_email=") == 2); + } + + // --- updateVariations --- + + @Test + public void updateVariationsWithSingleEmailShouldIncludeEmailInUrl() throws Exception { + mockServer.enqueue(new MockResponse().setResponseCode(200).setBody(MOCK_RESPONSE_BODY)); + ConstructorIO constructor = createConstructorIO(); + ConstructorVariation[] variations = {Utils.createProductVariation("item-1")}; + + constructor.updateVariations(variations, "Products", false, "test@constructor.io"); + + RecordedRequest request = mockServer.takeRequest(); + String path = request.getPath(); + assertTrue( + "URL should contain notification_email", + path.contains("notification_email=test%40constructor.io")); + } + + @Test + public void updateVariationsWithMultipleEmailsShouldIncludeAllEmailsInUrl() throws Exception { + mockServer.enqueue(new MockResponse().setResponseCode(200).setBody(MOCK_RESPONSE_BODY)); + ConstructorIO constructor = createConstructorIO(); + ConstructorVariation[] variations = {Utils.createProductVariation("item-1")}; + List emails = Arrays.asList("a@constructor.io", "b@constructor.io"); + + constructor.updateVariations(variations, "Products", false, emails); + + RecordedRequest request = mockServer.takeRequest(); + String path = request.getPath(); + assertTrue( + "URL should contain first notification_email", + path.contains("notification_email=a%40constructor.io")); + assertTrue( + "URL should contain second notification_email", + path.contains("notification_email=b%40constructor.io")); + assertTrue( + "URL should contain two notification_email params", + countOccurrences(path, "notification_email=") == 2); + } + + // --- replaceCatalog --- + + @Test + public void replaceCatalogWithMultipleEmailsShouldIncludeAllEmailsInUrl() throws Exception { + mockServer.enqueue(new MockResponse().setResponseCode(200).setBody(MOCK_RESPONSE_BODY)); + ConstructorIO constructor = createConstructorIO(); + + File csvFile = tempFolder.newFile("items.csv"); + FileWriter writer = new FileWriter(csvFile); + writer.write("id,name\n1,test\n"); + writer.close(); + + Map files = new HashMap(); + files.put("items", csvFile); + CatalogRequest req = new CatalogRequest(files, "Products"); + req.setNotificationEmail(Arrays.asList("a@constructor.io", "b@constructor.io")); + + constructor.replaceCatalog(req); + + RecordedRequest request = mockServer.takeRequest(); + String path = request.getPath(); + assertTrue( + "URL should contain first notification_email", + path.contains("notification_email=a%40constructor.io")); + assertTrue( + "URL should contain second notification_email", + path.contains("notification_email=b%40constructor.io")); + assertTrue( + "URL should contain two notification_email params", + countOccurrences(path, "notification_email=") == 2); + } + + @Test + public void replaceCatalogWithSingleEmailShouldIncludeEmailInUrl() throws Exception { + mockServer.enqueue(new MockResponse().setResponseCode(200).setBody(MOCK_RESPONSE_BODY)); + ConstructorIO constructor = createConstructorIO(); + + File csvFile = tempFolder.newFile("items.csv"); + FileWriter writer = new FileWriter(csvFile); + writer.write("id,name\n1,test\n"); + writer.close(); + + Map files = new HashMap(); + files.put("items", csvFile); + CatalogRequest req = new CatalogRequest(files, "Products"); + req.setNotificationEmail("test@constructor.io"); + + constructor.replaceCatalog(req); + + RecordedRequest request = mockServer.takeRequest(); + String path = request.getPath(); + assertTrue( + "URL should contain notification_email", + path.contains("notification_email=test%40constructor.io")); + } + + // --- updateCatalog --- + + @Test + public void updateCatalogWithMultipleEmailsShouldIncludeAllEmailsInUrl() throws Exception { + mockServer.enqueue(new MockResponse().setResponseCode(200).setBody(MOCK_RESPONSE_BODY)); + ConstructorIO constructor = createConstructorIO(); + + File csvFile = tempFolder.newFile("items.csv"); + FileWriter writer = new FileWriter(csvFile); + writer.write("id,name\n1,test\n"); + writer.close(); + + Map files = new HashMap(); + files.put("items", csvFile); + CatalogRequest req = new CatalogRequest(files, "Products"); + req.setNotificationEmail(Arrays.asList("a@constructor.io", "b@constructor.io")); + + constructor.updateCatalog(req); + + RecordedRequest request = mockServer.takeRequest(); + String path = request.getPath(); + assertTrue( + "URL should contain first notification_email", + path.contains("notification_email=a%40constructor.io")); + assertTrue( + "URL should contain second notification_email", + path.contains("notification_email=b%40constructor.io")); + assertTrue( + "URL should contain two notification_email params", + countOccurrences(path, "notification_email=") == 2); + } + + // --- patchCatalog --- + + @Test + public void patchCatalogWithMultipleEmailsShouldIncludeAllEmailsInUrl() throws Exception { + mockServer.enqueue(new MockResponse().setResponseCode(200).setBody(MOCK_RESPONSE_BODY)); + ConstructorIO constructor = createConstructorIO(); + + File csvFile = tempFolder.newFile("items.csv"); + FileWriter writer = new FileWriter(csvFile); + writer.write("id,name\n1,test\n"); + writer.close(); + + Map files = new HashMap(); + files.put("items", csvFile); + CatalogRequest req = new CatalogRequest(files, "Products"); + req.setNotificationEmail(Arrays.asList("a@constructor.io", "b@constructor.io")); + + constructor.patchCatalog(req); + + RecordedRequest request = mockServer.takeRequest(); + String path = request.getPath(); + assertTrue( + "URL should contain first notification_email", + path.contains("notification_email=a%40constructor.io")); + assertTrue( + "URL should contain second notification_email", + path.contains("notification_email=b%40constructor.io")); + assertTrue( + "URL should contain two notification_email params", + countOccurrences(path, "notification_email=") == 2); + } + + // --- Edge cases --- + + @Test + public void createOrReplaceItemsWithNullEmailsShouldNotIncludeEmailInUrl() throws Exception { + mockServer.enqueue(new MockResponse().setResponseCode(200).setBody(MOCK_RESPONSE_BODY)); + ConstructorIO constructor = createConstructorIO(); + ConstructorItem[] items = {Utils.createProductItem()}; + + constructor.createOrReplaceItems(items, "Products", false, (List) null); + + RecordedRequest request = mockServer.takeRequest(); + String path = request.getPath(); + assertTrue( + "URL should not contain notification_email", !path.contains("notification_email")); + } + + @Test + public void createOrReplaceItemsWithNoEmailShouldNotIncludeEmailInUrl() throws Exception { + mockServer.enqueue(new MockResponse().setResponseCode(200).setBody(MOCK_RESPONSE_BODY)); + ConstructorIO constructor = createConstructorIO(); + ConstructorItem[] items = {Utils.createProductItem()}; + + constructor.createOrReplaceItems(items, "Products"); + + RecordedRequest request = mockServer.takeRequest(); + String path = request.getPath(); + assertTrue( + "URL should not contain notification_email", !path.contains("notification_email")); + } +} From 635c72ded82a513c80577326db7afdc770c56f39 Mon Sep 17 00:00:00 2001 From: mudaafi Date: Fri, 17 Apr 2026 02:55:30 +0800 Subject: [PATCH 2/5] update tests --- .../io/constructor/client/ConstructorIOTaskTest.java | 10 ++++------ .../io/constructor/client/ConstructorIOTasksTest.java | 10 ++++------ 2 files changed, 8 insertions(+), 12 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..453a7c1c 100644 --- a/constructorio-client/src/test/java/io/constructor/client/ConstructorIOTaskTest.java +++ b/constructorio-client/src/test/java/io/constructor/client/ConstructorIOTaskTest.java @@ -78,8 +78,7 @@ public void TaskRequestWithInvalidTokenShouldError() throws Exception { 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"); + "[HTTP 401] Unauthorized"); Task response = constructor.task(request); } @@ -90,8 +89,7 @@ public void TaskAsJSONRequestWithInvalidTokenShouldError() throws Exception { 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"); + "[HTTP 401] Unauthorized"); String response = constructor.taskAsJson(request); } @@ -103,7 +101,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`. You can find your key at app.constructor.io/dashboard/accounts/api_integration.")); Task response = constructor.task(request); } @@ -115,7 +113,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`. You can find your key at app.constructor.io/dashboard/accounts/api_integration.")); 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..41b6b809 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); } @@ -219,8 +219,7 @@ public void AllTasksShouldReturnErrorWithInvalidApiToken() throws Exception { 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"); + "[HTTP 401] Unauthorized"); AllTasksResponse response = constructor.allTasks(request); } @@ -232,7 +231,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); } @@ -243,8 +242,7 @@ public void AllTasksAsJSONShouldReturnErrorWithInvalidApiToken() throws Exceptio 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"); + "[HTTP 401] Unauthorized"); String response = constructor.allTasksAsJson(request); } } From e652f66fe2365af37982fbb1ad7f5bf1ebdaf165 Mon Sep 17 00:00:00 2001 From: mudaafi Date: Fri, 17 Apr 2026 03:08:17 +0800 Subject: [PATCH 3/5] lint --- .../java/io/constructor/client/ConstructorIOTaskTest.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 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 8919224b..42d90467 100644 --- a/constructorio-client/src/test/java/io/constructor/client/ConstructorIOTaskTest.java +++ b/constructorio-client/src/test/java/io/constructor/client/ConstructorIOTaskTest.java @@ -99,7 +99,9 @@ public void TaskRequestWithInvalidApiKeyShouldError() throws Exception { thrown.expect(ConstructorException.class); thrown.expectMessage( StringContains.containsString( - "[HTTP 400] You have supplied an invalid `key` or `autocomplete_key`. You can find your key at app.constructor.io/dashboard/accounts/api_integration.")); + "[HTTP 400] You have supplied an invalid `key` or `autocomplete_key`. You" + + " can find your key at" + + " app.constructor.io/dashboard/accounts/api_integration.")); Task response = constructor.task(request); } @@ -111,7 +113,9 @@ public void TaskAsJSONRequestWithInvalidApiKeyShouldError() throws Exception { thrown.expect(ConstructorException.class); thrown.expectMessage( StringContains.containsString( - "[HTTP 400] You have supplied an invalid `key` or `autocomplete_key`. You can find your key at app.constructor.io/dashboard/accounts/api_integration.")); + "[HTTP 400] You have supplied an invalid `key` or `autocomplete_key`. You" + + " can find your key at" + + " app.constructor.io/dashboard/accounts/api_integration.")); String response = constructor.taskAsJson(request); } } From 34b15dc7f9796c046cd1df3c544dc11a53696680 Mon Sep 17 00:00:00 2001 From: mudaafi Date: Sat, 18 Apr 2026 00:44:06 +0800 Subject: [PATCH 4/5] address comments --- .../io/constructor/client/ConstructorIO.java | 2 +- .../ConstructorIONotificationEmailTest.java | 31 +++---------------- .../src/test/resources/items.csv | 2 ++ 3 files changed, 7 insertions(+), 28 deletions(-) create mode 100644 constructorio-client/src/test/resources/items.csv 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 d4b56b8e..b3a85452 100644 --- a/constructorio-client/src/main/java/io/constructor/client/ConstructorIO.java +++ b/constructorio-client/src/main/java/io/constructor/client/ConstructorIO.java @@ -737,7 +737,7 @@ public String updateVariations( * @param force whether or not the system should process the request even if it will invalidate * a large number of existing variations. * @param notificationEmails A list of email addresses to receive an email notification if the - * notification in case the task fails. + * task fails. * @return a string of JSON * @throws ConstructorException if the request is invalid. */ diff --git a/constructorio-client/src/test/java/io/constructor/client/ConstructorIONotificationEmailTest.java b/constructorio-client/src/test/java/io/constructor/client/ConstructorIONotificationEmailTest.java index e82e26bf..79aa461d 100644 --- a/constructorio-client/src/test/java/io/constructor/client/ConstructorIONotificationEmailTest.java +++ b/constructorio-client/src/test/java/io/constructor/client/ConstructorIONotificationEmailTest.java @@ -3,7 +3,6 @@ import static org.junit.Assert.assertTrue; import java.io.File; -import java.io.FileWriter; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -16,7 +15,6 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import org.junit.rules.TemporaryFolder; public class ConstructorIONotificationEmailTest { @@ -26,7 +24,6 @@ public class ConstructorIONotificationEmailTest { "{\"task_id\": 1, \"task_status_path\": \"/task/1\"}"; @Rule public ExpectedException thrown = ExpectedException.none(); - @Rule public TemporaryFolder tempFolder = new TemporaryFolder(); @BeforeClass public static void setup() throws Exception { @@ -296,13 +293,8 @@ public void replaceCatalogWithMultipleEmailsShouldIncludeAllEmailsInUrl() throws mockServer.enqueue(new MockResponse().setResponseCode(200).setBody(MOCK_RESPONSE_BODY)); ConstructorIO constructor = createConstructorIO(); - File csvFile = tempFolder.newFile("items.csv"); - FileWriter writer = new FileWriter(csvFile); - writer.write("id,name\n1,test\n"); - writer.close(); - Map files = new HashMap(); - files.put("items", csvFile); + files.put("items", new File("src/test/resources/items.csv")); CatalogRequest req = new CatalogRequest(files, "Products"); req.setNotificationEmail(Arrays.asList("a@constructor.io", "b@constructor.io")); @@ -326,13 +318,8 @@ public void replaceCatalogWithSingleEmailShouldIncludeEmailInUrl() throws Except mockServer.enqueue(new MockResponse().setResponseCode(200).setBody(MOCK_RESPONSE_BODY)); ConstructorIO constructor = createConstructorIO(); - File csvFile = tempFolder.newFile("items.csv"); - FileWriter writer = new FileWriter(csvFile); - writer.write("id,name\n1,test\n"); - writer.close(); - Map files = new HashMap(); - files.put("items", csvFile); + files.put("items", new File("src/test/resources/items.csv")); CatalogRequest req = new CatalogRequest(files, "Products"); req.setNotificationEmail("test@constructor.io"); @@ -352,13 +339,8 @@ public void updateCatalogWithMultipleEmailsShouldIncludeAllEmailsInUrl() throws mockServer.enqueue(new MockResponse().setResponseCode(200).setBody(MOCK_RESPONSE_BODY)); ConstructorIO constructor = createConstructorIO(); - File csvFile = tempFolder.newFile("items.csv"); - FileWriter writer = new FileWriter(csvFile); - writer.write("id,name\n1,test\n"); - writer.close(); - Map files = new HashMap(); - files.put("items", csvFile); + files.put("items", new File("src/test/resources/items.csv")); CatalogRequest req = new CatalogRequest(files, "Products"); req.setNotificationEmail(Arrays.asList("a@constructor.io", "b@constructor.io")); @@ -384,13 +366,8 @@ public void patchCatalogWithMultipleEmailsShouldIncludeAllEmailsInUrl() throws E mockServer.enqueue(new MockResponse().setResponseCode(200).setBody(MOCK_RESPONSE_BODY)); ConstructorIO constructor = createConstructorIO(); - File csvFile = tempFolder.newFile("items.csv"); - FileWriter writer = new FileWriter(csvFile); - writer.write("id,name\n1,test\n"); - writer.close(); - Map files = new HashMap(); - files.put("items", csvFile); + files.put("items", new File("src/test/resources/items.csv")); CatalogRequest req = new CatalogRequest(files, "Products"); req.setNotificationEmail(Arrays.asList("a@constructor.io", "b@constructor.io")); diff --git a/constructorio-client/src/test/resources/items.csv b/constructorio-client/src/test/resources/items.csv new file mode 100644 index 00000000..b8fd1a96 --- /dev/null +++ b/constructorio-client/src/test/resources/items.csv @@ -0,0 +1,2 @@ +id,name +1,test From 5e9308e21ea33812e65b60ac401728390addc2ff Mon Sep 17 00:00:00 2001 From: mudaafi Date: Thu, 23 Apr 2026 09:04:46 +0800 Subject: [PATCH 5/5] don't overload the setter --- .../src/main/java/io/constructor/client/CatalogRequest.java | 4 +++- .../test/java/io/constructor/client/CatalogRequestTest.java | 2 +- .../client/ConstructorIONotificationEmailTest.java | 6 +++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/constructorio-client/src/main/java/io/constructor/client/CatalogRequest.java b/constructorio-client/src/main/java/io/constructor/client/CatalogRequest.java index 0dcc35b0..22757cda 100644 --- a/constructorio-client/src/main/java/io/constructor/client/CatalogRequest.java +++ b/constructorio-client/src/main/java/io/constructor/client/CatalogRequest.java @@ -86,7 +86,9 @@ public String getSection() { /** * @param email the email address to receive a notification in case the task fails + * @deprecated Use {@link #setNotificationEmails(List)} instead to support multiple emails. */ + @Deprecated public void setNotificationEmail(String email) { this.notificationEmails.clear(); if (email != null) { @@ -98,7 +100,7 @@ public void setNotificationEmail(String email) { * @param emails list of email addresses where you'd like to receive notifications in case the * task fails */ - public void setNotificationEmail(List emails) { + public void setNotificationEmails(List emails) { this.notificationEmails.clear(); if (emails != null) { this.notificationEmails.addAll(emails); diff --git a/constructorio-client/src/test/java/io/constructor/client/CatalogRequestTest.java b/constructorio-client/src/test/java/io/constructor/client/CatalogRequestTest.java index c4383593..b38ceed3 100644 --- a/constructorio-client/src/test/java/io/constructor/client/CatalogRequestTest.java +++ b/constructorio-client/src/test/java/io/constructor/client/CatalogRequestTest.java @@ -86,7 +86,7 @@ public void setNotificationEmailShouldSet() throws Exception { CatalogRequest request = new CatalogRequest(files, "Products"); List emails = Arrays.asList("a@constructor.io", "b@constructor.io"); - request.setNotificationEmail(emails); + request.setNotificationEmails(emails); assertEquals(request.getNotificationEmails(), emails); assertEquals(request.getNotificationEmail(), "a@constructor.io"); diff --git a/constructorio-client/src/test/java/io/constructor/client/ConstructorIONotificationEmailTest.java b/constructorio-client/src/test/java/io/constructor/client/ConstructorIONotificationEmailTest.java index 79aa461d..3afa1728 100644 --- a/constructorio-client/src/test/java/io/constructor/client/ConstructorIONotificationEmailTest.java +++ b/constructorio-client/src/test/java/io/constructor/client/ConstructorIONotificationEmailTest.java @@ -296,7 +296,7 @@ public void replaceCatalogWithMultipleEmailsShouldIncludeAllEmailsInUrl() throws Map files = new HashMap(); files.put("items", new File("src/test/resources/items.csv")); CatalogRequest req = new CatalogRequest(files, "Products"); - req.setNotificationEmail(Arrays.asList("a@constructor.io", "b@constructor.io")); + req.setNotificationEmails(Arrays.asList("a@constructor.io", "b@constructor.io")); constructor.replaceCatalog(req); @@ -342,7 +342,7 @@ public void updateCatalogWithMultipleEmailsShouldIncludeAllEmailsInUrl() throws Map files = new HashMap(); files.put("items", new File("src/test/resources/items.csv")); CatalogRequest req = new CatalogRequest(files, "Products"); - req.setNotificationEmail(Arrays.asList("a@constructor.io", "b@constructor.io")); + req.setNotificationEmails(Arrays.asList("a@constructor.io", "b@constructor.io")); constructor.updateCatalog(req); @@ -369,7 +369,7 @@ public void patchCatalogWithMultipleEmailsShouldIncludeAllEmailsInUrl() throws E Map files = new HashMap(); files.put("items", new File("src/test/resources/items.csv")); CatalogRequest req = new CatalogRequest(files, "Products"); - req.setNotificationEmail(Arrays.asList("a@constructor.io", "b@constructor.io")); + req.setNotificationEmails(Arrays.asList("a@constructor.io", "b@constructor.io")); constructor.patchCatalog(req);