From f7e5706aabd2ce63d3134331a5aea8bca8f570b4 Mon Sep 17 00:00:00 2001 From: Tyler Roach Date: Fri, 5 Dec 2025 09:33:41 -0500 Subject: [PATCH 1/2] Configurable import strict mode --- .../mixpanel/mixpanelapi/DeliveryOptions.java | 114 +++++++++++++++ .../com/mixpanel/mixpanelapi/MixpanelAPI.java | 57 ++++++-- .../mixpanel/mixpanelapi/MixpanelAPITest.java | 136 ++++++++++++++++++ 3 files changed, 298 insertions(+), 9 deletions(-) create mode 100644 src/main/java/com/mixpanel/mixpanelapi/DeliveryOptions.java diff --git a/src/main/java/com/mixpanel/mixpanelapi/DeliveryOptions.java b/src/main/java/com/mixpanel/mixpanelapi/DeliveryOptions.java new file mode 100644 index 0000000..4675ef9 --- /dev/null +++ b/src/main/java/com/mixpanel/mixpanelapi/DeliveryOptions.java @@ -0,0 +1,114 @@ +package com.mixpanel.mixpanelapi; + +/** + * Options for configuring how messages are delivered to Mixpanel. + * Use the {@link Builder} to create instances. + * + *

Different options apply to different message types: + *

+ * + *

Example usage: + *

{@code
+ * DeliveryOptions options = new DeliveryOptions.Builder()
+ *     .importStrictMode(false)  // Disable strict validation for imports
+ *     .useIpAddress(true)       // Use IP address for geolocation (events/people/groups only)
+ *     .build();
+ *
+ * mixpanelApi.deliver(delivery, options);
+ * }
+ */ +public class DeliveryOptions { + + private final boolean mImportStrictMode; + private final boolean mUseIpAddress; + + private DeliveryOptions(Builder builder) { + mImportStrictMode = builder.importStrictMode; + mUseIpAddress = builder.useIpAddress; + } + + /** + * Returns whether strict mode is enabled for import messages. + * + *

Note: This option only applies to import messages (historical events). + * It has no effect on regular events, people, or groups messages. + * + *

When strict mode is enabled (default), the /import endpoint validates each event + * and returns a 400 error if any event has issues. Correctly formed events are still + * ingested, and problematic events are returned in the response with error messages. + * + *

When strict mode is disabled, validation is bypassed and all events are imported + * regardless of their validity. + * + * @return true if strict mode is enabled for imports, false otherwise + */ + public boolean isImportStrictMode() { + return mImportStrictMode; + } + + /** + * Returns whether the IP address should be used for geolocation. + * + *

Note: This option only applies to events, people, and groups messages. + * It does NOT apply to import messages, which use Basic Auth and don't support the ip parameter. + * + * @return true if IP address should be used for geolocation, false otherwise + */ + public boolean useIpAddress() { + return mUseIpAddress; + } + + /** + * Builder for creating {@link DeliveryOptions} instances. + */ + public static class Builder { + private boolean importStrictMode = true; + private boolean useIpAddress = false; + + /** + * Sets whether to use strict mode for import messages. + * + * will validate the supplied events and return a 400 status code if any of the events fail validation with details of the error + * + *

Setting this value to true (default) will validate the supplied events and return + * a 400 status code if any of the events fail validation with details of the error. + * Setting this value to false disables validation. + * + * @param importStrictMode true to enable strict validation (default), false to disable + * @return this Builder instance for method chaining + */ + public Builder importStrictMode(boolean importStrictMode) { + this.importStrictMode = importStrictMode; + return this; + } + + /** + * Sets whether to use the IP address for geolocation. + * + *

Note: This option only applies to events, people, and groups messages. + * It does NOT apply to import messages. + * + *

When enabled, Mixpanel will use the IP address of the request to set + * geolocation properties on events and profiles. + * + * @param useIpAddress true to use IP address for geolocation, false otherwise (default) + * @return this Builder instance for method chaining + */ + public Builder useIpAddress(boolean useIpAddress) { + this.useIpAddress = useIpAddress; + return this; + } + + /** + * Builds and returns a new {@link DeliveryOptions} instance. + * + * @return a new DeliveryOptions with the configured settings + */ + public DeliveryOptions build() { + return new DeliveryOptions(this); + } + } +} diff --git a/src/main/java/com/mixpanel/mixpanelapi/MixpanelAPI.java b/src/main/java/com/mixpanel/mixpanelapi/MixpanelAPI.java index c8d332d..47669af 100644 --- a/src/main/java/com/mixpanel/mixpanelapi/MixpanelAPI.java +++ b/src/main/java/com/mixpanel/mixpanelapi/MixpanelAPI.java @@ -269,14 +269,41 @@ public void deliver(ClientDelivery toSend) throws IOException { * should be called in a separate thread or in a queue consumer. * * @param toSend a ClientDelivery containing a number of Mixpanel messages + * @param useIpAddress if true, Mixpanel will use the ip address of the request for geolocation * @throws IOException * @see ClientDelivery */ public void deliver(ClientDelivery toSend, boolean useIpAddress) throws IOException { - String ipParameter = "ip=0"; - if (useIpAddress) { - ipParameter = "ip=1"; - } + DeliveryOptions options = new DeliveryOptions.Builder() + .useIpAddress(useIpAddress) + .build(); + deliver(toSend, options); + } + + /** + * Attempts to send a given delivery to the Mixpanel servers with custom options. + * Will block, possibly on multiple server requests. For most applications, this method + * should be called in a separate thread or in a queue consumer. + * + *

Example usage: + *

{@code
+     * DeliveryOptions options = new DeliveryOptions.Builder()
+     *     .importStrictMode(false)  // Disable strict validation for imports
+     *     .useIpAddress(true)       // Use IP address for geolocation (events/people/groups only)
+     *     .build();
+     *
+     * mixpanelApi.deliver(delivery, options);
+     * }
+ * + * @param toSend a ClientDelivery containing a number of Mixpanel messages + * @param options configuration options for delivery + * @throws IOException if there's a network error + * @throws MixpanelServerException if the server rejects the messages + * @see ClientDelivery + * @see DeliveryOptions + */ + public void deliver(ClientDelivery toSend, DeliveryOptions options) throws IOException { + String ipParameter = options.useIpAddress() ? "ip=1" : "ip=0"; String eventsUrl = mEventsEndpoint + "?" + ipParameter; List events = toSend.getEventsMessages(); @@ -290,10 +317,10 @@ public void deliver(ClientDelivery toSend, boolean useIpAddress) throws IOExcept List groupMessages = toSend.getGroupMessages(); sendMessages(groupMessages, groupsUrl); - // Handle import messages - use strict mode and extract token for auth List importMessages = toSend.getImportMessages(); if (importMessages.size() > 0) { - String importUrl = mImportEndpoint + "?strict=1"; + String strictParam = options.isImportStrictMode() ? "1" : "0"; + String importUrl = mImportEndpoint + "?strict=" + strictParam; sendImportMessages(importMessages, importUrl); } } @@ -534,7 +561,7 @@ private String dataString(List messages) { responseStream = conn.getInputStream(); response = slurp(responseStream); } catch (IOException e) { - // HTTP error codes (401, 400, etc.) throw IOException when calling getInputStream() + // HTTP error codes (401, 400, 413, etc.) throw IOException when calling getInputStream() // Check if it's an HTTP error and read the error stream for details InputStream errorStream = conn.getErrorStream(); if (errorStream != null) { @@ -559,12 +586,24 @@ private String dataString(List messages) { } } - // Import endpoint returns JSON like {"code":200,"status":"OK","num_records_imported":N} + // Import endpoint returns different formats depending on strict mode: + // - strict=1: JSON like {"code":200,"status":"OK","num_records_imported":N} + // - strict=0: Plain text "0" (not imported) or "1" (imported) if (response == null) { return false; } - // Parse JSON response + // First, try to handle strict=0 response format (plain text "0" or "1") + String trimmedResponse = response.trim(); + if ("1".equals(trimmedResponse)) { + // strict=0 with successful import + return true; + } else if ("0".equals(trimmedResponse)) { + // strict=0 with failed import (events not imported, reason unknown) + return false; + } + + // Try to parse as JSON response (strict=1 format) try { JSONObject jsonResponse = new JSONObject(response); diff --git a/src/test/java/com/mixpanel/mixpanelapi/MixpanelAPITest.java b/src/test/java/com/mixpanel/mixpanelapi/MixpanelAPITest.java index a28b6a0..9193797 100644 --- a/src/test/java/com/mixpanel/mixpanelapi/MixpanelAPITest.java +++ b/src/test/java/com/mixpanel/mixpanelapi/MixpanelAPITest.java @@ -1439,4 +1439,140 @@ public void testNegativeValueTimeoutUsesDefaults() { assertEquals(Integer.valueOf(10000), api.mReadTimeout); api.close(); } + + // ==================== DeliveryOptions Tests ==================== + + public void testDeliveryOptionsDefaultValues() { + // GIVEN/WHEN + DeliveryOptions options = new DeliveryOptions.Builder().build(); + + // THEN - defaults should be importStrictMode=true, useIpAddress=false + assertTrue(options.isImportStrictMode()); + assertFalse(options.useIpAddress()); + } + + public void testDeliveryOptionsAllCustomValues() { + // GIVEN/WHEN + DeliveryOptions options = new DeliveryOptions.Builder() + .importStrictMode(false) + .useIpAddress(true) + .build(); + + // THEN + assertFalse(options.isImportStrictMode()); + assertTrue(options.useIpAddress()); + } + + + // ==================== Strict Mode Import Tests ==================== + + public void testImportWithStrictModeEnabled() { + // Test that strict=1 is in the URL when strictMode is true (default) + final Map capturedUrls = new HashMap(); + + MixpanelAPI api = new MixpanelAPI("events url", "people url", "groups url", "import url") { + @Override + public boolean sendImportData(String dataString, String endpointUrl, String token) { + capturedUrls.put("endpoint", endpointUrl); + return true; + } + }; + + ClientDelivery c = new ClientDelivery(); + long historicalTime = System.currentTimeMillis() - (90L * 24L * 60L * 60L * 1000L); + + try { + JSONObject props = new JSONObject(); + props.put("time", historicalTime); + props.put("$insert_id", "insert-id-1"); + JSONObject importEvent = mBuilder.importEvent("user-1", "test event", props); + c.addMessage(importEvent); + + // Use default options (strictMode=true) + api.deliver(c); + + String url = capturedUrls.get("endpoint"); + assertTrue("Default: strict=1 in URL", url.contains("strict=1")); + + } catch (IOException e) { + fail("IOException: " + e.toString()); + } catch (JSONException e) { + fail("JSON error: " + e.toString()); + } + + api.close(); + } + + public void testImportWithStrictModeDisabled() { + // Test that strict=0 is in the URL when strictMode is false + final Map capturedUrls = new HashMap(); + + MixpanelAPI api = new MixpanelAPI("events url", "people url", "groups url", "import url") { + @Override + public boolean sendImportData(String dataString, String endpointUrl, String token) { + capturedUrls.put("endpoint", endpointUrl); + return true; + } + }; + + ClientDelivery c = new ClientDelivery(); + long historicalTime = System.currentTimeMillis() - (90L * 24L * 60L * 60L * 1000L); + + try { + JSONObject props = new JSONObject(); + props.put("time", historicalTime); + props.put("$insert_id", "insert-id-1"); + JSONObject importEvent = mBuilder.importEvent("user-1", "test event", props); + c.addMessage(importEvent); + + // Disable strict mode + DeliveryOptions options = new DeliveryOptions.Builder() + .importStrictMode(false) + .build(); + api.deliver(c, options); + + String url = capturedUrls.get("endpoint"); + assertTrue("With importStrictMode=false: strict=0 in URL", url.contains("strict=0")); + + } catch (IOException e) { + fail("IOException: " + e.toString()); + } catch (JSONException e) { + fail("JSON error: " + e.toString()); + } + + api.close(); + } + + public void testDeliverWithOptionsUsesIpAddress() { + // Test that useIpAddress option is respected + final Map capturedUrls = new HashMap(); + + MixpanelAPI api = new MixpanelAPI("events url", "people url", "groups url", "import url") { + @Override + public boolean sendData(String dataString, String endpointUrl) { + capturedUrls.put("events", endpointUrl); + return true; + } + }; + + ClientDelivery c = new ClientDelivery(); + JSONObject event = mBuilder.event("user-1", "test event", null); + c.addMessage(event); + + try { + // With useIpAddress=true + DeliveryOptions options = new DeliveryOptions.Builder() + .useIpAddress(true) + .build(); + api.deliver(c, options); + + String url = capturedUrls.get("events"); + assertTrue("With useIpAddress=true: ip=1 in URL", url.contains("ip=1")); + + } catch (IOException e) { + fail("IOException: " + e.toString()); + } + + api.close(); + } } From f66d33ac18cd45e730fdf7b4d2b87cd1ac07b5a0 Mon Sep 17 00:00:00 2001 From: Tyler Roach Date: Mon, 8 Dec 2025 16:50:24 -0500 Subject: [PATCH 2/2] Configurable import max message count --- .../com/mixpanel/mixpanelapi/MixpanelAPI.java | 67 ++++++++---- .../mixpanel/mixpanelapi/MixpanelAPITest.java | 102 ++++++++++++++++++ 2 files changed, 148 insertions(+), 21 deletions(-) diff --git a/src/main/java/com/mixpanel/mixpanelapi/MixpanelAPI.java b/src/main/java/com/mixpanel/mixpanelapi/MixpanelAPI.java index 47669af..62ad37b 100644 --- a/src/main/java/com/mixpanel/mixpanelapi/MixpanelAPI.java +++ b/src/main/java/com/mixpanel/mixpanelapi/MixpanelAPI.java @@ -53,6 +53,7 @@ public class MixpanelAPI implements AutoCloseable { protected final boolean mUseGzipCompression; protected final Integer mConnectTimeout; protected final Integer mReadTimeout; + protected Integer mImportMaxMessageCount; protected final LocalFlagsProvider mLocalFlags; protected final RemoteFlagsProvider mRemoteFlags; protected final JsonSerializer mJsonSerializer; @@ -71,7 +72,7 @@ public MixpanelAPI() { * @param useGzipCompression whether to use gzip compression for network requests */ public MixpanelAPI(boolean useGzipCompression) { - this(null, null, null, null, useGzipCompression, null, null, null, null, null); + this(null, null, null, null, useGzipCompression, null, null, null, null, null, null); } /** @@ -100,7 +101,7 @@ public MixpanelAPI(RemoteFlagsConfig remoteFlagsConfig) { * @param remoteFlagsConfig configuration for remote feature flags evaluation (can be null) */ private MixpanelAPI(LocalFlagsConfig localFlagsConfig, RemoteFlagsConfig remoteFlagsConfig) { - this(null, null, null, null, false, localFlagsConfig, remoteFlagsConfig, null, null, null); + this(null, null, null, null, false, localFlagsConfig, remoteFlagsConfig, null, null, null, null); } /** @@ -113,7 +114,7 @@ private MixpanelAPI(LocalFlagsConfig localFlagsConfig, RemoteFlagsConfig remoteF * @see #MixpanelAPI() */ public MixpanelAPI(String eventsEndpoint, String peopleEndpoint) { - this(eventsEndpoint, peopleEndpoint, null, null, false, null, null, null, null, null); + this(eventsEndpoint, peopleEndpoint, null, null, false, null, null, null, null, null, null); } /** @@ -127,7 +128,7 @@ public MixpanelAPI(String eventsEndpoint, String peopleEndpoint) { * @see #MixpanelAPI() */ public MixpanelAPI(String eventsEndpoint, String peopleEndpoint, String groupsEndpoint) { - this(eventsEndpoint, peopleEndpoint, groupsEndpoint, null, false, null, null, null, null, null); + this(eventsEndpoint, peopleEndpoint, groupsEndpoint, null, false, null, null, null, null, null, null); } /** @@ -142,7 +143,7 @@ public MixpanelAPI(String eventsEndpoint, String peopleEndpoint, String groupsEn * @see #MixpanelAPI() */ public MixpanelAPI(String eventsEndpoint, String peopleEndpoint, String groupsEndpoint, String importEndpoint) { - this(eventsEndpoint, peopleEndpoint, groupsEndpoint, importEndpoint, false, null, null, null, null, null); + this(eventsEndpoint, peopleEndpoint, groupsEndpoint, importEndpoint, false, null, null, null, null, null, null); } /** @@ -158,7 +159,7 @@ public MixpanelAPI(String eventsEndpoint, String peopleEndpoint, String groupsEn * @see #MixpanelAPI() */ public MixpanelAPI(String eventsEndpoint, String peopleEndpoint, String groupsEndpoint, String importEndpoint, boolean useGzipCompression) { - this(eventsEndpoint, peopleEndpoint, groupsEndpoint, importEndpoint, useGzipCompression, null, null, null, null, null); + this(eventsEndpoint, peopleEndpoint, groupsEndpoint, importEndpoint, useGzipCompression, null, null, null, null, null, null); } /** @@ -168,16 +169,17 @@ public MixpanelAPI(String eventsEndpoint, String peopleEndpoint, String groupsEn */ private MixpanelAPI(Builder builder) { this( - builder.eventsEndpoint, - builder.peopleEndpoint, - builder.groupsEndpoint, - builder.importEndpoint, + builder.eventsEndpoint, + builder.peopleEndpoint, + builder.groupsEndpoint, + builder.importEndpoint, builder.useGzipCompression, builder.flagsConfig instanceof LocalFlagsConfig ? (LocalFlagsConfig) builder.flagsConfig : null, builder.flagsConfig instanceof RemoteFlagsConfig ? (RemoteFlagsConfig) builder.flagsConfig : null, builder.jsonSerializer, builder.connectTimeout, - builder.readTimeout + builder.readTimeout, + builder.importMaxMessageCount ); } @@ -192,18 +194,22 @@ private MixpanelAPI(Builder builder) { * @param localFlagsConfig configuration for local feature flags * @param remoteFlagsConfig configuration for remote feature flags * @param jsonSerializer custom JSON serializer (null uses default) + * @param connectTimeout connection timeout in milliseconds (null uses default) + * @param readTimeout read timeout in milliseconds (null uses default) + * @param importMaxMessageCount maximum messages per import batch (null uses default) */ private MixpanelAPI( - String eventsEndpoint, - String peopleEndpoint, - String groupsEndpoint, - String importEndpoint, - boolean useGzipCompression, - LocalFlagsConfig localFlagsConfig, + String eventsEndpoint, + String peopleEndpoint, + String groupsEndpoint, + String importEndpoint, + boolean useGzipCompression, + LocalFlagsConfig localFlagsConfig, RemoteFlagsConfig remoteFlagsConfig, JsonSerializer jsonSerializer, Integer connectTimeout, - Integer readTimeout + Integer readTimeout, + Integer importMaxMessageCount ) { mEventsEndpoint = eventsEndpoint != null ? eventsEndpoint : Config.BASE_ENDPOINT + "/track"; mPeopleEndpoint = peopleEndpoint != null ? peopleEndpoint : Config.BASE_ENDPOINT + "/engage"; @@ -212,6 +218,8 @@ private MixpanelAPI( mUseGzipCompression = useGzipCompression; mConnectTimeout = connectTimeout != null ? connectTimeout : DEFAULT_CONNECT_TIMEOUT_MILLIS; mReadTimeout = readTimeout != null ? readTimeout : DEFAULT_READ_TIMEOUT_MILLIS; + mImportMaxMessageCount = importMaxMessageCount != null ? + Math.min(importMaxMessageCount, Config.IMPORT_MAX_MESSAGE_SIZE) : Config.IMPORT_MAX_MESSAGE_SIZE; mDefaultJsonSerializer = new OrgJsonSerializer(); if (jsonSerializer != null) { logger.log(Level.INFO, "Custom JsonSerializer provided: " + jsonSerializer.getClass().getName()); @@ -453,10 +461,10 @@ private void sendImportMessages(List messages, String endpointUrl) t } } - // Send messages in batches (max 2000 per batch for /import) + // Send messages in batches (max 2000 per batch for /import by default) // If token is empty, the server will reject with 401 Unauthorized - for (int i = 0; i < messages.size(); i += Config.IMPORT_MAX_MESSAGE_SIZE) { - int endIndex = i + Config.IMPORT_MAX_MESSAGE_SIZE; + for (int i = 0; i < messages.size(); i += mImportMaxMessageCount) { + int endIndex = i + mImportMaxMessageCount; endIndex = Math.min(endIndex, messages.size()); List batch = messages.subList(i, endIndex); @@ -698,6 +706,7 @@ public static class Builder { private JsonSerializer jsonSerializer; private Integer connectTimeout; private Integer readTimeout; + private Integer importMaxMessageCount; /** * Sets the endpoint URL for Mixpanel events messages. @@ -807,6 +816,22 @@ public Builder readTimeout(int readTimeoutInMillis) { return this; } + /** + * Sets the maximum number of messages to include in a single batch for the /import endpoint. + * The default value is 2000 messages per batch. + * The max accepted value is 2000 + * + * @param importMaxMessageCount the maximum number of import messages per batch. + * Value must be greater than 0 and less than or equal to 2000. + * @return this Builder instance for method chaining + */ + public Builder importMaxMessageCount(int importMaxMessageCount) { + if (importMaxMessageCount > 0 && importMaxMessageCount <= Config.IMPORT_MAX_MESSAGE_SIZE) { + this.importMaxMessageCount = importMaxMessageCount; + } + return this; + } + /** * Builds and returns a new MixpanelAPI instance with the configured settings. * diff --git a/src/test/java/com/mixpanel/mixpanelapi/MixpanelAPITest.java b/src/test/java/com/mixpanel/mixpanelapi/MixpanelAPITest.java index 9193797..14f55da 100644 --- a/src/test/java/com/mixpanel/mixpanelapi/MixpanelAPITest.java +++ b/src/test/java/com/mixpanel/mixpanelapi/MixpanelAPITest.java @@ -852,6 +852,67 @@ public boolean sendImportData(String dataString, String endpointUrl, String toke } } + public void testCustomImportMaxMessageCount() { + // Test that custom importMaxMessageCount configuration is respected via Builder + final List sends = new ArrayList(); + final int customBatchSize = 100; + + // We can't override sendImportData with builder so we have to set it custom here. + MixpanelAPI testApi = new MixpanelAPI("events url", "people url", "groups url", "import url") { + { + // Use the custom batch size + mImportMaxMessageCount = customBatchSize; + } + + @Override + public boolean sendImportData(String dataString, String endpointUrl, String token) { + sends.add(dataString); + return true; + } + }; + + ClientDelivery c = new ClientDelivery(); + // Use 180 days ago (6 months, within >5 days and <1 year range) + long historicalTime = System.currentTimeMillis() - (180L * 24L * 60L * 60L * 1000L); + + // Create 250 import events (should be split into 3 batches: 100 + 100 + 50) + int totalEvents = 250; + for (int i = 0; i < totalEvents; i++) { + try { + JSONObject props = new JSONObject(); + props.put("time", historicalTime + i); + props.put("$insert_id", "custom-batch-" + i); + props.put("count", i); + + JSONObject importEvent = mBuilder.importEvent("a distinct id", "Test Event", props); + c.addMessage(importEvent); + } catch (JSONException e) { + fail("Failed to create import event: " + e.toString()); + } + } + + try { + testApi.deliver(c); + + // Should be split into 3 batches (100 + 100 + 50) + assertEquals("Messages split into 3 batches", 3, sends.size()); + + JSONArray firstBatch = new JSONArray(sends.get(0)); + assertEquals("First batch has 100 events", customBatchSize, firstBatch.length()); + + JSONArray secondBatch = new JSONArray(sends.get(1)); + assertEquals("Second batch has 100 events", customBatchSize, secondBatch.length()); + + JSONArray thirdBatch = new JSONArray(sends.get(2)); + assertEquals("Third batch has 50 events", 50, thirdBatch.length()); + + } catch (IOException e) { + fail("IOException during delivery: " + e.toString()); + } catch (JSONException e) { + fail("JSON parsing error: " + e.toString()); + } + } + public void testImportMessageValidation() { // Test that import messages are validated correctly ClientDelivery c = new ClientDelivery(); @@ -1135,6 +1196,7 @@ public void testBuilderWithDefaults() { assertEquals(Config.BASE_ENDPOINT + "/engage", api.mPeopleEndpoint); assertEquals(Config.BASE_ENDPOINT + "/groups", api.mGroupsEndpoint); assertEquals(Config.BASE_ENDPOINT + "/import", api.mImportEndpoint); + assertEquals((Integer) Config.IMPORT_MAX_MESSAGE_SIZE, (Integer) api.mImportMaxMessageCount); assertFalse(api.mUseGzipCompression); assertNull(api.mLocalFlags); assertNull(api.mRemoteFlags); @@ -1152,6 +1214,7 @@ public void testBuilderWithAllOptions() { String expectedPeopleEndpoint = "https://custom.example.com/people"; String expectedGroupsEndpoint = "https://custom.example.com/groups"; String expectedImportEndpoint = "https://custom.example.com/import"; + Integer expectedImportMaxMessageCount = 150; Integer expectedReadTimeout = 5000; Integer expectedConnectTimeout = 7000; boolean expectedGzipCompression = true; @@ -1165,6 +1228,7 @@ public void testBuilderWithAllOptions() { .peopleEndpoint(expectedPeopleEndpoint) .groupsEndpoint(expectedGroupsEndpoint) .importEndpoint(expectedImportEndpoint) + .importMaxMessageCount(expectedImportMaxMessageCount) .useGzipCompression(expectedGzipCompression) .flagsConfig(expectedLocalFlagsConfig) .jsonSerializer(expectedJsonSerializer) @@ -1179,6 +1243,7 @@ public void testBuilderWithAllOptions() { assertEquals(expectedImportEndpoint, api.mImportEndpoint); assertEquals(expectedGzipCompression, api.mUseGzipCompression); assertEquals(expectedJsonSerializer, api.mJsonSerializer); + assertEquals(expectedImportMaxMessageCount, api.mImportMaxMessageCount); assertNotNull(api.mLocalFlags); assertNull(api.mRemoteFlags); assertEquals(expectedReadTimeout, api.mReadTimeout); @@ -1186,6 +1251,43 @@ public void testBuilderWithAllOptions() { api.close(); } + /** + * Test import max message count is 2000 + */ + public void testBuilderImportMaxDoesNotExceed() { + MixpanelAPI api = new MixpanelAPI.Builder() + .importMaxMessageCount(3000) + .build(); + + assertEquals((Integer) 2000, api.mImportMaxMessageCount); + api.close(); + } + + /** + * Test import max message count at set minimum + */ + public void testBuilderImportMaxWithMinimum() { + MixpanelAPI api = new MixpanelAPI.Builder() + .importMaxMessageCount(1) + .build(); + + assertEquals((Integer) 1, api.mImportMaxMessageCount); + api.close(); + } + + /** + * Test import max message count ignores if lower than minimum + */ + public void testBuilderImportMaxHasMinimum() { + MixpanelAPI api = new MixpanelAPI.Builder() + .importMaxMessageCount(0) + .build(); + + assertEquals((Integer) 2000, api.mImportMaxMessageCount); + api.close(); + } + + /** * Test builder with LocalFlagsConfig */