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:
+ *
+ * - {@code importStrictMode} - Only applies to import messages
+ * - {@code useIpAddress} - Only applies to events, people, and groups messages (NOT imports)
+ *
+ *
+ * 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..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());
@@ -269,14 +277,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 +325,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);
}
}
@@ -426,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);
@@ -534,7 +569,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 +594,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);
@@ -659,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.
@@ -768,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 a28b6a0..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
*/
@@ -1439,4 +1541,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();
+ }
}