diff --git a/CHANGELOG.md b/CHANGELOG.md
index 23bf8ada1..373180cb4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -37,6 +37,16 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Addressed a text truncation issue in Embedded Message templates for applications targeting Android 14 and Android 15.
- Improved InboxActivity compatibility with edge-to-edge layouts, ensuring seamless handling of notches and display cutouts.
+## [3.6.0-beta1]
+
+#### Added
+- This release includes initial support for Anonymous user activation, a feature that allows marketers to convert valuable visitors into customers. With this feature, the SDK can:
+ - Fetch anonymous profile creation criteria from your Iterable project, and then automatically create Iterable user profiles for anonymous users who meet these criteria.
+ - Save information about a user's previous interactions with your application to their anonymous profile, after it's created.
+ - Display personalized messages for anonymous users (in-app, push, and embedded messages).
+ - Merge anonymous profiles into an existing, known user profiles (when needed).
+- Anonymous user activation is currently in private beta. If you'd like to learn more about it or discuss using it, talk to your Iterable customer success manager (who can also provide detailed documentation).
+
## [3.5.3]
#### Fixed
- Fixed an [issue](https://github.com/Iterable/react-native-sdk/issues/547) where the SDK would crash if the `IterableInAppMessage` object was null when consuming an in-app message.
diff --git a/app/.idea/workspace.xml b/app/.idea/workspace.xml
new file mode 100644
index 000000000..9c6424696
--- /dev/null
+++ b/app/.idea/workspace.xml
@@ -0,0 +1,135 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1728484381438
+
+
+ 1728484381438
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index 899da28b3..a1299cd4d 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -53,6 +53,7 @@ dependencies {
implementation 'androidx.multidex:multidex:2.0.1'
implementation 'androidx.annotation:annotation:1.1.0'
implementation 'androidx.fragment:fragment:1.2.4'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
debugImplementation 'androidx.fragment:fragment-testing:1.2.4'
implementation project(':iterableapi')
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index a3609abf3..b3bc47ec8 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,13 +1,18 @@
-
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/iterable/androidsdk/AnonTrackingTestActivity.java b/app/src/main/java/com/iterable/androidsdk/AnonTrackingTestActivity.java
new file mode 100644
index 000000000..786b053bc
--- /dev/null
+++ b/app/src/main/java/com/iterable/androidsdk/AnonTrackingTestActivity.java
@@ -0,0 +1,205 @@
+package com.iterable.androidsdk;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.util.Log;
+import android.widget.CheckBox;
+import android.widget.EditText;
+
+import com.iterable.iterableapi.AuthFailure;
+import com.iterable.iterableapi.CommerceItem;
+import com.iterable.iterableapi.IterableAnonUserHandler;
+import com.iterable.iterableapi.IterableApi;
+import com.iterable.iterableapi.IterableAuthHandler;
+import com.iterable.iterableapi.IterableConfig;
+import com.iterable.iterableapi.IterableConstants;
+import com.iterable.iterableapi.testapp.R;
+import com.iterable.iterableapi.util.IterableJwtGenerator;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public class AnonTrackingTestActivity extends AppCompatActivity implements IterableAnonUserHandler, IterableAuthHandler {
+
+ private CheckBox anonymousUsageTrackedCheckBox;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_test);
+ anonymousUsageTrackedCheckBox = findViewById(R.id.anonymousUsageTracked_check_box);
+ IterableConfig iterableConfig = new IterableConfig.Builder().setEnableAnonActivation(true).setIterableAnonUserHandler(this).setAuthHandler(this).build();
+
+ // clear data for testing
+ SharedPreferences sharedPref = getSharedPreferences(IterableConstants.SHARED_PREFS_FILE, Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = sharedPref.edit();
+ editor.putString(IterableConstants.SHARED_PREFS_ANON_SESSIONS, "");
+ editor.putString(IterableConstants.SHARED_PREFS_EVENT_LIST_KEY, "");
+ editor.putBoolean(IterableConstants.SHARED_PREFS_VISITOR_USAGE_TRACKED, false);
+ editor.apply();
+
+ new Handler().postDelayed(() -> {
+ IterableApi.initialize(getBaseContext(), "bef41e4c1ab24b21bb3d65e47aa57a89", iterableConfig);
+ IterableApi.getInstance().setUserId(null);
+ IterableApi.getInstance().setEmail(null);
+ printAllSharedPreferencesData(this);
+ IterableApi.getInstance().setVisitorUsageTracked(anonymousUsageTrackedCheckBox.isChecked());
+
+ }, 1000);
+
+ anonymousUsageTrackedCheckBox.setOnCheckedChangeListener((buttonView, isChecked) -> {
+ IterableApi.getInstance().setVisitorUsageTracked(isChecked);
+ });
+
+ findViewById(R.id.updateCart).setOnClickListener(view -> {
+ EditText updateCart_edit = findViewById(R.id.updateCart_edit);
+ if(updateCart_edit == null) return;
+ Log.d("TEST_USER", String.valueOf(updateCart_edit.getText()));
+ try {
+ JSONArray cartJSOnItems = new JSONArray(String.valueOf(updateCart_edit.getText()));
+ List items = new ArrayList<>();
+ for(int i = 0; i < cartJSOnItems.length(); i++) {
+ final JSONObject item = cartJSOnItems.getJSONObject(i);
+ items.add(new CommerceItem(item.getString("id"), item.getString("name"), item.getDouble("price"), item.getInt("quantity")));
+ }
+ IterableApi.getInstance().updateCart(items);
+ } catch (JSONException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ findViewById(R.id.trackPurchase).setOnClickListener(view -> {
+ EditText purchase_items = findViewById(R.id.trackPurchase_edit);
+ if(purchase_items == null) return;
+ Log.d("TEST_USER", String.valueOf(purchase_items.getText()));
+
+ int total;
+
+ try {
+ JSONObject jsonData = new JSONObject(String.valueOf(purchase_items.getText()));
+ total = (int) jsonData.get("total");
+ JSONArray items_array = jsonData.getJSONArray("items");
+ List items = new ArrayList<>();
+ for(int i = 0; i < items_array.length(); i++) {
+ final JSONObject item = items_array.getJSONObject(i);
+ items.add(new CommerceItem(item.getString("id"), item.getString("name"), item.getDouble("price"), item.getInt("quantity")));
+ }
+ IterableApi.getInstance().trackPurchase(total, items);
+ } catch (JSONException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ findViewById(R.id.customEvent).setOnClickListener(view -> {
+ EditText customEvent_edit = findViewById(R.id.customEvent_edit);
+ if(customEvent_edit == null) return;
+ Log.d("TEST_USER", String.valueOf(customEvent_edit.getText()));
+
+ try {
+ JSONObject customEventItem = new JSONObject(String.valueOf(customEvent_edit.getText()));
+ JSONObject items = new JSONObject(customEventItem.get("dataFields").toString());
+ if(customEventItem.has("eventName")) {
+ items.put("eventName", customEventItem.getString("eventName"));
+ }
+ IterableApi.getInstance().track("customEvent", 0, 0, items);
+ } catch (JSONException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ findViewById(R.id.updateUser).setOnClickListener(view -> {
+ EditText updateUser_edit = findViewById(R.id.updateUser_edit);
+ if(updateUser_edit == null) return;
+ Log.d("TEST_USER", String.valueOf(updateUser_edit.getText()));
+
+ try {
+ JSONObject updateUserItem = new JSONObject(String.valueOf(updateUser_edit.getText()));
+ IterableApi.getInstance().updateUser(updateUserItem);
+
+ } catch (JSONException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ findViewById(R.id.setUser).setOnClickListener(view -> {
+ EditText setUser_edit = findViewById(R.id.setUser_edit);
+ if(setUser_edit == null) return;;
+ IterableApi.getInstance().setUserId(String.valueOf(setUser_edit.getText()));
+ });
+ findViewById(R.id.setEmail).setOnClickListener(view -> {
+ EditText setEmail_edit = findViewById(R.id.setEmail_edit);
+ if(setEmail_edit == null) return;
+ IterableApi.getInstance().setEmail(String.valueOf(setEmail_edit.getText()));
+ });
+
+ findViewById(R.id.btn_logout).setOnClickListener(view -> {
+ IterableApi.getInstance().setUserId(null);
+ IterableApi.getInstance().setEmail(null);
+ });
+
+ }
+ public void printAllSharedPreferencesData(Context context) {
+ SharedPreferences sharedPref = context.getSharedPreferences(IterableConstants.SHARED_PREFS_FILE, Context.MODE_PRIVATE);
+ Map allEntries = sharedPref.getAll();
+
+ for (Map.Entry entry : allEntries.entrySet()) {
+ Log.d("SharedPref", entry.getKey() + ": " + entry.getValue().toString());
+ }
+ }
+
+ @Override
+ public void onAnonUserCreated(String userId) {
+ Log.d("userId", userId);
+ }
+
+ @Override
+ public String onAuthTokenRequested() {
+ IterableApi instance = IterableApi.getInstance();
+ if (instance.getAuthToken() == null) {
+ final String secret = "1bb125ddcda2808f118c8b5e774d341c4b03fae68ebef0d140a22da1ec0295ad24d98981fd262c93bac98fa2e63a08142c0a36fe4322c09bea90f48c161780e0";
+ String userId;
+ String userEmail;
+ String jwtToken = null;
+ if (instance.getUserId() != null) {
+ userId = instance.getUserId(); // set as destination user Id
+ } else {
+ userId = null;
+ }
+ if (instance.getEmail() != null) {
+ userEmail = instance.getEmail(); // set as destination email Id
+ } else {
+ userEmail = null;
+ }
+ final Duration days7 = Duration.ofDays(7);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ jwtToken = IterableJwtGenerator.generateToken(secret, days7, userEmail, userId);
+ }
+ return jwtToken;
+ } else {
+ return instance.getAuthToken();
+ }
+ }
+
+ @Override
+ public void onTokenRegistrationSuccessful(String authToken) {
+ Log.d("Successful", authToken);
+ if (IterableApi.getInstance().getEmail() != null) {
+ Log.d("getEmail", IterableApi.getInstance().getEmail());
+ }
+ if (IterableApi.getInstance().getUserId() != null) {
+ Log.d("getUserId", IterableApi.getInstance().getUserId());
+ }
+ }
+
+ @Override
+ public void onAuthFailure(AuthFailure authFailure) {
+ Log.d("Failure", authFailure.failureReason.toString());
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/iterable/androidsdk/MainActivity.java b/app/src/main/java/com/iterable/androidsdk/MainActivity.java
index c5f0725b8..a788a14a5 100644
--- a/app/src/main/java/com/iterable/androidsdk/MainActivity.java
+++ b/app/src/main/java/com/iterable/androidsdk/MainActivity.java
@@ -1,16 +1,28 @@
package com.iterable.androidsdk;
+import android.content.Intent;
import android.os.Bundle;
+
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar;
+
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
+
import android.view.View;
import android.view.Menu;
import android.view.MenuItem;
+import com.iterable.iterableapi.CommerceItem;
+import com.iterable.iterableapi.IterableApi;
import com.iterable.iterableapi.testapp.R;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.List;
+
public class MainActivity extends AppCompatActivity {
@Override
@@ -19,6 +31,8 @@ protected void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
+ //Below api key is used to display merge user feature
+ IterableApi.initialize(this, "289895aa038648ee9e4ce60bd0a46e9c");
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@@ -28,6 +42,46 @@ public void onClick(View view) {
.setAction("Action", null).show();
}
});
+
+ findViewById(R.id.mainLayout).setOnLongClickListener(v -> {
+ Intent intent = new Intent(this, AnonTrackingTestActivity.class);
+ startActivity(intent);
+ return true;
+ });
+
+ findViewById(R.id.btn_track_event).setOnClickListener(v -> IterableApi.getInstance().track("Browse Mocha"));
+
+ findViewById(R.id.btn_update_cart).setOnClickListener(v -> {
+ List items = new ArrayList<>();
+ items.add(new CommerceItem("123", "Mocha", 1, 1));
+ IterableApi.getInstance().updateCart(items);
+ });
+
+ findViewById(R.id.btn_buy_mocha).setOnClickListener(v -> {
+ List items = new ArrayList<>();
+ items.add(new CommerceItem("456", "Black Coffee", 2, 1));
+ IterableApi.getInstance().trackPurchase(4, items);
+ });
+
+ findViewById(R.id.btn_buy_coffee).setOnClickListener(v -> {
+ List items = new ArrayList<>();
+ items.add(new CommerceItem("456", "Black Coffee", 5, 1));
+ IterableApi.getInstance().trackPurchase(5, items);
+ });
+
+ findViewById(R.id.btn_set_user).setOnClickListener(v -> IterableApi.getInstance().setUserId("hani7"));
+
+ findViewById(R.id.btn_update_user).setOnClickListener(v -> {
+ try {
+ JSONObject jsonObject = new JSONObject();
+ jsonObject.put("firstName", "Hani");
+ IterableApi.getInstance().updateUser(jsonObject);
+ } catch (JSONException e) {
+ throw new RuntimeException(e);
+ }
+ });
+
+ findViewById(R.id.btn_logout).setOnClickListener(view -> IterableApi.getInstance().setUserId(null));
}
@Override
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index f128d96bd..904a7bf1d 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -5,6 +5,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
+ android:id="@+id/mainLayout"
tools:context="com.iterable.androidsdk.MainActivity">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/content_main.xml b/app/src/main/res/layout/content_main.xml
index 5646874e1..a56897e97 100644
--- a/app/src/main/res/layout/content_main.xml
+++ b/app/src/main/res/layout/content_main.xml
@@ -1,5 +1,5 @@
-
+ tools:showIn="@layout/activity_main"
+ android:gravity="center"
+ android:orientation="vertical">
-
-
+ android:text="Browse Mocha" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/iterableapi-ui/build.gradle b/iterableapi-ui/build.gradle
index adf47bd7f..b4165c1c4 100644
--- a/iterableapi-ui/build.gradle
+++ b/iterableapi-ui/build.gradle
@@ -51,7 +51,7 @@ dependencies {
ext {
libraryName = 'iterableapi-ui'
- libraryVersion = '3.5.8'
+ libraryVersion = '3.6.0-beta1'
}
if (hasProperty("mavenPublishEnabled")) {
diff --git a/iterableapi/build.gradle b/iterableapi/build.gradle
index d0bcfbf9c..1fb34f13a 100644
--- a/iterableapi/build.gradle
+++ b/iterableapi/build.gradle
@@ -19,7 +19,7 @@ android {
minSdkVersion 16
targetSdkVersion 31
- buildConfigField "String", "ITERABLE_SDK_VERSION", "\"3.5.7\""
+ buildConfigField "String", "ITERABLE_SDK_VERSION", "\"3.6.0-beta1\""
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@@ -57,6 +57,7 @@ dependencies {
api 'androidx.appcompat:appcompat:1.0.0'
api 'androidx.annotation:annotation:1.0.0'
api 'com.google.firebase:firebase-messaging:20.3.0'
+ implementation 'com.google.code.gson:gson:2.10.1'
implementation "androidx.security:security-crypto:1.1.0-alpha06"
testImplementation 'junit:junit:4.13.2'
@@ -85,7 +86,7 @@ dependencies {
ext {
libraryName = 'iterableapi'
- libraryVersion = '3.5.8'
+ libraryVersion = '3.6.0-beta1'
}
if (hasProperty("mavenPublishEnabled")) {
diff --git a/iterableapi/src/androidTest/java/com/iterable/iterableapi/util/CombinationComplexCriteriaCheckerTest.java b/iterableapi/src/androidTest/java/com/iterable/iterableapi/util/CombinationComplexCriteriaCheckerTest.java
new file mode 100644
index 000000000..7814c976c
--- /dev/null
+++ b/iterableapi/src/androidTest/java/com/iterable/iterableapi/util/CombinationComplexCriteriaCheckerTest.java
@@ -0,0 +1,655 @@
+package com.iterable.iterableapi.util;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import org.json.JSONArray;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class CombinationComplexCriteriaCheckerTest {
+
+ private CriteriaCompletionChecker evaluator;
+
+ @Before
+ public void setUp() {
+ evaluator = new CriteriaCompletionChecker();
+ }
+
+ private final String complexCriteria1 = "{\n" +
+ " \"count\": 1,\n" +
+ " \"criteriaSets\": [\n" +
+ " {\n" +
+ " \"criteriaId\": \"290\",\n" +
+ " \"name\": \"Complex Criteria Unit Test #1\",\n" +
+ " \"createdAt\": 1722532861551,\n" +
+ " \"updatedAt\": 1722532861551,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"Or\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"firstName\",\n" +
+ " \"comparatorType\": \"StartsWith\",\n" +
+ " \"value\": \"A\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"firstName\",\n" +
+ " \"comparatorType\": \"StartsWith\",\n" +
+ " \"value\": \"B\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"firstName\",\n" +
+ " \"comparatorType\": \"StartsWith\",\n" +
+ " \"value\": \"C\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " },\n" +
+ " {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"field\": \"eventName\",\n" +
+ " \"comparatorType\": \"IsSet\",\n" +
+ " \"value\": \"\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"field\": \"saved_cars.color\",\n" +
+ " \"comparatorType\": \"IsSet\",\n" +
+ " \"value\": \"\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"field\": \"eventName\",\n" +
+ " \"comparatorType\": \"IsSet\",\n" +
+ " \"value\": \"\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"field\": \"animal-found.vaccinated\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": \"true\",\n" +
+ " \"fieldType\": \"boolean\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " },\n" +
+ " {\n" +
+ " \"combinator\": \"Not\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"purchase\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"purchase\",\n" +
+ " \"field\": \"total\",\n" +
+ " \"comparatorType\": \"LessThanOrEqualTo\",\n" +
+ " \"value\": \"100\",\n" +
+ " \"fieldType\": \"double\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"purchase\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"purchase\",\n" +
+ " \"field\": \"reason\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": \"testing\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }";
+
+ @Test
+ public void complexCriteria1TestPass() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"reason\": \"new\",\n" +
+ " \"total\": 10\n" +
+ " },\n" +
+ " \"eventType\": \"purchase\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"vaccinated\": true\n" +
+ " },\n" +
+ " \"eventName\": \"animal-found\",\n" +
+ " \"eventType\": \"customEvent\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"color\": \"black\"\n" +
+ " },\n" +
+ " \"eventName\": \"saved_cars\",\n" +
+ " \"eventType\": \"customEvent\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"firstName\": \"Adam\"\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(complexCriteria1, jsonArray);
+ assertNotNull(result);
+ }
+
+ @Test
+ public void complexCriteria1TestFail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"reason\": \"new\",\n" +
+ " \"total\": 10\n" +
+ " },\n" +
+ " \"eventType\": \"purchase\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"vaccinated\": true\n" +
+ " },\n" +
+ " \"eventName\": \"animal-found\",\n" +
+ " \"eventType\": \"customEvent\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"color\": \"\"\n" +
+ " },\n" +
+ " \"eventName\": \"saved_cars\",\n" +
+ " \"eventType\": \"customEvent\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"firstName\": \"Adam\"\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(complexCriteria1, jsonArray);
+ assertNull(result);
+ }
+
+ private final String complexCriteria2 = "{\n" +
+ " \"count\": 1,\n" +
+ " \"criteriaSets\": [\n" +
+ " {\n" +
+ " \"criteriaId\": \"291\",\n" +
+ " \"name\": \"Complex Criteria Unit Test #2\",\n" +
+ " \"createdAt\": 1722533473263,\n" +
+ " \"updatedAt\": 1722533473263,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"Or\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"Not\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"firstName\",\n" +
+ " \"comparatorType\": \"StartsWith\",\n" +
+ " \"value\": \"A\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"firstName\",\n" +
+ " \"comparatorType\": \"StartsWith\",\n" +
+ " \"value\": \"B\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"firstName\",\n" +
+ " \"comparatorType\": \"StartsWith\",\n" +
+ " \"value\": \"C\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " },\n" +
+ " {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"field\": \"eventName\",\n" +
+ " \"comparatorType\": \"IsSet\",\n" +
+ " \"value\": \"\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"field\": \"saved_cars.color\",\n" +
+ " \"comparatorType\": \"IsSet\",\n" +
+ " \"value\": \"\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"field\": \"animal-found.vaccinated\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": \"true\",\n" +
+ " \"fieldType\": \"boolean\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " },\n" +
+ " {\n" +
+ " \"combinator\": \"Or\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"purchase\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"purchase\",\n" +
+ " \"field\": \"total\",\n" +
+ " \"comparatorType\": \"GreaterThanOrEqualTo\",\n" +
+ " \"value\": \"100\",\n" +
+ " \"fieldType\": \"double\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"purchase\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"purchase\",\n" +
+ " \"field\": \"reason\",\n" +
+ " \"comparatorType\": \"DoesNotEqual\",\n" +
+ " \"value\": \"gift\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }";
+
+ @Test
+ public void complexCriteria2TestPass() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"reason\": \"new\",\n" +
+ " \"total\": 110\n" +
+ " },\n" +
+ " \"eventType\": \"purchase\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"vaccinated\": true\n" +
+ " },\n" +
+ " \"eventName\": \"animal-found\",\n" +
+ " \"eventType\": \"customEvent\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"color\": \"black\"\n" +
+ " },\n" +
+ " \"eventName\": \"saved_cars\",\n" +
+ " \"eventType\": \"customEvent\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"firstName\": \"Xcode\"\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(complexCriteria2, jsonArray);
+ assertNotNull(result);
+ }
+
+ @Test
+ public void complexCriteria2TestFail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"reason\": \"gift\",\n" +
+ " \"total\": 10\n" +
+ " },\n" +
+ " \"eventType\": \"purchase\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"firstName\": \"Alex\"\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(complexCriteria2, jsonArray);
+ assertNull(result);
+ }
+
+ private final String complexCriteria3 = "{\n" +
+ " \"count\": 1,\n" +
+ " \"criteriaSets\": [\n" +
+ " {\n" +
+ " \"criteriaId\": \"292\",\n" +
+ " \"name\": \"Complex Criteria Unit Test #3\",\n" +
+ " \"createdAt\": 1722533789589,\n" +
+ " \"updatedAt\": 1722533838989,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"Not\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"firstName\",\n" +
+ " \"comparatorType\": \"StartsWith\",\n" +
+ " \"value\": \"A\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"lastName\",\n" +
+ " \"comparatorType\": \"StartsWith\",\n" +
+ " \"value\": \"A\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " },\n" +
+ " {\n" +
+ " \"combinator\": \"Or\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"firstName\",\n" +
+ " \"comparatorType\": \"StartsWith\",\n" +
+ " \"value\": \"C\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"field\": \"animal-found.vaccinated\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": \"false\",\n" +
+ " \"fieldType\": \"boolean\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"field\": \"animal-found.count\",\n" +
+ " \"comparatorType\": \"LessThan\",\n" +
+ " \"value\": \"5\",\n" +
+ " \"fieldType\": \"long\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " },\n" +
+ " {\n" +
+ " \"combinator\": \"Not\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"purchase\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"purchase\",\n" +
+ " \"field\": \"total\",\n" +
+ " \"comparatorType\": \"LessThanOrEqualTo\",\n" +
+ " \"value\": \"10\",\n" +
+ " \"fieldType\": \"double\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"purchase\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"purchase\",\n" +
+ " \"field\": \"shoppingCartItems.quantity\",\n" +
+ " \"comparatorType\": \"LessThanOrEqualTo\",\n" +
+ " \"value\": \"34\",\n" +
+ " \"fieldType\": \"long\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }";
+
+
+ @Test
+ public void complexCriteria3TestPass() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"eventType\": \"purchase\",\n" +
+ " \"createdAt\": 1699246745093,\n" +
+ " \"items\": [{\n" +
+ " \"id\": \"12\",\n" +
+ " \"name\": \"coffee\",\n" +
+ " \"price\": \"100\",\n" +
+ " \"quantity\": \"2\"\n" +
+ " }]\n" +
+ " }\n" +
+ "]";
+
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(complexCriteria3, jsonArray);
+ assertNotNull(result);
+ }
+
+ @Test
+ public void complexCriteria3TestPass2() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"eventType\": \"purchase\",\n" +
+ " \"createdAt\": 1699246745067,\n" +
+ " \"items\": [{\n" +
+ " \"id\": \"13\",\n" +
+ " \"name\": \"kittens\",\n" +
+ " \"price\": \"2\",\n" +
+ " \"quantity\": \"2\"\n" +
+ " }]\n" +
+ " }\n" +
+ "]";
+
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(complexCriteria3, jsonArray);
+ assertNotNull(result);
+ }
+
+ @Test
+ public void complexCriteria3TestFail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"eventType\": \"purchase\",\n" +
+ " \"createdAt\": 1699246745093,\n" +
+ " \"items\": [{\n" +
+ " \"id\": \"12\",\n" +
+ " \"name\": \"coffee\",\n" +
+ " \"price\": \"100\",\n" +
+ " \"quantity\": \"2\"\n" +
+ " }]\n" +
+ " },\n" +
+ " {\n" +
+ " \"eventType\": \"user\",\n" +
+ " \"dataFields\": {\n" +
+ " \"firstName\": \"Alex\",\n" +
+ " \"lastName\": \"Aris\"\n" +
+ " }\n" +
+ " }\n" +
+ "]";
+
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(complexCriteria3, jsonArray);
+ assertNull(result);
+ }
+}
diff --git a/iterableapi/src/androidTest/java/com/iterable/iterableapi/util/CombinationLogicEventTypeCriteriaTest.java b/iterableapi/src/androidTest/java/com/iterable/iterableapi/util/CombinationLogicEventTypeCriteriaTest.java
new file mode 100644
index 000000000..16cdab180
--- /dev/null
+++ b/iterableapi/src/androidTest/java/com/iterable/iterableapi/util/CombinationLogicEventTypeCriteriaTest.java
@@ -0,0 +1,1194 @@
+package com.iterable.iterableapi.util;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import org.json.JSONArray;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class CombinationLogicEventTypeCriteriaTest {
+
+ private CriteriaCompletionChecker evaluator;
+
+ @Before
+ public void setUp() {
+ evaluator = new CriteriaCompletionChecker();
+ }
+
+ static final String mockDataCombinatorContactPropertyANDCustomEvent = "{\n" +
+ " \"count\": 1,\n" +
+ " \"criteriaSets\": [\n" +
+ " {\n" +
+ " \"criteriaId\": \"310\",\n" +
+ " \"name\": \"Contact_Property_AND_Custom_Event\",\n" +
+ " \"createdAt\": 1723113771608,\n" +
+ " \"updatedAt\": 1723113771608,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"firstName\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": \"David\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"field\": \"total\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": \"10\",\n" +
+ " \"fieldType\": \"double\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+
+ @Test
+ public void testCompareDataContactPropertyANDCustomEventPass() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"eventType\": \"user\",\n" +
+ " \"dataFields\": {\n" +
+ " \"firstName\": \"David\"\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"total\": \"10\"\n" +
+ " },\n" +
+ " \"eventType\": \"customEvent\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataCombinatorContactPropertyANDCustomEvent, jsonArray);
+ assertTrue(result != null);
+ }
+ @Test
+ public void testCompareDataContactPropertyANDCustomEventFail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"eventType\": \"user\",\n" +
+ " \"dataFields\": {\n" +
+ " \"firstName\": \"DavidJohn\"\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"total\": \"11\"\n" +
+ " },\n" +
+ " \"eventType\": \"customEvent\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataCombinatorContactPropertyANDCustomEvent, jsonArray);
+ assertFalse(result != null);
+ }
+
+ static final String mockDataCombinatorContactPropertyORCustomEvent = "{\n" +
+ " \"count\": 1,\n" +
+ " \"criteriaSets\": [\n" +
+ " {\n" +
+ " \"criteriaId\": \"312\",\n" +
+ " \"name\": \"Contact_Property_OR_Custom_Event\",\n" +
+ " \"createdAt\": 1723115120517,\n" +
+ " \"updatedAt\": 1723115120517,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"Or\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"firstName\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": \"David\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"field\": \"total\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": \"10\",\n" +
+ " \"fieldType\": \"double\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+ @Test
+ public void testCompareDataContactPropertyORCustomEventPass() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"eventType\": \"user\",\n" +
+ " \"dataFields\": {\n" +
+ " \"firstName\": \"David\"\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"total\": \"10\"\n" +
+ " },\n" +
+ " \"eventType\": \"customEvent\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataCombinatorContactPropertyORCustomEvent, jsonArray);
+ assertTrue(result != null);
+ }
+ @Test
+ public void testCompareDataContactPropertyORCustomEventFail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"eventType\": \"user\",\n" +
+ " \"dataFields\": {\n" +
+ " \"firstName\": \"DavidAs\"\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"total\": \"101\"\n" +
+ " },\n" +
+ " \"eventType\": \"customEvent\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataCombinatorContactPropertyORCustomEvent, jsonArray);
+ assertFalse(result != null);
+ }
+
+ static final String mockDataCombinatorContactPropertyNOTCustomEvent = "{\n" +
+ " \"count\": 1,\n" +
+ " \"criteriaSets\": [\n" +
+ " {\n" +
+ " \"criteriaId\": \"312\",\n" +
+ " \"name\": \"Contact_Property_NOT_Custom_Event\",\n" +
+ " \"createdAt\": 1723115120517,\n" +
+ " \"updatedAt\": 1723115120517,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"Not\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"firstName\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": \"David\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"field\": \"total\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": \"10\",\n" +
+ " \"fieldType\": \"double\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+
+ @Test
+ public void testCompareDataContactPropertyNOTCustomEventPass() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"eventType\": \"user\",\n" +
+ " \"dataFields\": {\n" +
+ " \"firstName\": \"Davidson\"\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"total\": \"1\"\n" +
+ " },\n" +
+ " \"eventType\": \"customEvent\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataCombinatorContactPropertyNOTCustomEvent, jsonArray);
+ assertTrue(result != null);
+ }
+ @Test
+ public void testCompareDataContactPropertyNOTCustomEventFail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"eventType\": \"user\",\n" +
+ " \"dataFields\": {\n" +
+ " \"firstName\": \"David\"\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"total\": \"10\"\n" +
+ " },\n" +
+ " \"eventType\": \"customEvent\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataCombinatorContactPropertyNOTCustomEvent, jsonArray);
+ assertFalse(result != null);
+ }
+
+ static final String mockDataCombinatorUpdateCartANDContactProperty = "{\n" +
+ " \"count\": 1,\n" +
+ " \"criteriaSets\": [\n" +
+ " {\n" +
+ " \"criteriaId\": \"315\",\n" +
+ " \"name\": \"UpdateCart_AND_ContactProperty\",\n" +
+ " \"createdAt\": 1723119153268,\n" +
+ " \"updatedAt\": 1723119153268,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"field\": \"updateCart.updatedShoppingCartItems.name\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": \"fried\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"firstName\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": \"David\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+ @Test
+ public void testCompareDataUpdateCartANDContactPropertyPass() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"items\": [\n" +
+ " {\n" +
+ " \"name\": \"fried\"\n" +
+ " }\n" +
+ " ],\n" +
+ " \"eventType\": \"cartUpdate\"\n" +
+ " }, \n" +
+ " {\n" +
+ " \"eventType\": \"user\",\n" +
+ " \"dataFields\": {\n" +
+ " \"firstName\": \"David\"\n" +
+ " }\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataCombinatorUpdateCartANDContactProperty, jsonArray);
+ assertTrue(result != null);
+ }
+ @Test
+ public void testCompareDataUpdateCartANDContactPropertyFail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"items\": [\n" +
+ " {\n" +
+ " \"name\": \"boiled\"\n" +
+ " }\n" +
+ " ],\n" +
+ " \"eventType\": \"cartUpdate\"\n" +
+ " }, \n" +
+ " {\n" +
+ " \"eventType\": \"user\",\n" +
+ " \"dataFields\": {\n" +
+ " \"firstName\": \"David\"\n" +
+ " }\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataCombinatorUpdateCartANDContactProperty, jsonArray);
+ assertFalse(result != null);
+ }
+
+ static final String mockDataCombinatorUpdateCartORContactProperty = "{\n" +
+ " \"count\": 1,\n" +
+ " \"criteriaSets\": [\n" +
+ " {\n" +
+ " \"criteriaId\": \"315\",\n" +
+ " \"name\": \"UpdateCart_OR_ContactProperty\",\n" +
+ " \"createdAt\": 1723119153268,\n" +
+ " \"updatedAt\": 1723119153268,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"Or\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"field\": \"updateCart.updatedShoppingCartItems.name\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": \"fried\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"firstName\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": \"David\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+ @Test
+ public void testCompareDataUpdateCartORContactPropertyPass() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"items\": [\n" +
+ " {\n" +
+ " \"name\": \"fried\"\n" +
+ " }\n" +
+ " ],\n" +
+ " \"eventType\": \"cartUpdate\"\n" +
+ " }, \n" +
+ " {\n" +
+ " \"eventType\": \"user\",\n" +
+ " \"dataFields\": {\n" +
+ " \"firstName\": \"David\"\n" +
+ " }\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataCombinatorUpdateCartORContactProperty, jsonArray);
+ assertTrue(result != null);
+ }
+
+ @Test
+ public void testCompareDataUpdateCartORContactPropertyFail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"items\": [\n" +
+ " {\n" +
+ " \"name\": \"boiled\"\n" +
+ " }\n" +
+ " ],\n" +
+ " \"eventType\": \"cartUpdate\"\n" +
+ " }, \n" +
+ " {\n" +
+ " \"eventType\": \"user\",\n" +
+ " \"dataFields\": {\n" +
+ " \"firstName\": \"John\"\n" +
+ " }\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataCombinatorUpdateCartORContactProperty, jsonArray);
+ assertFalse(result != null);
+ }
+
+ static final String mockDataCombinatorUpdateCartNOTContactProperty = "{\n" +
+ " \"count\": 1,\n" +
+ " \"criteriaSets\": [\n" +
+ " {\n" +
+ " \"criteriaId\": \"315\",\n" +
+ " \"name\": \"UpdateCart_NOT_ContactProperty\",\n" +
+ " \"createdAt\": 1723119153268,\n" +
+ " \"updatedAt\": 1723119153268,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"Not\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"field\": \"updateCart.updatedShoppingCartItems.name\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": \"fried\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"firstName\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": \"David\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+ @Test
+ public void testCompareDataUpdateCartNOTContactPropertyPass() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"items\": [\n" +
+ " {\n" +
+ " \"name\": \"boiled\"\n" +
+ " }\n" +
+ " ],\n" +
+ " \"eventType\": \"cartUpdate\"\n" +
+ " }, \n" +
+ " {\n" +
+ " \"eventType\": \"user\",\n" +
+ " \"dataFields\": {\n" +
+ " \"firstName\": \"DavidJohn\"\n" +
+ " }\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataCombinatorUpdateCartNOTContactProperty, jsonArray);
+ assertTrue(result != null);
+ }
+
+ @Test
+ public void testCompareDataUpdateCartNOTContactPropertyFail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"items\": [\n" +
+ " {\n" +
+ " \"name\": \"fried\"\n" +
+ " }\n" +
+ " ],\n" +
+ " \"eventType\": \"cartUpdate\"\n" +
+ " }, \n" +
+ " {\n" +
+ " \"eventType\": \"user\",\n" +
+ " \"dataFields\": {\n" +
+ " \"firstName\": \"David\"\n" +
+ " }\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataCombinatorUpdateCartNOTContactProperty, jsonArray);
+ assertFalse(result != null);
+ }
+
+ static final String mockDataCombinatorPurchaseANDUpdateCart = "{\n" +
+ " \"count\": 1,\n" +
+ " \"criteriaSets\": [\n" +
+ " {\n" +
+ " \"criteriaId\": \"316\",\n" +
+ " \"name\": \"Purchase_AND_UpdateCart\",\n" +
+ " \"createdAt\": 1723124161944,\n" +
+ " \"updatedAt\": 1723124205406,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"purchase\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"purchase\",\n" +
+ " \"field\": \"shoppingCartItems.name\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": \"chicken\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"field\": \"updateCart.updatedShoppingCartItems.name\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": \"fried\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+ @Test
+ public void testCompareDataPurchaseANDUpdateCartPass() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"items\": [\n" +
+ " {\n" +
+ " \"name\": \"fried\"\n" +
+ " }\n" +
+ " ],\n" +
+ " \"eventType\": \"cartUpdate\"\n" +
+ " }, \n" +
+ " {\n" +
+ " \"items\": [\n" +
+ " {\n" +
+ " \"name\": \"chicken\"\n" +
+ " }\n" +
+ " ],\n" +
+ " \"eventType\": \"purchase\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataCombinatorPurchaseANDUpdateCart, jsonArray);
+ assertTrue(result != null);
+ }
+
+ @Test
+ public void testCompareDataPurchaseANDUpdateCartFail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"items\": [\n" +
+ " {\n" +
+ " \"name\": \"boiled\"\n" +
+ " }\n" +
+ " ],\n" +
+ " \"eventType\": \"cartUpdate\"\n" +
+ " }, \n" +
+ " {\n" +
+ " \"items\": [\n" +
+ " {\n" +
+ " \"name\": \"chicken\"\n" +
+ " }\n" +
+ " ],\n" +
+ " \"eventType\": \"purchase\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataCombinatorPurchaseANDUpdateCart, jsonArray);
+ assertFalse(result != null);
+ }
+
+ static final String mockDataCombinatorPurchaseORUpdateCart = "{\n" +
+ " \"count\": 1,\n" +
+ " \"criteriaSets\": [\n" +
+ " {\n" +
+ " \"criteriaId\": \"316\",\n" +
+ " \"name\": \"Purchase_OR_UpdateCart\",\n" +
+ " \"createdAt\": 1723124161944,\n" +
+ " \"updatedAt\": 1723124205406,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"Or\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"purchase\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"purchase\",\n" +
+ " \"field\": \"shoppingCartItems.name\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": \"chicken\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"field\": \"updateCart.updatedShoppingCartItems.name\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": \"fried\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+ @Test
+ public void testCompareDataPurchaseORUpdateCartPass() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"items\": [\n" +
+ " {\n" +
+ " \"name\": \"fried\"\n" +
+ " }\n" +
+ " ],\n" +
+ " \"eventType\": \"cartUpdate\"\n" +
+ " }, \n" +
+ " {\n" +
+ " \"items\": [\n" +
+ " {\n" +
+ " \"name\": \"chicken\"\n" +
+ " }\n" +
+ " ],\n" +
+ " \"eventType\": \"purchase\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataCombinatorPurchaseORUpdateCart, jsonArray);
+ assertTrue(result != null);
+ }
+
+ @Test
+ public void testCompareDataPurchaseORUpdateCartFail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"items\": [\n" +
+ " {\n" +
+ " \"name\": \"boiled\"\n" +
+ " }\n" +
+ " ],\n" +
+ " \"eventType\": \"cartUpdate\"\n" +
+ " }, \n" +
+ " {\n" +
+ " \"items\": [\n" +
+ " {\n" +
+ " \"name\": \"beef\"\n" +
+ " }\n" +
+ " ],\n" +
+ " \"eventType\": \"purchase\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataCombinatorPurchaseORUpdateCart, jsonArray);
+ assertFalse(result != null);
+ }
+
+ static final String mockDataCombinatorPurchaseNOTUpdateCart = "{\n" +
+ " \"count\": 1,\n" +
+ " \"criteriaSets\": [\n" +
+ " {\n" +
+ " \"criteriaId\": \"316\",\n" +
+ " \"name\": \"Purchase_NOT_UpdateCart\",\n" +
+ " \"createdAt\": 1723124161944,\n" +
+ " \"updatedAt\": 1723124205406,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"Not\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"purchase\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"purchase\",\n" +
+ " \"field\": \"shoppingCartItems.name\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": \"chicken\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"field\": \"updateCart.updatedShoppingCartItems.name\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": \"fried\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+ @Test
+ public void testCompareDataPurchaseNOTUpdateCartPass() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"items\": [\n" +
+ " {\n" +
+ " \"name\": \"boiled\"\n" +
+ " }\n" +
+ " ],\n" +
+ " \"eventType\": \"cartUpdate\"\n" +
+ " }, \n" +
+ " {\n" +
+ " \"items\": [\n" +
+ " {\n" +
+ " \"name\": \"beef\"\n" +
+ " }\n" +
+ " ],\n" +
+ " \"eventType\": \"purchase\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataCombinatorPurchaseNOTUpdateCart, jsonArray);
+ assertTrue(result != null);
+ }
+
+ @Test
+ public void testCompareDataPurchaseNOTUpdateCartFail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"items\": [\n" +
+ " {\n" +
+ " \"name\": \"fried\"\n" +
+ " }\n" +
+ " ],\n" +
+ " \"eventType\": \"cartUpdate\"\n" +
+ " }, \n" +
+ " {\n" +
+ " \"items\": [\n" +
+ " {\n" +
+ " \"name\": \"chicken\"\n" +
+ " }\n" +
+ " ],\n" +
+ " \"eventType\": \"purchase\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataCombinatorPurchaseNOTUpdateCart, jsonArray);
+ assertFalse(result != null);
+ }
+
+ static final String mockDataCombinatorCustomEventANDPurchase = "{\n" +
+ " \"count\": 1,\n" +
+ " \"criteriaSets\": [\n" +
+ " {\n" +
+ " \"criteriaId\": \"320\",\n" +
+ " \"name\": \"CustomEvent_AND_Purchase\",\n" +
+ " \"createdAt\": 1723184939510,\n" +
+ " \"updatedAt\": 1723184939510,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"field\": \"eventName\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": \"birthday\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"purchase\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"purchase\",\n" +
+ " \"field\": \"shoppingCartItems.name\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": \"chicken\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+ @Test
+ public void testCompareDataCustomEventANDPurchasePass() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"eventType\": \"customEvent\",\n" +
+ " \"dataFields\": {\n" +
+ " \"eventName\": \"birthday\"\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"items\": [\n" +
+ " {\n" +
+ " \"name\": \"chicken\"\n" +
+ " }\n" +
+ " ],\n" +
+ " \"eventType\": \"purchase\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataCombinatorCustomEventANDPurchase, jsonArray);
+ assertTrue(result != null);
+ }
+
+ @Test
+ public void testCompareDataCustomEventANDPurchaseFail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"eventType\": \"customEvent\",\n" +
+ " \"dataFields\": {\n" +
+ " \"eventName\": \"birthday\"\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"items\": [\n" +
+ " {\n" +
+ " \"name\": \"beef\"\n" +
+ " }\n" +
+ " ],\n" +
+ " \"eventType\": \"purchase\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataCombinatorCustomEventANDPurchase, jsonArray);
+ assertFalse(result != null);
+ }
+
+ static final String mockDataCombinatorCustomEventORPurchase = "{\n" +
+ " \"count\": 1,\n" +
+ " \"criteriaSets\": [\n" +
+ " {\n" +
+ " \"criteriaId\": \"320\",\n" +
+ " \"name\": \"CustomEvent_OR_Purchase\",\n" +
+ " \"createdAt\": 1723184939510,\n" +
+ " \"updatedAt\": 1723184939510,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"Or\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"field\": \"eventName\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": \"birthday\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"purchase\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"purchase\",\n" +
+ " \"field\": \"shoppingCartItems.name\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": \"chicken\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+ @Test
+ public void testCompareDataCustomEventORPurchasePass() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"eventType\": \"customEvent\",\n" +
+ " \"dataFields\": {\n" +
+ " \"eventName\": \"birthday\"\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"items\": [\n" +
+ " {\n" +
+ " \"name\": \"beef\"\n" +
+ " }\n" +
+ " ],\n" +
+ " \"eventType\": \"purchase\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataCombinatorCustomEventORPurchase, jsonArray);
+ assertTrue(result != null);
+ }
+
+ @Test
+ public void testCompareDataCustomEventORPurchaseFail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"eventType\": \"customEvent\",\n" +
+ " \"dataFields\": {\n" +
+ " \"eventName\": \"anniversary\"\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"items\": [\n" +
+ " {\n" +
+ " \"name\": \"beef\"\n" +
+ " }\n" +
+ " ],\n" +
+ " \"eventType\": \"purchase\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataCombinatorCustomEventORPurchase, jsonArray);
+ assertFalse(result != null);
+ }
+
+ static final String mockDataCombinatorCustomEventNOTPurchase = "{\n" +
+ " \"count\": 1,\n" +
+ " \"criteriaSets\": [\n" +
+ " {\n" +
+ " \"criteriaId\": \"320\",\n" +
+ " \"name\": \"CustomEvent_NOT_Purchase\",\n" +
+ " \"createdAt\": 1723184939510,\n" +
+ " \"updatedAt\": 1723184939510,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"Not\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"field\": \"eventName\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": \"birthday\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"purchase\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"purchase\",\n" +
+ " \"field\": \"shoppingCartItems.name\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": \"chicken\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+ @Test
+ public void testCompareDataCustomEventNOTPurchasePass() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"eventType\": \"customEvent\",\n" +
+ " \"dataFields\": {\n" +
+ " \"eventName\": \"birthday1\"\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"items\": [\n" +
+ " {\n" +
+ " \"name\": \"chicken\"\n" +
+ " }\n" +
+ " ],\n" +
+ " \"eventType\": \"purchase\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataCombinatorCustomEventNOTPurchase, jsonArray);
+ assertTrue(result != null);
+ }
+
+ @Test
+ public void testCompareDataCustomEventNOTPurchaseFail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"eventType\": \"customEvent\",\n" +
+ " \"dataFields\": {\n" +
+ " \"eventName\": \"birthday\"\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"items\": [\n" +
+ " {\n" +
+ " \"name\": \"chicken\"\n" +
+ " }\n" +
+ " ],\n" +
+ " \"eventType\": \"purchase\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataCombinatorCustomEventNOTPurchase, jsonArray);
+ assertFalse(result != null);
+ }
+}
diff --git a/iterableapi/src/androidTest/java/com/iterable/iterableapi/util/ComplexCriteriaCheckerTest.java b/iterableapi/src/androidTest/java/com/iterable/iterableapi/util/ComplexCriteriaCheckerTest.java
new file mode 100644
index 000000000..e4002078c
--- /dev/null
+++ b/iterableapi/src/androidTest/java/com/iterable/iterableapi/util/ComplexCriteriaCheckerTest.java
@@ -0,0 +1,834 @@
+package com.iterable.iterableapi.util;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.json.JSONArray;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class ComplexCriteriaCheckerTest {
+ private CriteriaCompletionChecker evaluator;
+
+ private String criteriaMinMax = "{"
+ + "\"count\": 1,"
+ + "\"criteriaSets\": ["
+ + " {"
+ + " \"criteriaId\": \"1\","
+ + " \"name\": \"Custom Event\","
+ + " \"createdAt\": 1716560453973,"
+ + " \"updatedAt\": 1716560453973,"
+ + " \"searchQuery\": {"
+ + " \"combinator\": \"And\","
+ + " \"searchQueries\": ["
+ + " {"
+ + " \"combinator\": \"And\","
+ + " \"searchQueries\": ["
+ + " {"
+ + " \"dataType\": \"customEvent\","
+ + " \"minMatch\": 2,"
+ + " \"maxMatch\": 3,"
+ + " \"searchCombo\": {"
+ + " \"combinator\": \"And\","
+ + " \"searchQueries\": ["
+ + " {"
+ + " \"field\": \"updateCart.updatedShoppingCartItems.price\","
+ + " \"fieldType\": \"double\","
+ + " \"comparatorType\": \"GreaterThanOrEqualTo\","
+ + " \"dataType\": \"customEvent\","
+ + " \"id\": 2,"
+ + " \"value\": \"50.0\""
+ + " }"
+ + " ]"
+ + " }"
+ + " },"
+ + " {"
+ + " \"dataType\": \"user\","
+ + " \"searchCombo\": {"
+ + " \"combinator\": \"And\","
+ + " \"searchQueries\": ["
+ + " {"
+ + " \"field\": \"preferred_car_models\","
+ + " \"fieldType\": \"string\","
+ + " \"comparatorType\": \"Contains\","
+ + " \"dataType\": \"user\","
+ + " \"id\": 4,"
+ + " \"value\": \"Honda\""
+ + " }"
+ + " ]"
+ + " }"
+ + " }"
+ + " ]"
+ + " }"
+ + " ]"
+ + " }"
+ + " }"
+ + "]"
+ + "}";
+
+ private String complexCriteria1 = "{\n" +
+ " \"count\": 1,\n" +
+ " \"criteriaSets\": [\n" +
+ " {\n" +
+ " \"criteriaId\": \"1\",\n" +
+ " \"name\": \"Custom Event\",\n" +
+ " \"createdAt\": 1716560453973,\n" +
+ " \"updatedAt\": 1716560453973,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"Or\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"field\": \"eventName\",\n" +
+ " \"fieldType\": \"string\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"id\": 23,\n" +
+ " \"value\": \"button.clicked\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"field\": \"button-clicked.animal\",\n" +
+ " \"fieldType\": \"string\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"id\": 25,\n" +
+ " \"value\": \"giraffe\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"field\": \"updateCart.updatedShoppingCartItems.price\",\n" +
+ " \"fieldType\": \"double\",\n" +
+ " \"comparatorType\": \"GreaterThanOrEqualTo\",\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"id\": 28,\n" +
+ " \"value\": \"120\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"field\": \"updateCart.updatedShoppingCartItems.quantity\",\n" +
+ " \"fieldType\": \"long\",\n" +
+ " \"comparatorType\": \"GreaterThanOrEqualTo\",\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"id\": 29,\n" +
+ " \"valueLong\": 100,\n" +
+ " \"value\": \"100\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " },\n" +
+ " {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"purchase\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"field\": \"shoppingCartItems.name\",\n" +
+ " \"fieldType\": \"string\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"dataType\": \"purchase\",\n" +
+ " \"id\": 31,\n" +
+ " \"value\": \"monitor\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"field\": \"shoppingCartItems.quantity\",\n" +
+ " \"fieldType\": \"long\",\n" +
+ " \"comparatorType\": \"GreaterThanOrEqualTo\",\n" +
+ " \"dataType\": \"purchase\",\n" +
+ " \"id\": 32,\n" +
+ " \"valueLong\": 5,\n" +
+ " \"value\": \"5\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"field\": \"country\",\n" +
+ " \"fieldType\": \"string\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"id\": 34,\n" +
+ " \"value\": \"Japan\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"field\": \"preferred_car_models\",\n" +
+ " \"fieldType\": \"string\",\n" +
+ " \"comparatorType\": \"Contains\",\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"id\": 36,\n" +
+ " \"value\": \"Honda\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+ private String complexCriteria2 = "{"
+ + "\"count\": 1,"
+ + "\"criteriaSets\": ["
+ + " {"
+ + " \"criteriaId\": \"1\","
+ + " \"name\": \"Custom Event\","
+ + " \"createdAt\": 1716560453973,"
+ + " \"updatedAt\": 1716560453973,"
+ + " \"searchQuery\": {"
+ + " \"combinator\": \"And\","
+ + " \"searchQueries\": ["
+ + " {"
+ + " \"combinator\": \"Or\","
+ + " \"searchQueries\": ["
+ + " {"
+ + " \"dataType\": \"customEvent\","
+ + " \"searchCombo\": {"
+ + " \"combinator\": \"And\","
+ + " \"searchQueries\": ["
+ + " {"
+ + " \"field\": \"eventName\","
+ + " \"fieldType\": \"string\","
+ + " \"comparatorType\": \"Equals\","
+ + " \"dataType\": \"customEvent\","
+ + " \"id\": 2,"
+ + " \"value\": \"button-clicked\""
+ + " },"
+ + " {"
+ + " \"field\": \"button-clicked.lastPageViewed\","
+ + " \"fieldType\": \"string\","
+ + " \"comparatorType\": \"Equals\","
+ + " \"dataType\": \"customEvent\","
+ + " \"id\": 4,"
+ + " \"value\": \"welcome page\""
+ + " }"
+ + " ]"
+ + " }"
+ + " },"
+ + " {"
+ + " \"dataType\": \"customEvent\","
+ + " \"minMatch\": 2,"
+ + " \"maxMatch\": 3,"
+ + " \"searchCombo\": {"
+ + " \"combinator\": \"And\","
+ + " \"searchQueries\": ["
+ + " {"
+ + " \"field\": \"updateCart.updatedShoppingCartItems.price\","
+ + " \"fieldType\": \"double\","
+ + " \"comparatorType\": \"GreaterThanOrEqualTo\","
+ + " \"dataType\": \"customEvent\","
+ + " \"id\": 6,"
+ + " \"value\": \"85\""
+ + " },"
+ + " {"
+ + " \"field\": \"updateCart.updatedShoppingCartItems.quantity\","
+ + " \"fieldType\": \"long\","
+ + " \"comparatorType\": \"GreaterThanOrEqualTo\","
+ + " \"dataType\": \"customEvent\","
+ + " \"id\": 7,"
+ + " \"valueLong\": 50,"
+ + " \"value\": \"50\""
+ + " }"
+ + " ]"
+ + " }"
+ + " }"
+ + " ]"
+ + " },"
+ + " {"
+ + " \"combinator\": \"Or\","
+ + " \"searchQueries\": ["
+ + " {"
+ + " \"dataType\": \"purchase\","
+ + " \"searchCombo\": {"
+ + " \"combinator\": \"And\","
+ + " \"searchQueries\": ["
+ + " {"
+ + " \"field\": \"shoppingCartItems.name\","
+ + " \"fieldType\": \"string\","
+ + " \"comparatorType\": \"Equals\","
+ + " \"dataType\": \"purchase\","
+ + " \"id\": 16,"
+ + " \"isFiltering\": false,"
+ + " \"value\": \"coffee\""
+ + " },"
+ + " {"
+ + " \"field\": \"shoppingCartItems.quantity\","
+ + " \"fieldType\": \"long\","
+ + " \"comparatorType\": \"GreaterThanOrEqualTo\","
+ + " \"dataType\": \"purchase\","
+ + " \"id\": 17,"
+ + " \"valueLong\": 2,"
+ + " \"value\": \"2\""
+ + " }"
+ + " ]"
+ + " }"
+ + " },"
+ + " {"
+ + " \"dataType\": \"user\","
+ + " \"searchCombo\": {"
+ + " \"combinator\": \"And\","
+ + " \"searchQueries\": ["
+ + " {"
+ + " \"field\": \"country\","
+ + " \"fieldType\": \"string\","
+ + " \"comparatorType\": \"Equals\","
+ + " \"dataType\": \"user\","
+ + " \"id\": 19,"
+ + " \"value\": \"USA\""
+ + " },"
+ + " {"
+ + " \"field\": \"preferred_car_models\","
+ + " \"fieldType\": \"string\","
+ + " \"comparatorType\": \"Contains\","
+ + " \"dataType\": \"user\","
+ + " \"id\": 21,"
+ + " \"value\": \"Subaru\""
+ + " }"
+ + " ]"
+ + " }"
+ + " }"
+ + " ]"
+ + " }"
+ + " ]"
+ + " }"
+ + " }"
+ + "]"
+ + "}";
+
+ private String complexCriteria3 = "{"
+ + "\"count\": 1,"
+ + "\"criteriaSets\": ["
+ + " {"
+ + " \"criteriaId\": \"1\","
+ + " \"name\": \"Custom Event\","
+ + " \"createdAt\": 1716560453973,"
+ + " \"updatedAt\": 1716560453973,"
+ + " \"searchQuery\": {"
+ + " \"combinator\": \"And\","
+ + " \"searchQueries\": ["
+ + " {"
+ + " \"combinator\": \"And\","
+ + " \"searchQueries\": ["
+ + " {"
+ + " \"dataType\": \"customEvent\","
+ + " \"searchCombo\": {"
+ + " \"combinator\": \"And\","
+ + " \"searchQueries\": ["
+ + " {"
+ + " \"field\": \"eventName\","
+ + " \"fieldType\": \"string\","
+ + " \"comparatorType\": \"Equals\","
+ + " \"dataType\": \"customEvent\","
+ + " \"id\": 2,"
+ + " \"value\": \"button-clicked\""
+ + " },"
+ + " {"
+ + " \"field\": \"button-clicked.lastPageViewed\","
+ + " \"fieldType\": \"string\","
+ + " \"comparatorType\": \"Equals\","
+ + " \"dataType\": \"customEvent\","
+ + " \"id\": 4,"
+ + " \"value\": \"welcome page\""
+ + " }"
+ + " ]"
+ + " }"
+ + " },"
+ + " {"
+ + " \"dataType\": \"customEvent\","
+ + " \"minMatch\": 2,"
+ + " \"maxMatch\": 3,"
+ + " \"searchCombo\": {"
+ + " \"combinator\": \"And\","
+ + " \"searchQueries\": ["
+ + " {"
+ + " \"field\": \"updateCart.updatedShoppingCartItems.price\","
+ + " \"fieldType\": \"double\","
+ + " \"comparatorType\": \"GreaterThanOrEqualTo\","
+ + " \"dataType\": \"customEvent\","
+ + " \"id\": 6,"
+ + " \"value\": \"85\""
+ + " },"
+ + " {"
+ + " \"field\": \"updateCart.updatedShoppingCartItems.quantity\","
+ + " \"fieldType\": \"long\","
+ + " \"comparatorType\": \"GreaterThanOrEqualTo\","
+ + " \"dataType\": \"customEvent\","
+ + " \"id\": 7,"
+ + " \"valueLong\": 50,"
+ + " \"value\": \"50\""
+ + " }"
+ + " ]"
+ + " }"
+ + " },"
+ + " {"
+ + " \"dataType\": \"purchase\","
+ + " \"searchCombo\": {"
+ + " \"combinator\": \"And\","
+ + " \"searchQueries\": ["
+ + " {"
+ + " \"field\": \"shoppingCartItems.name\","
+ + " \"fieldType\": \"string\","
+ + " \"comparatorType\": \"Equals\","
+ + " \"dataType\": \"purchase\","
+ + " \"id\": 9,"
+ + " \"value\": \"coffee\""
+ + " },"
+ + " {"
+ + " \"field\": \"shoppingCartItems.quantity\","
+ + " \"fieldType\": \"long\","
+ + " \"comparatorType\": \"GreaterThanOrEqualTo\","
+ + " \"dataType\": \"purchase\","
+ + " \"id\": 10,"
+ + " \"valueLong\": 2,"
+ + " \"value\": \"2\""
+ + " }"
+ + " ]"
+ + " }"
+ + " },"
+ + " {"
+ + " \"dataType\": \"user\","
+ + " \"searchCombo\": {"
+ + " \"combinator\": \"And\","
+ + " \"searchQueries\": ["
+ + " {"
+ + " \"field\": \"country\","
+ + " \"fieldType\": \"string\","
+ + " \"comparatorType\": \"Equals\","
+ + " \"dataType\": \"user\","
+ + " \"id\": 12,"
+ + " \"value\": \"USA\""
+ + " },"
+ + " {"
+ + " \"field\": \"preferred_car_models\","
+ + " \"fieldType\": \"string\","
+ + " \"comparatorType\": \"Contains\","
+ + " \"dataType\": \"user\","
+ + " \"id\": 14,"
+ + " \"value\": \"Subaru\""
+ + " }"
+ + " ]"
+ + " }"
+ + " }"
+ + " ]"
+ + " }"
+ + " ]"
+ + " }"
+ + " }"
+ + "]"
+ + "}";
+
+ private String complexCriteria4 = "{\n" +
+ " \"count\": 1,\n" +
+ " \"criteriaSets\": [\n" +
+ " {\n" +
+ " \"criteriaId\": \"1\",\n" +
+ " \"name\": \"Custom Event\",\n" +
+ " \"createdAt\": 1716560453973,\n" +
+ " \"updatedAt\": 1716560453973,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"Not\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"purchase\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"field\": \"shoppingCartItems.name\",\n" +
+ " \"fieldType\": \"string\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"dataType\": \"purchase\",\n" +
+ " \"id\": 1,\n" +
+ " \"value\": \"sneakers\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"field\": \"shoppingCartItems.quantity\",\n" +
+ " \"fieldType\": \"long\",\n" +
+ " \"comparatorType\": \"LessThanOrEqualTo\",\n" +
+ " \"dataType\": \"purchase\",\n" +
+ " \"id\": 2,\n" +
+ " \"valueLong\": 3,\n" +
+ " \"value\": \"3\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " },\n" +
+ " {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"purchase\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"field\": \"shoppingCartItems.name\",\n" +
+ " \"fieldType\": \"string\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"dataType\": \"purchase\",\n" +
+ " \"id\": 4,\n" +
+ " \"value\": \"slippers\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"field\": \"shoppingCartItems.quantity\",\n" +
+ " \"fieldType\": \"long\",\n" +
+ " \"comparatorType\": \"GreaterThanOrEqualTo\",\n" +
+ " \"dataType\": \"purchase\",\n" +
+ " \"id\": 5,\n" +
+ " \"valueLong\": 3,\n" +
+ " \"value\": \"3\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+ @Before
+ public void setUp() {
+ evaluator = new CriteriaCompletionChecker();
+ }
+
+ @Test
+ public void testMinMatchWithCriteriaMinMaxData() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"items\": \"[{\\\"id\\\":\\\"12\\\",\\\"name\\\":\\\"Mocha\\\",\\\"price\\\":50,\\\"quantity\\\":40}]\",\n" +
+ " \"createdAt\": 1700071052507,\n" +
+ " \"total\": 50,\n" +
+ " \"eventType\": \"cartUpdate\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"items\": \"[{\\\"id\\\":\\\"12\\\",\\\"name\\\":\\\"Mocha\\\",\\\"price\\\":50,\\\"quantity\\\":40}]\",\n" +
+ " \"createdAt\": 1700071052507,\n" +
+ " \"total\": 50,\n" +
+ " \"eventType\": \"cartUpdate\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"preferred_car_models\": \"Honda\"\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(criteriaMinMax, jsonArray);
+ assertTrue(result != null);
+ }
+
+ @Test
+ public void testMinMatchWithCriteriaMinMaxDataFail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"items\": \"[{\\\"id\\\":\\\"12\\\",\\\"name\\\":\\\"Mocha\\\",\\\"price\\\":50,\\\"quantity\\\":40}]\",\n" +
+ " \"createdAt\": 1700071052507,\n" +
+ " \"total\": 50,\n" +
+ " \"eventType\": \"cartUpdate\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"preferred_car_models\": \"Honda\"\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(criteriaMinMax, jsonArray);
+ assertFalse(result != null);
+ }
+
+ @Test
+ public void testComplexCriteria1() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"items\": \"[{\\\"id\\\":\\\"12\\\",\\\"name\\\":\\\"monitor\\\",\\\"price\\\":50,\\\"quantity\\\":10}]\",\n" +
+ " \"createdAt\": 1700071052507,\n" +
+ " \"total\": 50,\n" +
+ " \"eventType\": \"purchase\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"preferred_car_models\": \"Honda\",\n" +
+ " \"country\": \"Japan\"\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(complexCriteria1, jsonArray);
+ assertTrue(result != null);
+ }
+
+ @Test
+ public void testComplexCriteria1Fail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"items\": \"[{\\\"id\\\":\\\"12\\\",\\\"name\\\":\\\"monitor\\\",\\\"price\\\":50,\\\"quantity\\\":10}]\",\n" +
+ " \"createdAt\": 1700071052507,\n" +
+ " \"total\": 50,\n" +
+ " \"eventType\": \"purchase\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"preferred_car_models\": \"Honda\"\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(complexCriteria1, jsonArray);
+ assertFalse(result != null);
+ }
+
+ @Test
+ public void testComplexCriteria2() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"items\": \"[{\\\"id\\\":\\\"12\\\",\\\"name\\\":\\\"Mocha\\\",\\\"price\\\":90,\\\"quantity\\\":50}]\",\n" +
+ " \"createdAt\": 1700071052507,\n" +
+ " \"total\": 50,\n" +
+ " \"eventType\": \"cartUpdate\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"items\": \"[{\\\"id\\\":\\\"12\\\",\\\"name\\\":\\\"Mocha\\\",\\\"price\\\":90,\\\"quantity\\\":50}]\",\n" +
+ " \"createdAt\": 1700071052507,\n" +
+ " \"total\": 50,\n" +
+ " \"eventType\": \"cartUpdate\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"preferred_car_models\": \"Subaru\",\n" +
+ " \"country\": \"USA\"\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(complexCriteria2, jsonArray);
+ assertTrue(result != null);
+ }
+
+ @Test
+ public void testComplexCriteria2Fail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"items\": \"[{\\\"id\\\":\\\"12\\\",\\\"name\\\":\\\"Mocha\\\",\\\"price\\\":90,\\\"quantity\\\":50}]\",\n" +
+ " \"createdAt\": 1700071052507,\n" +
+ " \"total\": 50,\n" +
+ " \"eventType\": \"cartUpdate\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"preferred_car_models\": \"Subaru\",\n" +
+ " \"country\": \"USA\"\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(complexCriteria2, jsonArray);
+ assertFalse(result != null);
+ }
+
+ @Test
+ public void testComplexCriteria3() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"items\": \"[{\\\"id\\\":\\\"12\\\",\\\"name\\\":\\\"Mocha\\\",\\\"price\\\":90,\\\"quantity\\\":50}]\",\n" +
+ " \"createdAt\": 1700071052507,\n" +
+ " \"total\": 50,\n" +
+ " \"eventType\": \"cartUpdate\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"items\": \"[{\\\"id\\\":\\\"12\\\",\\\"name\\\":\\\"Mocha\\\",\\\"price\\\":90,\\\"quantity\\\":50}]\",\n" +
+ " \"createdAt\": 1700071052507,\n" +
+ " \"total\": 50,\n" +
+ " \"eventType\": \"cartUpdate\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"preferred_car_models\": \"Subaru\",\n" +
+ " \"country\": \"USA\"\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"eventName\": \"button-clicked\",\n" +
+ " \"dataFields\": {\n" +
+ " \"lastPageViewed\": \"welcome page\"\n" +
+ " },\n" +
+ " \"total\": 3,\n" +
+ " \"createdAt\": 1700071052507,\n" +
+ " \"eventType\": \"customEvent\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"items\": [\n" +
+ " {\n" +
+ " \"id\": \"12\",\n" +
+ " \"name\": \"coffee\",\n" +
+ " \"price\": 10,\n" +
+ " \"quantity\": 5\n" +
+ " }\n" +
+ " ],\n" +
+ " \"createdAt\": 1700071052507,\n" +
+ " \"total\": 2,\n" +
+ " \"eventType\": \"purchase\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"eventName\": \"button-clicked\",\n" +
+ " \"dataFields\": {\n" +
+ " \"lastPageViewed\": \"welcome page\"\n" +
+ " },\n" +
+ " \"total\": 3,\n" +
+ " \"createdAt\": 1700071052507,\n" +
+ " \"eventType\": \"customEvent\"\n" +
+ " }\n" +
+ "]";
+
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(complexCriteria3, jsonArray);
+ assertTrue(result != null);
+ }
+
+ @Test
+ public void testComplexCriteria3Fail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"items\": \"[{\\\"id\\\":\\\"12\\\",\\\"name\\\":\\\"Mocha\\\",\\\"price\\\":90,\\\"quantity\\\":50}]\",\n" +
+ " \"createdAt\": 1700071052507,\n" +
+ " \"total\": 50,\n" +
+ " \"eventType\": \"cartUpdate\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"items\": \"[{\\\"id\\\":\\\"12\\\",\\\"name\\\":\\\"Mocha\\\",\\\"price\\\":90,\\\"quantity\\\":50}]\",\n" +
+ " \"createdAt\": 1700071052507,\n" +
+ " \"total\": 50,\n" +
+ " \"eventType\": \"cartUpdate\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"preferred_car_models\": \"Subaru\",\n" +
+ " \"country\": \"USA\"\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"eventName\": \"button-clicked\",\n" +
+ " \"dataFields\": {\n" +
+ " \"lastPageViewed\": \"welcome page\"\n" +
+ " },\n" +
+ " \"total\": 3,\n" +
+ " \"createdAt\": 1700071052507,\n" +
+ " \"eventType\": \"customEvent\"\n" +
+ " }\n" +
+ "]";
+
+
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(complexCriteria3, jsonArray);
+ assertFalse(result != null);
+ }
+
+ @Test
+ public void testComplexCriteria4() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"items\": [\n" +
+ " {\n" +
+ " \"id\": \"12\",\n" +
+ " \"name\": \"sneakers\",\n" +
+ " \"price\": 10,\n" +
+ " \"quantity\": 5\n" +
+ " },\n" +
+ " {\n" +
+ " \"id\": \"12\",\n" +
+ " \"name\": \"slippers\",\n" +
+ " \"price\": 10,\n" +
+ " \"quantity\": 5\n" +
+ " }\n" +
+ " ],\n" +
+ " \"createdAt\": 1700071052507,\n" +
+ " \"total\": 2,\n" +
+ " \"eventType\": \"purchase\"\n" +
+ " }\n" +
+ "]";
+
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(complexCriteria4, jsonArray);
+ System.out.println("TEST_USER: " + String.valueOf(result));
+ assertTrue(result != null);
+ }
+
+ @Test
+ public void testComplexCriteria4Fail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"items\": [\n" +
+ " {\n" +
+ " \"id\": \"12\",\n" +
+ " \"name\": \"sneakers\",\n" +
+ " \"price\": 10,\n" +
+ " \"quantity\": 3\n" +
+ " },\n" +
+ " {\n" +
+ " \"id\": \"12\",\n" +
+ " \"name\": \"slippers\",\n" +
+ " \"price\": 10,\n" +
+ " \"quantity\": 5\n" +
+ " }\n" +
+ " ],\n" +
+ " \"createdAt\": 1700071052507,\n" +
+ " \"total\": 2,\n" +
+ " \"eventType\": \"purchase\"\n" +
+ " }\n" +
+ "]";
+
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(complexCriteria4, jsonArray);
+ System.out.println("TEST_USER: " + String.valueOf(result));
+ assertFalse(result != null);
+ }
+}
diff --git a/iterableapi/src/androidTest/java/com/iterable/iterableapi/util/CriteriaCompletionCheckerTest.java b/iterableapi/src/androidTest/java/com/iterable/iterableapi/util/CriteriaCompletionCheckerTest.java
new file mode 100644
index 000000000..fd8ead79f
--- /dev/null
+++ b/iterableapi/src/androidTest/java/com/iterable/iterableapi/util/CriteriaCompletionCheckerTest.java
@@ -0,0 +1,539 @@
+package com.iterable.iterableapi.util;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.json.JSONArray;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class CriteriaCompletionCheckerTest {
+
+ private CriteriaCompletionChecker evaluator;
+
+ private final String mockDataWithAnd = "{\n" +
+ " \"count\":2,\n" +
+ " \"criteriaSets\":[\n" +
+ " {\n" +
+ " \"criteriaId\":12345,\n" +
+ " \"searchQuery\":{\n" +
+ " \"combinator\":\"Or\",\n" +
+ " \"searchQueries\":[\n" +
+ " {\n" +
+ " \"combinator\":\"And\",\n" +
+ " \"searchQueries\":[\n" +
+ " {\n" +
+ " \"dataType\":\"purchase\",\n" +
+ " \"searchCombo\":{\n" +
+ " \"combinator\":\"And\",\n" +
+ " \"searchQueries\":[\n" +
+ " {\n" +
+ " \"field\":\"shoppingCartItems.price\",\n" +
+ " \"fieldType\":\"double\",\n" +
+ " \"comparatorType\":\"Equals\",\n" +
+ " \"dataType\":\"purchase\",\n" +
+ " \"id\":2,\n" +
+ " \"value\":\"4.67\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"field\":\"shoppingCartItems.quantity\",\n" +
+ " \"fieldType\":\"long\",\n" +
+ " \"comparatorType\":\"GreaterThanOrEqualTo\",\n" +
+ " \"dataType\":\"purchase\",\n" +
+ " \"id\":3,\n" +
+ " \"valueLong\":2,\n" +
+ " \"value\":\"2\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"criteriaId\":5678,\n" +
+ " \"searchQuery\":{\n" +
+ " \"combinator\":\"Or\",\n" +
+ " \"searchQueries\":[\n" +
+ " {\n" +
+ " \"combinator\":\"Or\",\n" +
+ " \"searchQueries\":[\n" +
+ " {\n" +
+ " \"dataType\":\"user\",\n" +
+ " \"searchCombo\":{\n" +
+ " \"combinator\":\"And\",\n" +
+ " \"searchQueries\":[\n" +
+ " {\n" +
+ " \"field\":\"itblInternal.emailDomain\",\n" +
+ " \"fieldType\":\"string\",\n" +
+ " \"comparatorType\":\"Equals\",\n" +
+ " \"dataType\":\"user\",\n" +
+ " \"id\":6,\n" +
+ " \"value\":\"gmail.com\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\":\"customEvent\",\n" +
+ " \"searchCombo\":{\n" +
+ " \"combinator\":\"And\",\n" +
+ " \"searchQueries\":[\n" +
+ " {\n" +
+ " \"field\":\"eventName\",\n" +
+ " \"fieldType\":\"string\",\n" +
+ " \"comparatorType\":\"Equals\",\n" +
+ " \"dataType\":\"customEvent\",\n" +
+ " \"id\":9,\n" +
+ " \"value\":\"processing_cancelled\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"field\":\"createdAt\",\n" +
+ " \"fieldType\":\"date\",\n" +
+ " \"comparatorType\":\"GreaterThan\",\n" +
+ " \"dataType\":\"customEvent\",\n" +
+ " \"id\":10,\n" +
+ " \"dateRange\":{\n" +
+ " \n" +
+ " },\n" +
+ " \"isRelativeDate\":false,\n" +
+ " \"value\":\"1688194800000\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+ private final String mockDataWithOr = "{\n" +
+ " \"count\":2,\n" +
+ " \"criteriaSets\":[\n" +
+ " {\n" +
+ " \"criteriaId\":12345,\n" +
+ " \"searchQuery\":{\n" +
+ " \"combinator\":\"Or\",\n" +
+ " \"searchQueries\":[\n" +
+ " {\n" +
+ " \"combinator\":\"And\",\n" +
+ " \"searchQueries\":[\n" +
+ " {\n" +
+ " \"dataType\":\"purchase\",\n" +
+ " \"searchCombo\":{\n" +
+ " \"combinator\":\"Or\",\n" +
+ " \"searchQueries\":[\n" +
+ " {\n" +
+ " \"field\":\"shoppingCartItems.price\",\n" +
+ " \"fieldType\":\"double\",\n" +
+ " \"comparatorType\":\"Equals\",\n" +
+ " \"dataType\":\"purchase\",\n" +
+ " \"id\":2,\n" +
+ " \"value\":\"4.67\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"field\":\"shoppingCartItems.quantity\",\n" +
+ " \"fieldType\":\"long\",\n" +
+ " \"comparatorType\":\"GreaterThanOrEqualTo\",\n" +
+ " \"dataType\":\"purchase\",\n" +
+ " \"id\":3,\n" +
+ " \"valueLong\":2,\n" +
+ " \"value\":\"2\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"criteriaId\":5678,\n" +
+ " \"searchQuery\":{\n" +
+ " \"combinator\":\"Or\",\n" +
+ " \"searchQueries\":[\n" +
+ " {\n" +
+ " \"combinator\":\"Or\",\n" +
+ " \"searchQueries\":[\n" +
+ " {\n" +
+ " \"dataType\":\"user\",\n" +
+ " \"searchCombo\":{\n" +
+ " \"combinator\":\"And\",\n" +
+ " \"searchQueries\":[\n" +
+ " {\n" +
+ " \"field\":\"itblInternal.emailDomain\",\n" +
+ " \"fieldType\":\"string\",\n" +
+ " \"comparatorType\":\"Equals\",\n" +
+ " \"dataType\":\"user\",\n" +
+ " \"id\":6,\n" +
+ " \"value\":\"gmail.com\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\":\"customEvent\",\n" +
+ " \"searchCombo\":{\n" +
+ " \"combinator\":\"And\",\n" +
+ " \"searchQueries\":[\n" +
+ " {\n" +
+ " \"field\":\"eventName\",\n" +
+ " \"fieldType\":\"string\",\n" +
+ " \"comparatorType\":\"Equals\",\n" +
+ " \"dataType\":\"customEvent\",\n" +
+ " \"id\":9,\n" +
+ " \"value\":\"processing_cancelled\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"field\":\"createdAt\",\n" +
+ " \"fieldType\":\"date\",\n" +
+ " \"comparatorType\":\"GreaterThan\",\n" +
+ " \"dataType\":\"customEvent\",\n" +
+ " \"id\":10,\n" +
+ " \"dateRange\":{\n" +
+ " \n" +
+ " },\n" +
+ " \"isRelativeDate\":false,\n" +
+ " \"value\":\"1688194800000\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+ private String mockCriteria = "{\n" +
+ " \"count\": 4,\n" +
+ " \"criteriaSets\": [\n" +
+ " {\n" +
+ " \"criteriaId\": \"43\",\n" +
+ " \"name\": \"ContactProperty\",\n" +
+ " \"createdAt\": 1716560453973,\n" +
+ " \"updatedAt\": 1716560453973,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"Or\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"country\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": \"UK\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"preferred_car_models\",\n" +
+ " \"comparatorType\": \"Contains\",\n" +
+ " \"value\": \"Mazda\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"criteriaId\": \"42\",\n" +
+ " \"name\": \"purchase\",\n" +
+ " \"createdAt\": 1716560403912,\n" +
+ " \"updatedAt\": 1716560403912,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"Or\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"purchase\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"purchase\",\n" +
+ " \"field\": \"shoppingCartItems.name\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": \"keyboard\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"purchase\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"purchase\",\n" +
+ " \"field\": \"shoppingCartItems.quantity\",\n" +
+ " \"comparatorType\": \"GreaterThanOrEqualTo\",\n" +
+ " \"value\": \"3\",\n" +
+ " \"fieldType\": \"long\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"purchase\",\n" +
+ " \"field\": \"shoppingCartItems.price\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": \"10\",\n" +
+ " \"fieldType\": \"long\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"criteriaId\": \"41\",\n" +
+ " \"name\": \"updateCart\",\n" +
+ " \"createdAt\": 1716560369947,\n" +
+ " \"updatedAt\": 1716560369947,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"Or\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"field\": \"eventName\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": \"updateCart\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"field\": \"updateCart.updatedShoppingCartItems.price\",\n" +
+ " \"comparatorType\": \"LessThanOrEqualTo\",\n" +
+ " \"value\": \"10\",\n" +
+ " \"fieldType\": \"double\"\n" +
+ " }\n" +
+ " ]\n" +
+ " },\n" +
+ " \"minMatch\": 3,\n" +
+ " \"maxMatch\": 2\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"field\": \"eventName\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": \"updateCart\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"field\": \"updateCart.updatedShoppingCartItems.quantity\",\n" +
+ " \"comparatorType\": \"GreaterThanOrEqualTo\",\n" +
+ " \"value\": \"50\",\n" +
+ " \"fieldType\": \"long\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"criteriaId\": \"40\",\n" +
+ " \"name\": \"Customevent\",\n" +
+ " \"createdAt\": 1716560323583,\n" +
+ " \"updatedAt\": 1716560323583,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"field\": \"eventName\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": \"button-clicked\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"field\": \"button-clicked.lastPageViewed\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": \"signup page\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+ @Before
+ public void setUp() {
+ evaluator = new CriteriaCompletionChecker();
+ }
+
+ @Test
+ public void testCompareDataWithANDCombinatorFail() throws Exception {
+ JSONArray jsonArray = new JSONArray("[{\"items\":\"[{\\\"id\\\":\\\"12\\\",\\\"name\\\":\\\"Mocha\\\",\\\"price\\\":3.5,\\\"quantity\\\":6}]\",\"createdAt\":1700071052507,\"total\":4.67,\"dataType\":\"purchase\"}]");
+ boolean result = evaluator.getMatchedCriteria(mockDataWithAnd, jsonArray) != null;
+ assertFalse(result);
+ }
+
+ @Test
+ public void testCompareDataWithANDCombinator() throws Exception {
+ JSONArray jsonArray = new JSONArray("[{\"items\":\"[{\\\"id\\\":\\\"12\\\",\\\"name\\\":\\\"Mocha\\\",\\\"price\\\":4.67,\\\"quantity\\\":3}]\",\"createdAt\":1700071052507,\"total\":4.67,\"eventType\":\"purchase\"}]");
+ boolean result = evaluator.getMatchedCriteria(mockDataWithAnd, jsonArray) != null;
+ assertTrue(result);
+ }
+
+ @Test
+ public void testCompareDataWithORCombinator() throws Exception {
+ JSONArray jsonArray = new JSONArray("[{\"items\":\"[{\\\"id\\\":\\\"12\\\",\\\"name\\\":\\\"Mocha\\\",\\\"price\\\":4.67,\\\"quantity\\\":3}]\",\"createdAt\":1700071052507,\"total\":2,\"eventType\":\"purchase\"}]");
+ boolean result = evaluator.getMatchedCriteria(mockDataWithOr, jsonArray) != null;
+ assertTrue(result);
+ }
+
+ @Test
+ public void testCompareDataWithORCombinatorFail() throws Exception {
+ JSONArray jsonArray = new JSONArray("[{\"items\":\"[{\\\"id\\\":\\\"12\\\",\\\"name\\\":\\\"Mocha\\\",\\\"price\\\":3.5,\\\"quantity\\\":1}]\",\"createdAt\":1700071052507,\"total\":4.67,\"dataType\":\"purchase\"}]");
+ boolean result = evaluator.getMatchedCriteria(mockDataWithOr, jsonArray) != null;
+ assertFalse(result);
+ }
+
+ @Test
+ public void testUserWithMockData() throws Exception {
+ JSONArray jsonArray = new JSONArray("[{\"dataFields\":{\"country\":\"UK\"},\"eventType\":\"user\"}]");
+ String result = evaluator.getMatchedCriteria(mockCriteria, jsonArray);
+ assertTrue(result != null);
+ }
+
+ @Test
+ public void testUserWithMockDataFail() throws Exception {
+ JSONArray jsonArray = new JSONArray("[{\"dataFields\":{\"country\":\"US\"},\"eventType\":\"user\"}]");
+ String result = evaluator.getMatchedCriteria(mockCriteria, jsonArray);
+ assertFalse(result != null);
+ }
+
+ @Test
+ public void testPurchaseWithMockData() throws Exception {
+ JSONArray jsonArray = new JSONArray("[{\"items\":\"[{\\\"id\\\":\\\"12\\\",\\\"name\\\":\\\"keyboard\\\",\\\"price\\\":10,\\\"quantity\\\":2}]\",\"createdAt\":1700071052507,\"total\":2,\"eventType\":\"purchase\"}]");
+ String result = evaluator.getMatchedCriteria(mockCriteria, jsonArray);
+ assertTrue(result != null);
+ }
+
+ @Test
+ public void testPurchaseWithMockDataFail() throws Exception {
+ JSONArray jsonArray = new JSONArray("[{\"items\":\"[{\\\"id\\\":\\\"12\\\",\\\"name\\\":\\\"guitar\\\",\\\"price\\\":15,\\\"quantity\\\":2}]\",\"createdAt\":1700071052507,\"total\":2,\"eventType\":\"purchase\"}]");
+ String result = evaluator.getMatchedCriteria(mockCriteria, jsonArray);
+ assertFalse(result != null);
+ }
+
+ @Test
+ public void testUpdateCartWithMockData() throws Exception {
+ JSONArray jsonArray = new JSONArray("[{\"items\":\"[{\\\"id\\\":\\\"12\\\",\\\"name\\\":\\\"Mocha\\\",\\\"price\\\":9,\\\"quantity\\\":52}]\",\"createdAt\":1700071052507,\"total\":4.67,\"eventType\":\"cartUpdate\"}]");
+ String result = evaluator.getMatchedCriteria(mockCriteria, jsonArray);
+ assertTrue(result != null);
+ }
+
+ @Test
+ public void testUpdateCartWithMockDataFail() throws Exception {
+ JSONArray jsonArray = new JSONArray("[{\"items\":\"[{\\\"id\\\":\\\"12\\\",\\\"name\\\":\\\"Mocha\\\",\\\"price\\\":9,\\\"quantity\\\":40}]\",\"createdAt\":1700071052507,\"total\":9,\"eventType\":\"cartUpdate\"}]");
+ String result = evaluator.getMatchedCriteria(mockCriteria, jsonArray);
+ assertFalse(result != null);
+ }
+
+ @Test
+ public void testCustomEventWithMockData() throws Exception {
+ JSONArray jsonArray = new JSONArray("[{\"eventName\":\"button-clicked\",\"dataFields\":{\"lastPageViewed\":\"signup page\"},\"createdAt\":1700071052507,\"eventType\":\"customEvent\"}]");
+
+ String result = evaluator.getMatchedCriteria(mockCriteria, jsonArray);
+ assertTrue(result != null);
+ }
+
+ @Test
+ public void testCustomEventWithMockDataFail() throws Exception {
+ JSONArray jsonArray = new JSONArray("[{\"eventName\":\"button-clicked\",\"dataFields\":{\"lastPageViewed\":\"login page\"},\"createdAt\":1700071052507,\"eventType\":\"customEvent\"}]");
+
+ String result = evaluator.getMatchedCriteria(mockCriteria, jsonArray);
+ assertFalse(result != null);
+ }
+
+ @Test
+ public void testSingleItemMatchWithMockDataFail() throws Exception {
+ JSONArray jsonArray = new JSONArray("[{\"items\":\"[{\\\"id\\\":\\\"12\\\",\\\"name\\\":\\\"piano\\\",\\\"price\\\":10,\\\"quantity\\\":2},{\\\"id\\\":\\\"12\\\",\\\"name\\\":\\\"piano\\\",\\\"price\\\":5,\\\"quantity\\\":3}]\",\"createdAt\":1700071052507,\"total\":2,\"eventType\":\"purchase\"}]");
+
+ String result = evaluator.getMatchedCriteria(mockCriteria, jsonArray);
+ assertFalse(result != null);
+ }
+
+ @Test
+ public void testMinMatchWithMockData() throws Exception {
+ JSONArray jsonArray = new JSONArray("[{\"items\":\"[{\\\"id\\\":\\\"12\\\",\\\"name\\\":\\\"Mocha\\\",\\\"price\\\":10,\\\"quantity\\\":40}]\",\"createdAt\":1700071052507,\"total\":4.67,\"eventType\":\"cartUpdate\"},{\"items\":\"[{\\\"id\\\":\\\"12\\\",\\\"name\\\":\\\"Mocha\\\",\\\"price\\\":10,\\\"quantity\\\":40}]\",\"createdAt\":1700071052507,\"total\":4.67,\"eventType\":\"cartUpdate\"},{\"items\":\"[{\\\"id\\\":\\\"12\\\",\\\"name\\\":\\\"Mocha\\\",\\\"price\\\":10,\\\"quantity\\\":40}]\",\"createdAt\":1700071052507,\"total\":4.67,\"eventType\":\"cartUpdate\"}]");
+
+ String result = evaluator.getMatchedCriteria(mockCriteria, jsonArray);
+ assertTrue(result != null);
+ }
+
+ @Test
+ public void testMinMatchWithMockDataFail() throws Exception {
+ JSONArray jsonArray = new JSONArray("[{\"items\":\"[{\\\"id\\\":\\\"12\\\",\\\"name\\\":\\\"Mocha\\\",\\\"price\\\":10,\\\"quantity\\\":40}]\",\"createdAt\":1700071052507,\"total\":4.67,\"eventType\":\"cartUpdate\"},{\"items\":\"[{\\\"id\\\":\\\"12\\\",\\\"name\\\":\\\"Mocha\\\",\\\"price\\\":10,\\\"quantity\\\":40}]\",\"createdAt\":1700071052507,\"total\":4.67,\"eventType\":\"cartUpdate\"},{\"items\":\"[{\\\"id\\\":\\\"12\\\",\\\"name\\\":\\\"Mocha\\\",\\\"price\\\":15,\\\"quantity\\\":40}]\",\"createdAt\":1700071052507,\"total\":4.67,\"eventType\":\"cartUpdate\"}]");
+
+ String result = evaluator.getMatchedCriteria(mockCriteria, jsonArray);
+ assertFalse(result != null);
+ }
+
+}
\ No newline at end of file
diff --git a/iterableapi/src/androidTest/java/com/iterable/iterableapi/util/CriteriaCompletionComparatorTest.java b/iterableapi/src/androidTest/java/com/iterable/iterableapi/util/CriteriaCompletionComparatorTest.java
new file mode 100644
index 000000000..346d8bf97
--- /dev/null
+++ b/iterableapi/src/androidTest/java/com/iterable/iterableapi/util/CriteriaCompletionComparatorTest.java
@@ -0,0 +1,415 @@
+package com.iterable.iterableapi.util;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.json.JSONArray;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class CriteriaCompletionComparatorTest {
+ private CriteriaCompletionChecker evaluator;
+ private String isSetMockData = "{"
+ + "\"count\": 4,"
+ + "\"criteriaSets\": ["
+ + " {"
+ + " \"criteriaId\": \"1\","
+ + " \"name\": \"CustomEvent\","
+ + " \"createdAt\": 1716560453973,"
+ + " \"updatedAt\": 1716560453973,"
+ + " \"searchQuery\": {"
+ + " \"combinator\": \"And\","
+ + " \"searchQueries\": ["
+ + " {"
+ + " \"combinator\": \"And\","
+ + " \"searchQueries\": ["
+ + " {"
+ + " \"dataType\": \"customEvent\","
+ + " \"searchCombo\": {"
+ + " \"combinator\": \"And\","
+ + " \"searchQueries\": ["
+ + " {"
+ + " \"field\": \"button-clicked\","
+ + " \"fieldType\": \"object\","
+ + " \"comparatorType\": \"IsSet\","
+ + " \"dataType\": \"customEvent\","
+ + " \"id\": 2,"
+ + " \"value\": \"\""
+ + " },"
+ + " {"
+ + " \"field\": \"button-clicked.animal\","
+ + " \"fieldType\": \"string\","
+ + " \"comparatorType\": \"IsSet\","
+ + " \"dataType\": \"customEvent\","
+ + " \"id\": 4,"
+ + " \"value\": \"\""
+ + " },"
+ + " {"
+ + " \"field\": \"button-clicked.clickCount\","
+ + " \"fieldType\": \"long\","
+ + " \"comparatorType\": \"IsSet\","
+ + " \"dataType\": \"customEvent\","
+ + " \"id\": 5,"
+ + " \"valueLong\": null,"
+ + " \"value\": \"\""
+ + " },"
+ + " {"
+ + " \"field\": \"total\","
+ + " \"fieldType\": \"double\","
+ + " \"comparatorType\": \"IsSet\","
+ + " \"dataType\": \"customEvent\","
+ + " \"id\": 9,"
+ + " \"value\": \"\""
+ + " }"
+ + " ]"
+ + " }"
+ + " }"
+ + " ]"
+ + " }"
+ + " ]"
+ + " }"
+ + " },"
+ + " {"
+ + " \"criteriaId\": \"2\","
+ + " \"name\": \"UpdateCart\","
+ + " \"createdAt\": 1716560453973,"
+ + " \"updatedAt\": 1716560453973,"
+ + " \"searchQuery\": {"
+ + " \"combinator\": \"And\","
+ + " \"searchQueries\": ["
+ + " {"
+ + " \"combinator\": \"And\","
+ + " \"searchQueries\": ["
+ + " {"
+ + " \"dataType\": \"customEvent\","
+ + " \"searchCombo\": {"
+ + " \"combinator\": \"And\","
+ + " \"searchQueries\": ["
+ + " {"
+ + " \"field\": \"updateCart\","
+ + " \"fieldType\": \"object\","
+ + " \"comparatorType\": \"IsSet\","
+ + " \"dataType\": \"customEvent\","
+ + " \"id\": 9,"
+ + " \"value\": \"\""
+ + " },"
+ + " {"
+ + " \"field\": \"updateCart.updatedShoppingCartItems.name\","
+ + " \"fieldType\": \"string\","
+ + " \"comparatorType\": \"IsSet\","
+ + " \"dataType\": \"customEvent\","
+ + " \"id\": 13,"
+ + " \"value\": \"\""
+ + " },"
+ + " {"
+ + " \"field\": \"updateCart.updatedShoppingCartItems.price\","
+ + " \"fieldType\": \"double\","
+ + " \"comparatorType\": \"IsSet\","
+ + " \"dataType\": \"customEvent\","
+ + " \"id\": 15,"
+ + " \"value\": \"\""
+ + " },"
+ + " {"
+ + " \"field\": \"updateCart.updatedShoppingCartItems.quantity\","
+ + " \"fieldType\": \"long\","
+ + " \"comparatorType\": \"IsSet\","
+ + " \"dataType\": \"customEvent\","
+ + " \"id\": 16,"
+ + " \"valueLong\": null,"
+ + " \"value\": \"\""
+ + " }"
+ + " ]"
+ + " }"
+ + " }"
+ + " ]"
+ + " }"
+ + " ]"
+ + " }"
+ + " },"
+ + " {"
+ + " \"criteriaId\": \"3\","
+ + " \"name\": \"Purchase\","
+ + " \"createdAt\": 1716560453973,"
+ + " \"updatedAt\": 1716560453973,"
+ + " \"searchQuery\": {"
+ + " \"combinator\": \"And\","
+ + " \"searchQueries\": ["
+ + " {"
+ + " \"combinator\": \"And\","
+ + " \"searchQueries\": ["
+ + " {"
+ + " \"dataType\": \"purchase\","
+ + " \"searchCombo\": {"
+ + " \"combinator\": \"And\","
+ + " \"searchQueries\": ["
+ + " {"
+ + " \"field\": \"shoppingCartItems\","
+ + " \"fieldType\": \"object\","
+ + " \"comparatorType\": \"IsSet\","
+ + " \"dataType\": \"purchase\","
+ + " \"id\": 1,"
+ + " \"value\": \"\""
+ + " },"
+ + " {"
+ + " \"field\": \"shoppingCartItems.price\","
+ + " \"fieldType\": \"double\","
+ + " \"comparatorType\": \"IsSet\","
+ + " \"dataType\": \"purchase\","
+ + " \"id\": 3,"
+ + " \"value\": \"\""
+ + " },"
+ + " {"
+ + " \"field\": \"shoppingCartItems.name\","
+ + " \"fieldType\": \"string\","
+ + " \"comparatorType\": \"IsSet\","
+ + " \"dataType\": \"purchase\","
+ + " \"id\": 5,"
+ + " \"value\": \"\""
+ + " },"
+ + " {"
+ + " \"field\": \"total\","
+ + " \"fieldType\": \"double\","
+ + " \"comparatorType\": \"IsSet\","
+ + " \"dataType\": \"purchase\","
+ + " \"id\": 7,"
+ + " \"value\": \"\""
+ + " }"
+ + " ]"
+ + " }"
+ + " }"
+ + " ]"
+ + " }"
+ + " ]"
+ + " }"
+ + " },"
+ + " {"
+ + " \"criteriaId\": \"4\","
+ + " \"name\": \"User\","
+ + " \"createdAt\": 1716560453973,"
+ + " \"updatedAt\": 1716560453973,"
+ + " \"searchQuery\": {"
+ + " \"combinator\": \"And\","
+ + " \"searchQueries\": ["
+ + " {"
+ + " \"combinator\": \"And\","
+ + " \"searchQueries\": ["
+ + " {"
+ + " \"dataType\": \"user\","
+ + " \"searchCombo\": {"
+ + " \"combinator\": \"And\","
+ + " \"searchQueries\": ["
+ + " {"
+ + " \"field\": \"country\","
+ + " \"fieldType\": \"string\","
+ + " \"comparatorType\": \"IsSet\","
+ + " \"dataType\": \"user\","
+ + " \"id\": 25,"
+ + " \"value\": \"\""
+ + " },"
+ + " {"
+ + " \"field\": \"eventTimeStamp\","
+ + " \"fieldType\": \"long\","
+ + " \"comparatorType\": \"IsSet\","
+ + " \"dataType\": \"user\","
+ + " \"id\": 26,"
+ + " \"valueLong\": null,"
+ + " \"value\": \"\""
+ + " },"
+ + " {"
+ + " \"field\": \"phoneNumberDetails\","
+ + " \"fieldType\": \"object\","
+ + " \"comparatorType\": \"IsSet\","
+ + " \"dataType\": \"user\","
+ + " \"id\": 28,"
+ + " \"value\": \"\""
+ + " },"
+ + " {"
+ + " \"field\": \"shoppingCartItems.price\","
+ + " \"fieldType\": \"double\","
+ + " \"comparatorType\": \"IsSet\","
+ + " \"dataType\": \"user\","
+ + " \"id\": 30,"
+ + " \"value\": \"\""
+ + " }"
+ + " ]"
+ + " }"
+ + " }"
+ + " ]"
+ + " }"
+ + " ]"
+ + " }"
+ + " }"
+ + "]"
+ + "}";
+
+ @Before
+ public void setUp() {
+ evaluator = new CriteriaCompletionChecker();
+ }
+
+ @Test
+ public void testUserWithIsSetMockData() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"country\": \"UK\",\n" +
+ " \"eventTimeStamp\": 10,\n" +
+ " \"phoneNumberDetails\": \"99999999\",\n" +
+ " \"shoppingCartItems.price\": 50.5\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(isSetMockData, jsonArray);
+ assertTrue(result != null);
+ }
+
+ @Test
+ public void testUserWithIsSetMockDataFail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"eventTimeStamp\": 10,\n" +
+ " \"phoneNumberDetails\": \"99999999\",\n" +
+ " \"shoppingCartItems.price\": 50.5\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(isSetMockData, jsonArray);
+ assertFalse(result != null);
+ }
+
+ @Test
+ public void testPurchaseWithIsSetMockData() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"items\": [\n" +
+ " {\n" +
+ " \"id\": \"12\",\n" +
+ " \"name\": \"keyboard\",\n" +
+ " \"price\": 10,\n" +
+ " \"quantity\": 2\n" +
+ " }\n" +
+ " ],\n" +
+ " \"createdAt\": 1700071052507,\n" +
+ " \"total\": 2,\n" +
+ " \"eventType\": \"purchase\"\n" +
+ " }\n" +
+ "]";
+
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(isSetMockData, jsonArray);
+ assertTrue(result != null);
+ }
+
+ @Test
+ public void testPurchaseWithIsSetMockDataFail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"items\": [\n" +
+ " {\n" +
+ " \"id\": \"12\",\n" +
+ " \"name\": \"keyboard\"," +
+ " \"quantity\": 5\n" +
+ " }\n" +
+ " ],\n" +
+ " \"createdAt\": 1700071052507,\n" +
+ " \"total\": 2,\n" +
+ " \"eventType\": \"purchase\"\n" +
+ " }\n" +
+ "]";
+
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(isSetMockData, jsonArray);
+ assertFalse(result != null);
+ }
+
+ @Test
+ public void testUpdateCartWithIsSetMockData() throws Exception {
+ String jsonString = "["
+ + " {"
+ + " \"items\": ["
+ + " {"
+ + " \"id\": \"12\","
+ + " \"name\": \"Mocha\","
+ + " \"price\": 9,"
+ + " \"quantity\": 52"
+ + " }"
+ + " ],"
+ + " \"createdAt\": 1700071052507,"
+ + " \"eventType\": \"cartUpdate\""
+ + " }"
+ + "]";
+
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(isSetMockData, jsonArray);
+ assertTrue(result != null);
+ }
+
+ @Test
+ public void testUpdateCartWithIsSetMockDataFail() throws Exception {
+ String jsonString = "["
+ + " {"
+ + " \"items\": ["
+ + " {"
+ + " \"id\": \"12\","
+ + " \"name\": \"Mocha\","
+ + " \"quantity\": 5"
+ + " }"
+ + " ],"
+ + " \"createdAt\": 1700071052507,"
+ + " \"eventType\": \"cartUpdate\""
+ + " }"
+ + "]";
+
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(isSetMockData, jsonArray);
+ assertFalse(result != null);
+ }
+
+ @Test
+ public void testCustomEventWithIsSetMockData() throws Exception {
+ String jsonString = "["
+ + " {"
+ + " \"eventName\": \"button-clicked\","
+ + " \"dataFields\": {"
+ + " \"animal\": \"test page\","
+ + " \"clickCount\": \"2\""
+ + " },"
+ + " \"total\": 3,"
+ + " \"createdAt\": 1700071052507,"
+ + " \"eventType\": \"customEvent\""
+ + " }"
+ + "]";
+
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(isSetMockData, jsonArray);
+ assertTrue(result != null);
+ }
+
+ @Test
+ public void testCustomEventWithIsSetMockDataFail() throws Exception {
+ String jsonString = "["
+ + " {"
+ + " \"eventName\": \"button-clicked\","
+ + " \"dataFields\": {"
+ + " \"animal\": \"\""
+ + " },"
+ + " \"total\": 3,"
+ + " \"createdAt\": 1700071052507,"
+ + " \"eventType\": \"customEvent\""
+ + " }"
+ + "]";
+
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(isSetMockData, jsonArray);
+ assertFalse(result != null);
+ }
+
+}
diff --git a/iterableapi/src/androidTest/java/com/iterable/iterableapi/util/DataTypeComparatorArrayInputCriteriaTest.java b/iterableapi/src/androidTest/java/com/iterable/iterableapi/util/DataTypeComparatorArrayInputCriteriaTest.java
new file mode 100644
index 000000000..9b0bc6fed
--- /dev/null
+++ b/iterableapi/src/androidTest/java/com/iterable/iterableapi/util/DataTypeComparatorArrayInputCriteriaTest.java
@@ -0,0 +1,788 @@
+package com.iterable.iterableapi.util;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import org.json.JSONArray;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class DataTypeComparatorArrayInputCriteriaTest {
+
+ private CriteriaCompletionChecker evaluator;
+
+ @Before
+ public void setUp() {
+ evaluator = new CriteriaCompletionChecker();
+ }
+
+ static final String mockDataArrayDataTypeWithEquals = "{\n" +
+ " \"count\": 1,\n" +
+ " \"criteriaSets\": [\n" +
+ " {\n" +
+ " \"criteriaId\": \"285\",\n" +
+ " \"name\": \"Criteria_Equals\",\n" +
+ " \"createdAt\": 1722497422151,\n" +
+ " \"updatedAt\": 1722500235276,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"milestoneYears\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": \"1997\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"score\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": 11.5,\n" +
+ " \"fieldType\": \"double\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"timestamp\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": 1722500215276,\n" +
+ " \"fieldType\": \"long\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+ @Test
+ public void testEqualsArrayMockDataPass() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"milestoneYears\": [1996, 1997, 2002, 2020, 2024],\n" +
+ " \"score\": [10.5, 11.5, 12.5, 13.5, 14.5],\n" +
+ " \"timestamp\": [1722497422151, 1722500235276, 1722500215276, 1722500225276,\n" +
+ " 1722500245276]\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataArrayDataTypeWithEquals, jsonArray);
+ assertNotNull(result);
+ }
+
+ @Test
+ public void testEqualsArrayMockDataFail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"milestoneYears\": [1996, 1998, 2002, 2020, 2024],\n" +
+ " \"score\": [10.5, 11.5, 12.5, 13.5, 14.5],\n" +
+ " \"timestamp\": [1722497422151, 1722500235276, 1722500215276, 1722500225276,\n" +
+ " 1722500245276]\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataArrayDataTypeWithEquals, jsonArray);
+ assertNull(result);
+ }
+
+ static final String mockDataArrayDataTypeWithDoesNotEqual = "{\n" +
+ " \"count\": 1,\n" +
+ " \"criteriaSets\": [\n" +
+ " {\n" +
+ " \"criteriaId\": \"285\",\n" +
+ " \"name\": \"Criteria_DoesNotEqual\",\n" +
+ " \"createdAt\": 1722497422151,\n" +
+ " \"updatedAt\": 1722500235276,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"milestoneYears\",\n" +
+ " \"comparatorType\": \"DoesNotEqual\",\n" +
+ " \"value\": \"1997\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"score\",\n" +
+ " \"comparatorType\": \"DoesNotEqual\",\n" +
+ " \"value\": 11.5,\n" +
+ " \"fieldType\": \"double\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"timestamp\",\n" +
+ " \"comparatorType\": \"DoesNotEqual\",\n" +
+ " \"value\": 1722500215276,\n" +
+ " \"fieldType\": \"long\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+ @Test
+ public void testDoesNotEqualArrayMockDataPass() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"milestoneYears\": [1996, 1998, 2002, 2020, 2024],\n" +
+ " \"score\": [10.5, 8.5, 12.5, 13.5, 14.5],\n" +
+ " \"timestamp\": [1722497422151, 1722500235276, 1722500215275, 1722500225276,\n" +
+ " 1722500245276]\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataArrayDataTypeWithDoesNotEqual, jsonArray);
+ assertNotNull(result);
+ }
+
+ @Test
+ public void testDoesNotEqualArrayMockDataFail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"milestoneYears\": [1996, 1998, 2002, 2020, 2024],\n" +
+ " \"score\": [10.5, 11.5, 12.5, 13.5, 14.5],\n" +
+ " \"timestamp\": [1722497422151, 1722500235276, 1722500215276, 1722500225276,\n" +
+ " 1722500245276]\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataArrayDataTypeWithDoesNotEqual, jsonArray);
+ assertNull(result);
+ }
+
+ static final String mockDataArrayDataTypeWithGreaterThan = "{\n" +
+ " \"count\": 1,\n" +
+ " \"criteriaSets\": [\n" +
+ " {\n" +
+ " \"criteriaId\": \"285\",\n" +
+ " \"name\": \"Criteria_GreaterThan\",\n" +
+ " \"createdAt\": 1722497422151,\n" +
+ " \"updatedAt\": 1722500235276,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"milestoneYears\",\n" +
+ " \"comparatorType\": \"GreaterThan\",\n" +
+ " \"value\": 1997,\n" +
+ " \"fieldType\": \"double\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+ @Test
+ public void testGreaterThanArrayMockDataPass() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"milestoneYears\": [1996, 1998, 2002, 2020, 2024]\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataArrayDataTypeWithGreaterThan, jsonArray);
+ assertNotNull(result);
+ }
+
+ @Test
+ public void testGreaterThanArrayMockDataFail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"milestoneYears\": [1990, 1992, 1994, 1997]\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataArrayDataTypeWithGreaterThan, jsonArray);
+ assertNull(result);
+ }
+
+ static final String mockDataArrayDataTypeWithGreaterThanOrEqualTo = "{\n" +
+ " \"count\": 1,\n" +
+ " \"criteriaSets\": [\n" +
+ " {\n" +
+ " \"criteriaId\": \"285\",\n" +
+ " \"name\": \"Criteria_GreaterThanOrEqualTo\",\n" +
+ " \"createdAt\": 1722497422151,\n" +
+ " \"updatedAt\": 1722500235276,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"milestoneYears\",\n" +
+ " \"comparatorType\": \"GreaterThanOrEqualTo\",\n" +
+ " \"value\": 1997,\n" +
+ " \"fieldType\": \"double\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+ @Test
+ public void testGreaterThanOrEqualArrayMockDataPass() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"milestoneYears\": [1997, 1998, 2002, 2020, 2024]\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataArrayDataTypeWithGreaterThanOrEqualTo, jsonArray);
+ assertNotNull(result);
+ }
+
+ @Test
+ public void testGreaterThanOrEqualArrayMockDataFail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"milestoneYears\": [1990, 1992, 1994, 1996]\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataArrayDataTypeWithGreaterThanOrEqualTo, jsonArray);
+ assertNull(result);
+ }
+
+ static final String mockDataArrayDataTypeWithLessThan = "{\n" +
+ " \"count\": 1,\n" +
+ " \"criteriaSets\": [\n" +
+ " {\n" +
+ " \"criteriaId\": \"285\",\n" +
+ " \"name\": \"Criteria_LessThan\",\n" +
+ " \"createdAt\": 1722497422151,\n" +
+ " \"updatedAt\": 1722500235276,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"milestoneYears\",\n" +
+ " \"comparatorType\": \"LessThan\",\n" +
+ " \"value\": 1997,\n" +
+ " \"fieldType\": \"double\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+ @Test
+ public void testLessThanArrayMockDataPass() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"milestoneYears\": [1990, 1992, 1994, 1996, 1998]\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataArrayDataTypeWithLessThan, jsonArray);
+ assertNotNull(result);
+ }
+
+ @Test
+ public void testLessThanArrayMockDataFail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"milestoneYears\": [1997, 1999, 2002, 2004]\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataArrayDataTypeWithLessThan, jsonArray);
+ assertNull(result);
+ }
+
+ static final String mockDataArrayDataTypeWithLessThanOrEqualTo = "{\n" +
+ " \"count\": 1,\n" +
+ " \"criteriaSets\": [\n" +
+ " {\n" +
+ " \"criteriaId\": \"285\",\n" +
+ " \"name\": \"Criteria_LessThanOrEqualTo\",\n" +
+ " \"createdAt\": 1722497422151,\n" +
+ " \"updatedAt\": 1722500235276,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"milestoneYears\",\n" +
+ " \"comparatorType\": \"LessThanOrEqualTo\",\n" +
+ " \"value\": 1997,\n" +
+ " \"fieldType\": \"double\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+ @Test
+ public void testLessThanOrEqualsToArrayMockDataPass() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"milestoneYears\": [1990, 1992, 1994, 1996, 1998]\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataArrayDataTypeWithLessThanOrEqualTo, jsonArray);
+ assertNotNull(result);
+ }
+
+ @Test
+ public void testLessThanOrEqualsToArrayMockDataFail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"milestoneYears\": [1998, 1999, 2002, 2004]\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataArrayDataTypeWithLessThanOrEqualTo, jsonArray);
+ assertNull(result);
+ }
+
+ static final String mockDataArrayDataTypeWithContains = "{\n" +
+ " \"count\": 1,\n" +
+ " \"criteriaSets\": [\n" +
+ " {\n" +
+ " \"criteriaId\": \"285\",\n" +
+ " \"name\": \"Criteria_Contains\",\n" +
+ " \"createdAt\": 1722497422151,\n" +
+ " \"updatedAt\": 1722500235276,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"country\",\n" +
+ " \"comparatorType\": \"Contains\",\n" +
+ " \"value\": \"UK\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+ @Test
+ public void testContainArrayMockDataPass() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"country\": [\"US\", \"UK\", \"China\", \"Europe\"]\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataArrayDataTypeWithContains, jsonArray);
+ assertNotNull(result);
+ }
+
+ @Test
+ public void testContainArrayMockDataFail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"country\": [\"US\", \"Canada\", \"China\", \"Europe\"]\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataArrayDataTypeWithContains, jsonArray);
+ assertNull(result);
+ }
+
+ static final String mockDataArrayDataTypeWithMatchesRegex = "{\n" +
+ " \"count\": 1,\n" +
+ " \"criteriaSets\": [\n" +
+ " {\n" +
+ " \"criteriaId\": \"285\",\n" +
+ " \"name\": \"Criteria_MatchesRegex\",\n" +
+ " \"createdAt\": 1722497422151,\n" +
+ " \"updatedAt\": 1722500235276,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"country\",\n" +
+ " \"comparatorType\": \"MatchesRegex\",\n" +
+ " \"value\": \"^T.*iwa.*n$\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+ @Test
+ public void testMatchesRegexArrayMockDataPass() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"country\": [\"US\", \"Taiwan\", \"China\", \"Europe\"]\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataArrayDataTypeWithMatchesRegex, jsonArray);
+ assertNotNull(result);
+ }
+
+ @Test
+ public void testMatchesRegexArrayMockDataFail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"country\": [\"US\", \"Thailand\", \"China\", \"Europe\"]\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataArrayDataTypeWithMatchesRegex, jsonArray);
+ assertNull(result);
+ }
+
+
+ static final String mockDataArrayDataTypeWithStartsWith = "{\n" +
+ " \"count\": 1,\n" +
+ " \"criteriaSets\": [\n" +
+ " {\n" +
+ " \"criteriaId\": \"285\",\n" +
+ " \"name\": \"Criteria_StartsWith\",\n" +
+ " \"createdAt\": 1722497422151,\n" +
+ " \"updatedAt\": 1722500235276,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"country\",\n" +
+ " \"comparatorType\": \"StartsWith\",\n" +
+ " \"value\": \"T\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+ @Test
+ public void testStartsWithArrayMockDataPass() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"country\": [\"US\", \"Taiwan\", \"China\", \"Europe\"]\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataArrayDataTypeWithStartsWith, jsonArray);
+ assertNotNull(result);
+ }
+
+ @Test
+ public void testStartsWithArrayMockDataFail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"country\": [\"US\", \"Canada\", \"China\", \"Europe\"]\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataArrayDataTypeWithStartsWith, jsonArray);
+ assertNull(result);
+ }
+
+ static final String mockDataStringArrayMixCriteriaArea = " {\n" +
+ " \"count\": 1,\n" +
+ " \"criteriaSets\": [\n" +
+ " {\n" +
+ " \"criteriaId\": \"382\",\n" +
+ " \"name\": \"comparison_for_Array_data_types_or\",\n" +
+ " \"createdAt\": 1724315593795,\n" +
+ " \"updatedAt\": 1724315593795,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"Or\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"milestoneYears\",\n" +
+ " \"comparatorType\": \"GreaterThan\",\n" +
+ " \"value\": \"1997\",\n" +
+ " \"fieldType\": \"long\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"field\": \"button-clicked.animal\",\n" +
+ " \"comparatorType\": \"DoesNotEqual\",\n" +
+ " \"value\": \"giraffe\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"purchase\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"purchase\",\n" +
+ " \"field\": \"total\",\n" +
+ " \"comparatorType\": \"LessThanOrEqualTo\",\n" +
+ " \"value\": \"200\",\n" +
+ " \"fieldType\": \"double\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n";
+
+ @Test
+ public void testMixCriteriaAreaArrayMockDataPass() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": { \n" +
+ " \"createdAt\": 1699246745093,\n" +
+ " \"milestoneYears\": [1998, 1999, 2002, 2004]\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"button-clicked.animal\": [\"cow\", \"horse\"]\n" +
+ " }, \n" +
+ " \"eventType\": \"customEvent\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"total\": [199.99, 210.0, 220.20, 250.10]\n" +
+ " },\n" +
+ " \"eventType\": \"purchase\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataStringArrayMixCriteriaArea, jsonArray);
+ assertNotNull(result);
+ }
+
+ @Test
+ public void testMixCriteriaAreaArrayMockDataFail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": { \n" +
+ " \"createdAt\": 1699246745093,\n" +
+ " \"milestoneYears\": [1990, 1992, 1996, 1997]\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"button-clicked.animal\": [\"cow\", \"horse\", \"giraffe\"]\n" +
+ " }, \n" +
+ " \"eventType\": \"customEvent\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"total\": [210.0, 220.20, 250.10]\n" +
+ " },\n" +
+ " \"eventType\": \"purchase\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataStringArrayMixCriteriaArea, jsonArray);
+ assertNull(result);
+ }
+}
diff --git a/iterableapi/src/androidTest/java/com/iterable/iterableapi/util/DataTypeComparatorSearchQueryCriteriaTest.java b/iterableapi/src/androidTest/java/com/iterable/iterableapi/util/DataTypeComparatorSearchQueryCriteriaTest.java
new file mode 100644
index 000000000..a5578472f
--- /dev/null
+++ b/iterableapi/src/androidTest/java/com/iterable/iterableapi/util/DataTypeComparatorSearchQueryCriteriaTest.java
@@ -0,0 +1,810 @@
+package com.iterable.iterableapi.util;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import org.json.JSONArray;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class DataTypeComparatorSearchQueryCriteriaTest {
+
+ static final String mockDataEquals = "{\n" +
+ " \"count\": 1,\n" +
+ " \"criteriaSets\": [\n" +
+ " {\n" +
+ " \"criteriaId\": \"285\",\n" +
+ " \"name\": \"Criteria_EventTimeStamp_Equals\",\n" +
+ " \"createdAt\": 1722497422151,\n" +
+ " \"updatedAt\": 1722500235276,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"eventTimeStamp\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": \"3\",\n" +
+ " \"fieldType\": \"long\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"savings\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": \"19.99\",\n" +
+ " \"fieldType\": \"double\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"likes_boba\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": \"true\",\n" +
+ " \"fieldType\": \"boolean\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"country\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": \"China\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+ static final String mockDataDoesNotEqual = "{\n" +
+ " \"count\": 1,\n" +
+ " \"criteriaSets\": [\n" +
+ " {\n" +
+ " \"criteriaId\": \"286\",\n" +
+ " \"name\": \"Criteria_EventTimeStamp_DoesNotEqual\",\n" +
+ " \"createdAt\": 1722497422151,\n" +
+ " \"updatedAt\": 1722500235276,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"eventTimeStamp\",\n" +
+ " \"comparatorType\": \"DoesNotEqual\",\n" +
+ " \"value\": \"101\",\n" +
+ " \"fieldType\": \"long\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"savings\",\n" +
+ " \"comparatorType\": \"DoesNotEqual\",\n" +
+ " \"value\": \"101\",\n" +
+ " \"fieldType\": \"long\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"likes_boba\",\n" +
+ " \"comparatorType\": \"DoesNotEqual\",\n" +
+ " \"value\": \"false\",\n" +
+ " \"fieldType\": \"boolean\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"country\",\n" +
+ " \"comparatorType\": \"DoesNotEqual\",\n" +
+ " \"value\": \"China\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+ static final String mockDataGreaterThan = "{\n" +
+ " \"count\": 1,\n" +
+ " \"criteriaSets\": [\n" +
+ " {\n" +
+ " \"criteriaId\": \"287\",\n" +
+ " \"name\": \"Criteria_EventTimeStamp_GreaterThan\",\n" +
+ " \"createdAt\": 1722497422151,\n" +
+ " \"updatedAt\": 1722500235276,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"eventTimeStamp\",\n" +
+ " \"comparatorType\": \"GreaterThan\",\n" +
+ " \"value\": \"7\",\n" +
+ " \"fieldType\": \"long\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"savings\",\n" +
+ " \"comparatorType\": \"GreaterThan\",\n" +
+ " \"value\": \"10\",\n" +
+ " \"fieldType\": \"long\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+ static final String mockDataGreaterThanOrEqualTo = "{\n" +
+ " \"count\": 1,\n" +
+ " \"criteriaSets\": [\n" +
+ " {\n" +
+ " \"criteriaId\": \"288\",\n" +
+ " \"name\": \"Criteria_EventTimeStamp_GreaterThanOrEqualTo\",\n" +
+ " \"createdAt\": 1722497422151,\n" +
+ " \"updatedAt\": 1722500235276,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"eventTimeStamp\",\n" +
+ " \"comparatorType\": \"GreaterThanOrEqualTo\",\n" +
+ " \"value\": \"12\",\n" +
+ " \"fieldType\": \"long\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"savings\",\n" +
+ " \"comparatorType\": \"GreaterThanOrEqualTo\",\n" +
+ " \"value\": \"20\",\n" +
+ " \"fieldType\": \"long\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+ static final String mockDataLessThan = "{\n" +
+ " \"count\": 1,\n" +
+ " \"criteriaSets\": [\n" +
+ " {\n" +
+ " \"criteriaId\": \"289\",\n" +
+ " \"name\": \"Criteria_EventTimeStamp_LessThan\",\n" +
+ " \"createdAt\": 1722497422151,\n" +
+ " \"updatedAt\": 1722500235276,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"eventTimeStamp\",\n" +
+ " \"comparatorType\": \"LessThan\",\n" +
+ " \"value\": \"6\",\n" +
+ " \"fieldType\": \"long\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"savings\",\n" +
+ " \"comparatorType\": \"LessThan\",\n" +
+ " \"value\": \"10\",\n" +
+ " \"fieldType\": \"long\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+ static final String mockDataLessThanOrEqualTo = "{\n" +
+ " \"count\": 1,\n" +
+ " \"criteriaSets\": [\n" +
+ " {\n" +
+ " \"criteriaId\": \"290\",\n" +
+ " \"name\": \"Criteria_EventTimeStamp_LessThanOrEqualTo\",\n" +
+ " \"createdAt\": 1722497422151,\n" +
+ " \"updatedAt\": 1722500235276,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"eventTimeStamp\",\n" +
+ " \"comparatorType\": \"LessThanOrEqualTo\",\n" +
+ " \"value\": \"56\",\n" +
+ " \"fieldType\": \"long\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"savings\",\n" +
+ " \"comparatorType\": \"LessThanOrEqualTo\",\n" +
+ " \"value\": \"60\",\n" +
+ " \"fieldType\": \"long\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+
+ static final String mockDataIsSet = "{\n" +
+ " \"count\": 1,\n" +
+ " \"criteriaSets\": [\n" +
+ " {\n" +
+ " \"criteriaId\": \"291\",\n" +
+ " \"name\": \"Criteria_EventTimeStamp_IsSet\",\n" +
+ " \"createdAt\": 1722497422151,\n" +
+ " \"updatedAt\": 1722500235276,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"eventTimeStamp\",\n" +
+ " \"comparatorType\": \"IsSet\",\n" +
+ " \"value\": \"\",\n" +
+ " \"fieldType\": \"long\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"savings\",\n" +
+ " \"comparatorType\": \"IsSet\",\n" +
+ " \"value\": \"\",\n" +
+ " \"fieldType\": \"long\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"country\",\n" +
+ " \"comparatorType\": \"IsSet\",\n" +
+ " \"value\": \"\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"saved_cars\",\n" +
+ " \"comparatorType\": \"IsSet\",\n" +
+ " \"value\": \"\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+ static final String mockDataContains = "{\n" +
+ " \"count\": 1,\n" +
+ " \"criteriaSets\": [\n" +
+ " {\n" +
+ " \"criteriaId\": \"291\",\n" +
+ " \"name\": \"Criteria_EventTimeStamp_IsSet\",\n" +
+ " \"createdAt\": 1722497422151,\n" +
+ " \"updatedAt\": 1722500235276,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"country\",\n" +
+ " \"comparatorType\": \"Contains\",\n" +
+ " \"value\": \"Taiwan\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+ static final String mockDataMatchesRegex = "{\n" +
+ " \"count\": 1,\n" +
+ " \"criteriaSets\": [\n" +
+ " {\n" +
+ " \"criteriaId\": \"291\",\n" +
+ " \"name\": \"Criteria_EventTimeStamp_IsSet\",\n" +
+ " \"createdAt\": 1722497422151,\n" +
+ " \"updatedAt\": 1722500235276,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"country\",\n" +
+ " \"comparatorType\": \"MatchesRegex\",\n" +
+ " \"value\": \"^T.*iwa.*n$\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+ static final String mockDataStartsWith = "{\n" +
+ " \"count\": 1,\n" +
+ " \"criteriaSets\": [\n" +
+ " {\n" +
+ " \"criteriaId\": \"291\",\n" +
+ " \"name\": \"Criteria_EventTimeStamp_IsSet\",\n" +
+ " \"createdAt\": 1722497422151,\n" +
+ " \"updatedAt\": 1722500235276,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"country\",\n" +
+ " \"comparatorType\": \"StartsWith\",\n" +
+ " \"value\": \"T\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+
+ private CriteriaCompletionChecker evaluator;
+
+ @Before
+ public void setUp() {
+ evaluator = new CriteriaCompletionChecker();
+ }
+
+ @Test
+ public void testEqualsMockDataPass() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"eventTimeStamp\": 3\n," +
+ " \"savings\": 19.99\n," +
+ " \"likes_boba\": true\n," +
+ " \"country\": China\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataEquals, jsonArray);
+ assertNotNull(result);
+ }
+
+ @Test
+ public void testEqualsMockDataFail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"eventTimeStamp\": 4\n," +
+ " \"savings\": 20.99\n," +
+ " \"likes_boba\": true\n," +
+ " \"country\": China1\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataEquals, jsonArray);
+ assertNull(result);
+ }
+
+ @Test
+ public void testDoesNotEqualMockDataPass() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"eventTimeStamp\": 105\n," +
+ " \"savings\": 105\n," +
+ " \"likes_boba\": true\n," +
+ " \"country\": China1\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataDoesNotEqual, jsonArray);
+ assertNotNull(result);
+ }
+
+ @Test
+ public void testDoesNotEqualMockDataFail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"eventTimeStamp\": 101\n," +
+ " \"savings\": 101\n," +
+ " \"likes_boba\": false\n," +
+ " \"country\": China\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataDoesNotEqual, jsonArray);
+ assertNull(result);
+ }
+
+ @Test
+ public void testGreaterThanMockDataPass() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"eventTimeStamp\": 8\n," +
+ " \"savings\": 11\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataGreaterThan, jsonArray);
+ assertNotNull(result);
+ }
+
+ @Test
+ public void testGreaterThanMockDataFail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"eventTimeStamp\": 6\n," +
+ " \"savings\": 9\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataGreaterThan, jsonArray);
+ assertNull(result);
+ }
+
+ @Test
+ public void testGreaterThanOrEqualMockDataPass() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"eventTimeStamp\": 13\n," +
+ " \"savings\": 21\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataGreaterThanOrEqualTo, jsonArray);
+ assertNotNull(result);
+ }
+
+ @Test
+ public void testGreaterThanOrEqualMockDataFail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"eventTimeStamp\": 11\n," +
+ " \"savings\": 19\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataGreaterThanOrEqualTo, jsonArray);
+ assertNull(result);
+ }
+
+ @Test
+ public void testLessThanMockDataPass() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"eventTimeStamp\": 5\n," +
+ " \"savings\": 9\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataLessThan, jsonArray);
+ assertNotNull(result);
+ }
+
+ @Test
+ public void testLessThanMockDataFail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"eventTimeStamp\": 7\n," +
+ " \"savings\": 11\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataLessThan, jsonArray);
+ assertNull(result);
+ }
+
+ @Test
+ public void testLessThanOrEqualsToMockDataPass() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"eventTimeStamp\": 56\n," +
+ " \"savings\": 60\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataLessThanOrEqualTo, jsonArray);
+ assertNotNull(result);
+ }
+
+ @Test
+ public void testLessThanOrEqualsToMockDataFail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"eventTimeStamp\": 57\n," +
+ " \"savings\": 61\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataLessThanOrEqualTo, jsonArray);
+ assertNull(result);
+ }
+
+ @Test
+ public void testIsSetMockDataPass() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"eventTimeStamp\":\"10\"\n," +
+ " \"savings\":\"20\"\n," +
+ " \"country\":\"fwef\"\n," +
+ " \"saved_cars\":\"zdf\"\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataIsSet, jsonArray);
+ assertNotNull(result);
+ }
+
+ @Test
+ public void testIsSetMockDataFail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"eventTimeStamp\":\"12\"\n," +
+ " \"savings\":\"321\"\n," +
+ " \"country\":\"\"\n," +
+ " \"saved_cars\":\"\"\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataIsSet, jsonArray);
+ assertNull(result);
+ }
+
+ @Test
+ public void testContainsMockDataPass() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"country\":\"Taiwan\"\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataContains, jsonArray);
+ assertNotNull(result);
+ }
+
+ @Test
+ public void testContainsMockDataFail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"country\":\"Canada\"\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataContains, jsonArray);
+ assertNull(result);
+ }
+
+ @Test
+ public void testMatchesRegexMockDataPass() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"country\":\"Taiwan\"\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataMatchesRegex, jsonArray);
+ assertNotNull(result);
+ }
+
+ @Test
+ public void testMatchesRegexMockDataFail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"country\":\"Canada\"\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataMatchesRegex, jsonArray);
+ assertNull(result);
+ }
+
+ @Test
+ public void testStartsWithMockDataPass() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"country\":\"Taiwan\"\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataStartsWith, jsonArray);
+ assertNotNull(result);
+ }
+
+ @Test
+ public void testStartsWithMockDataFail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"country\":\"Canada\"\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataStartsWith, jsonArray);
+ assertNull(result);
+ }
+}
diff --git a/iterableapi/src/androidTest/java/com/iterable/iterableapi/util/DoesNotEqualCriteriaMatchTest.java b/iterableapi/src/androidTest/java/com/iterable/iterableapi/util/DoesNotEqualCriteriaMatchTest.java
new file mode 100644
index 000000000..819263e17
--- /dev/null
+++ b/iterableapi/src/androidTest/java/com/iterable/iterableapi/util/DoesNotEqualCriteriaMatchTest.java
@@ -0,0 +1,290 @@
+package com.iterable.iterableapi.util;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.json.JSONArray;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class DoesNotEqualCriteriaMatchTest {
+
+ static final String mockDataLongDoesNotEqual = "{\n" +
+ " \"count\": 1,\n" +
+ " \"criteriaSets\": [\n" +
+ " {\n" +
+ " \"criteriaId\": \"194\",\n" +
+ " \"name\": \"Contact: Phone Number != 57688559\",\n" +
+ " \"createdAt\": 1721337331194,\n" +
+ " \"updatedAt\": 1722338525737,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"eventTimeStamp\",\n" +
+ " \"comparatorType\": \"DoesNotEqual\",\n" +
+ " \"value\": \"15\",\n" +
+ " \"fieldType\": \"long\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+ static final String mockDataStringDoesNotEqual = "{\n" +
+ " \"count\": 1,\n" +
+ " \"criteriaSets\": [\n" +
+ " {\n" +
+ " \"criteriaId\": \"194\",\n" +
+ " \"name\": \"Contact: Phone Number != 57688559\",\n" +
+ " \"createdAt\": 1721337331194,\n" +
+ " \"updatedAt\": 1722338525737,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"phoneNumber\",\n" +
+ " \"comparatorType\": \"DoesNotEqual\",\n" +
+ " \"value\": \"57688559\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+ static final String mockDataBooleanDoesNotEqual = "{\n" +
+ " \"count\": 1,\n" +
+ " \"criteriaSets\": [\n" +
+ " {\n" +
+ " \"criteriaId\": \"194\",\n" +
+ " \"name\": \"Contact: Phone Number != 57688559\",\n" +
+ " \"createdAt\": 1721337331194,\n" +
+ " \"updatedAt\": 1722338525737,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"field\": \"subscribed\",\n" +
+ " \"fieldType\": \"boolean\",\n" +
+ " \"comparatorType\": \"DoesNotEqual\",\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"id\": 25,\n" +
+ " \"value\": \"true\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+ static final String mockDataDoubleDoesNotEqual = "{\n" +
+ " \"count\": 1,\n" +
+ " \"criteriaSets\": [\n" +
+ " {\n" +
+ " \"criteriaId\": \"194\",\n" +
+ " \"name\": \"Contact: Phone Number != 57688559\",\n" +
+ " \"createdAt\": 1721337331194,\n" +
+ " \"updatedAt\": 1722338525737,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"savings\",\n" +
+ " \"comparatorType\": \"DoesNotEqual\",\n" +
+ " \"value\": \"19.99\",\n" +
+ " \"fieldType\": \"double\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+ private CriteriaCompletionChecker evaluator;
+
+ @Before
+ public void setUp() {
+ evaluator = new CriteriaCompletionChecker();
+ }
+
+ @Test
+ public void testCompareDataLongDoesNotEqualPass() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"eventTimeStamp\": 17\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataLongDoesNotEqual, jsonArray);
+ assertTrue(result != null);
+ }
+
+ @Test
+ public void testCompareDataLongDoesNotEqualFail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"eventTimeStamp\": 15\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataLongDoesNotEqual, jsonArray);
+ assertFalse(result != null);
+ }
+
+ @Test
+ public void testCompareDataStringDoesNotEqualPass() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"phoneNumber\": \"5768855923\"\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataStringDoesNotEqual, jsonArray);
+ assertTrue(result != null);
+ }
+
+ @Test
+ public void testCompareDataStringDoesNotEqualFail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"phoneNumber\": \"57688559\"\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataStringDoesNotEqual, jsonArray);
+ assertFalse(result != null);
+ }
+
+ @Test
+ public void testCompareDataBooleanDoesNotEqualPass() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"subscribed\": false\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataBooleanDoesNotEqual, jsonArray);
+ assertTrue(result != null);
+ }
+
+ @Test
+ public void testCompareDataBooleanDoesNotEqualFail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"subscribed\": true\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataBooleanDoesNotEqual, jsonArray);
+ assertFalse(result != null);
+ }
+
+ @Test
+ public void testCompareDataDoubleDoesNotEqualPass() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"savings\": 20.99\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataDoubleDoesNotEqual, jsonArray);
+ assertTrue(result != null);
+ }
+
+ @Test
+ public void testCompareDataDoubleDoesNotEqualFail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"savings\": 19.99\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataDoubleDoesNotEqual, jsonArray);
+ assertFalse(result != null);
+ }
+}
diff --git a/iterableapi/src/androidTest/java/com/iterable/iterableapi/util/IsOneOfAndIsNotOneOfCriteriaMatchTest.java b/iterableapi/src/androidTest/java/com/iterable/iterableapi/util/IsOneOfAndIsNotOneOfCriteriaMatchTest.java
new file mode 100644
index 000000000..f09c150a2
--- /dev/null
+++ b/iterableapi/src/androidTest/java/com/iterable/iterableapi/util/IsOneOfAndIsNotOneOfCriteriaMatchTest.java
@@ -0,0 +1,185 @@
+package com.iterable.iterableapi.util;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import org.json.JSONArray;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class IsOneOfAndIsNotOneOfCriteriaMatchTest {
+
+ private CriteriaCompletionChecker evaluator;
+
+ @Before
+ public void setUp() {
+ evaluator = new CriteriaCompletionChecker();
+ }
+
+ static final String mockDataCriteriaIsOneOf = "{\n" +
+ " \"count\": 5,\n" +
+ " \"criteriaSets\": [\n" +
+ " {\n" +
+ " \"criteriaId\": \"299\",\n" +
+ " \"name\": \"Criteria_Is_One_of\",\n" +
+ " \"createdAt\": 1722851586508,\n" +
+ " \"updatedAt\": 1724404229481,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"country\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"values\": [\n" +
+ " \"China\",\n" +
+ " \"Japan\",\n" +
+ " \"Kenya\"\n" +
+ " ]\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"addresses\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"values\": [\n" +
+ " \"JP\",\n" +
+ " \"DE\",\n" +
+ " \"GB\"\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+ @Test
+ public void testIsOneOfPass() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"country\": \"China\",\n" +
+ " \"addresses\": [\"US\",\"UK\",\"JP\",\"GB\"]\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataCriteriaIsOneOf, jsonArray);
+ assertNotNull(result);
+ }
+
+ @Test
+ public void testIsOneOfFail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"country\": \"Korea\",\n" +
+ " \"addresses\": [\"US\", \"UK\"]\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataCriteriaIsOneOf, jsonArray);
+ assertNull(result);
+ }
+
+ static final String mockDataCriteriaIsNotOneOf = "{\n" +
+ " \"count\": 5,\n" +
+ " \"criteriaSets\": [\n" +
+ " {\n" +
+ " \"criteriaId\": \"299\",\n" +
+ " \"name\": \"Criteria_IsNonOf\",\n" +
+ " \"createdAt\": 1722851586508,\n" +
+ " \"updatedAt\": 1724404229481,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"country\",\n" +
+ " \"comparatorType\": \"DoesNotEqual\",\n" +
+ " \"values\": [\n" +
+ " \"China\",\n" +
+ " \"Japan\",\n" +
+ " \"Kenya\"\n" +
+ " ]\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"addresses\",\n" +
+ " \"comparatorType\": \"DoesNotEqual\",\n" +
+ " \"values\": [\n" +
+ " \"JP\",\n" +
+ " \"DE\",\n" +
+ " \"GB\"\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+ @Test
+ public void testIsNotOneOfPass() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"country\": \"Korea\",\n" +
+ " \"addresses\": [\"US\", \"UK\"]\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataCriteriaIsNotOneOf, jsonArray);
+ assertNotNull(result);
+ }
+
+ @Test
+ public void testIsNotOneOfFail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"country\": \"China\",\n" +
+ " \"addresses\": [\"US\",\"UK\",\"JP\",\"GB\"]\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataCriteriaIsNotOneOf, jsonArray);
+ assertNull(result);
+ }
+}
diff --git a/iterableapi/src/androidTest/java/com/iterable/iterableapi/util/MultiLevelNestedCriteriaMatchTest.java b/iterableapi/src/androidTest/java/com/iterable/iterableapi/util/MultiLevelNestedCriteriaMatchTest.java
new file mode 100644
index 000000000..839b634bd
--- /dev/null
+++ b/iterableapi/src/androidTest/java/com/iterable/iterableapi/util/MultiLevelNestedCriteriaMatchTest.java
@@ -0,0 +1,179 @@
+package com.iterable.iterableapi.util;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import org.json.JSONArray;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class MultiLevelNestedCriteriaMatchTest {
+
+ static final String mockDataMultiLevelNested = "{\n" +
+ " \"count\": 3,\n" +
+ " \"criteriaSets\": [\n" +
+ " {\n" +
+ " \"criteriaId\": \"425\",\n" +
+ " \"name\": \"Multi level Nested field criteria\",\n" +
+ " \"createdAt\": 1726811375306,\n" +
+ " \"updatedAt\": 1726811375306,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"field\": \"button-clicked.updateCart.updatedShoppingCartItems.quantity\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": \"10\",\n" +
+ " \"fieldType\": \"long\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"field\": \"button-clicked.browserVisit.website.domain\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": \"https://mybrand.com/socks\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+
+ private CriteriaCompletionChecker evaluator;
+
+ @Before
+ public void setUp() {
+ evaluator = new CriteriaCompletionChecker();
+ }
+
+ @Test
+ public void testMultiLevelNestedPass() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"updateCart\": {\n" +
+ " \"updatedShoppingCartItems\": {\n" +
+ " \"quantity\": 10\n" +
+ " }\n" +
+ " },\n" +
+ " \"browserVisit\": {\n" +
+ " \"website\": {\n" +
+ " \"domain\": \"https://mybrand.com/socks\"\n" +
+ " }\n" +
+ " }\n" +
+ " },\n" +
+ " \"eventType\": \"customEvent\",\n" +
+ " \"eventName\": \"button-clicked\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataMultiLevelNested, jsonArray);
+ assertNotNull(result);
+ }
+
+ @Test
+ public void testMultiLevelNestedFail1() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"updateCart\": {\n" +
+ " \"updatedShoppingCartItems\": {\n" +
+ " \"quantity\": 10\n" +
+ " }\n" +
+ " },\n" +
+ " \"browserVisit\": {\n" +
+ " \"website.domain\": \"https://mybrand.com/socks\"\n" +
+ " }\n" +
+ " },\n" +
+ " \"eventType\": \"customEvent\",\n" +
+ " \"eventName\": \"button-clicked\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataMultiLevelNested, jsonArray);
+ assertNull(result);
+ }
+
+ @Test
+ public void testMultiLevelNestedFail2() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"updateCart\": {\n" +
+ " \"updatedShoppingCartItems.quantity\": 10\n" +
+ " },\n" +
+ " \"browserVisit\": {\n" +
+ " \"website.domain\": \"https://mybrand.com/socks\"\n" +
+ " }\n" +
+ " },\n" +
+ " \"eventType\": \"customEvent\",\n" +
+ " \"eventName\": \"button-clicked\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataMultiLevelNested, jsonArray);
+ assertNull(result);
+ }
+
+ @Test
+ public void testMultiLevelNestedFail3() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"button-clicked\": {\n" +
+ " \"updateCart\": {\n" +
+ " \"updatedShoppingCartItems\": {\n" +
+ " \"quantity\": 10\n" +
+ " }\n" +
+ " }\n" +
+ " },\n" +
+ " \"browserVisit\": {\n" +
+ " \"website\": {\n" +
+ " \"domain\": \"https://mybrand.com/socks\"\n" +
+ " }\n" +
+ " }\n" +
+ " },\n" +
+ " \"eventType\": \"customEvent\",\n" +
+ " \"eventName\": \"button-clicked\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataMultiLevelNested, jsonArray);
+ assertNull(result);
+ }
+
+ @Test
+ public void testMultiLevelNestedFail4() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"quantity\": 10,\n" +
+ " \"domain\": \"https://mybrand.com/socks\"\n" +
+ " },\n" +
+ " \"eventType\": \"customEvent\",\n" +
+ " \"eventName\": \"button-clicked\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataMultiLevelNested, jsonArray);
+ assertNull(result);
+ }
+}
diff --git a/iterableapi/src/androidTest/java/com/iterable/iterableapi/util/MultiLevelNestedCriteriaWithArrayMatchTest.java b/iterableapi/src/androidTest/java/com/iterable/iterableapi/util/MultiLevelNestedCriteriaWithArrayMatchTest.java
new file mode 100644
index 000000000..ccaf5120a
--- /dev/null
+++ b/iterableapi/src/androidTest/java/com/iterable/iterableapi/util/MultiLevelNestedCriteriaWithArrayMatchTest.java
@@ -0,0 +1,224 @@
+package com.iterable.iterableapi.util;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import org.json.JSONArray;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class MultiLevelNestedCriteriaWithArrayMatchTest {
+
+ static final String mockDataNestedMultiLevelArrayTrackEvent = "{\n" +
+ " \"count\": 1,\n" +
+ " \"criteriaSets\": [\n" +
+ " {\n" +
+ " \"criteriaId\": \"459\",\n" +
+ " \"name\": \"event a.h.b=d && a.h.c=g\",\n" +
+ " \"createdAt\": 1727717997842,\n" +
+ " \"updatedAt\": 1728024187962,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"field\": \"TopLevelArrayObject.a.h.b\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": \"d\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"field\": \"TopLevelArrayObject.a.h.c\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": \"g\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }";
+
+ static final String mockDataMultiLevelNestedWithArray = "{\n" +
+ " \"count\": 1,\n" +
+ " \"criteriaSets\": [\n" +
+ " {\n" +
+ " \"criteriaId\": \"436\",\n" +
+ " \"name\": \"Criteria 2.1 - 09252024 Bug Bash\",\n" +
+ " \"createdAt\": 1727286807360,\n" +
+ " \"updatedAt\": 1727445082036,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"furniture.material.type\",\n" +
+ " \"comparatorType\": \"Contains\",\n" +
+ " \"value\": \"table\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"furniture.material.color\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"values\": [\"black\"]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}\n";
+
+
+ private CriteriaCompletionChecker evaluator;
+
+ @Before
+ public void setUp() {
+ evaluator = new CriteriaCompletionChecker();
+ }
+
+
+ @Test
+ public void testMultiLevelNestedWithArrayPass() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"furniture\": {\n" +
+ " \"material\": [\n" +
+ " {\n" +
+ " \"type\": \"table\",\n" +
+ " \"color\": \"black\",\n" +
+ " \"lengthInches\": 40,\n" +
+ " \"widthInches\": 60\n" +
+ " },\n" +
+ " {\n" +
+ " \"type\": \"Sofa\",\n" +
+ " \"color\": \"Gray\",\n" +
+ " \"lengthInches\": 20,\n" +
+ " \"widthInches\": 30\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]\n";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataMultiLevelNestedWithArray, jsonArray);
+ assertNotNull(result);
+ }
+
+ @Test
+ public void testMultiLevelNestedWithArrayFail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"furniture\": {\n" +
+ " \"material\": [\n" +
+ " {\n" +
+ " \"type\": \"table\",\n" +
+ " \"color\": \"gray\",\n" +
+ " \"lengthInches\": 40,\n" +
+ " \"widthInches\": 60\n" +
+ " },\n" +
+ " {\n" +
+ " \"type\": \"Sofa\",\n" +
+ " \"color\": \"black\",\n" +
+ " \"lengthInches\": 20,\n" +
+ " \"widthInches\": 30\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ "]\n";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataMultiLevelNestedWithArray, jsonArray);
+ assertNull(result);
+ }
+
+ @Test
+ public void testNestedMultiLevelArrayTrackEventPass() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"eventName\": \"TopLevelArrayObject\",\n" +
+ " \"dataFields\": {\n" +
+ " \"a\": {\n" +
+ " \"h\": [\n" +
+ " {\n" +
+ " \"b\": \"e\",\n" +
+ " \"c\": \"h\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"b\": \"d\",\n" +
+ " \"c\": \"g\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " },\n" +
+ " \"eventType\": \"customEvent\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataNestedMultiLevelArrayTrackEvent, jsonArray);
+ assertNotNull(result);
+ }
+
+ @Test
+ public void testNestedMultiLevelArrayTrackEventFail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"eventName\": \"TopLevelArrayObject\",\n" +
+ " \"dataFields\": {\n" +
+ " \"a\": {\n" +
+ " \"h\": [\n" +
+ " {\n" +
+ " \"b\": \"d\",\n" +
+ " \"c\": \"h\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"b\": \"e\",\n" +
+ " \"c\": \"g\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " },\n" +
+ " \"eventType\": \"customEvent\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataNestedMultiLevelArrayTrackEvent, jsonArray);
+ assertNull(result);
+ }
+}
diff --git a/iterableapi/src/androidTest/java/com/iterable/iterableapi/util/NestedCriteriaMatchTest.java b/iterableapi/src/androidTest/java/com/iterable/iterableapi/util/NestedCriteriaMatchTest.java
new file mode 100644
index 000000000..2247f91b8
--- /dev/null
+++ b/iterableapi/src/androidTest/java/com/iterable/iterableapi/util/NestedCriteriaMatchTest.java
@@ -0,0 +1,129 @@
+package com.iterable.iterableapi.util;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import org.json.JSONArray;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class NestedCriteriaMatchTest {
+
+ static final String mockDataNested = " {\n" +
+ " \"count\": 1,\n" +
+ " \"criteriaSets\": [\n" +
+ " {\n" +
+ " \"criteriaId\": \"168\",\n" +
+ " \"name\": \"nested testing\",\n" +
+ " \"createdAt\": 1721251169153,\n" +
+ " \"updatedAt\": 1723488175352,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"furniture\",\n" +
+ " \"comparatorType\": \"IsSet\",\n" +
+ " \"value\": \"\",\n" +
+ " \"fieldType\": \"nested\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"furniture.furnitureColor\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": \"White\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"user\",\n" +
+ " \"field\": \"furniture.furnitureType\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": \"Sofa\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }";
+
+
+ private CriteriaCompletionChecker evaluator;
+
+ @Before
+ public void setUp() {
+ evaluator = new CriteriaCompletionChecker();
+ }
+
+ @Test
+ public void testNestedPass() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"furniture\": [\n" +
+ " {\n" +
+ " \"furnitureType\": \"Sofa\",\n" +
+ " \"furnitureColor\": \"White\",\n" +
+ " \"lengthInches\": 40,\n" +
+ " \"widthInches\": 60\n" +
+ " },\n" +
+ " {\n" +
+ " \"furnitureType\": \"Table\",\n" +
+ " \"furnitureColor\": \"Gray\",\n" +
+ " \"lengthInches\": 20,\n" +
+ " \"widthInches\": 30\n" +
+ " }\n" +
+ " ]\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ " ]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataNested, jsonArray);
+ assertNotNull(result);
+ }
+
+ @Test
+ public void testNestedFail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"furniture\": [\n" +
+ " {\n" +
+ " \"furnitureType\": \"Sofa\",\n" +
+ " \"furnitureColor\": \"Gray\",\n" +
+ " \"lengthInches\": 40,\n" +
+ " \"widthInches\": 60\n" +
+ " },\n" +
+ " {\n" +
+ " \"furnitureType\": \"Table\",\n" +
+ " \"furnitureColor\": \"White\",\n" +
+ " \"lengthInches\": 20,\n" +
+ " \"widthInches\": 30\n" +
+ " }\n" +
+ " ]\n" +
+ " },\n" +
+ " \"eventType\": \"user\"\n" +
+ " }\n" +
+ " ]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataNested, jsonArray);
+ assertNull(result);
+ }
+}
diff --git a/iterableapi/src/androidTest/java/com/iterable/iterableapi/util/SinglePrimitiveArrayNestedCriteriaMatchTest.java b/iterableapi/src/androidTest/java/com/iterable/iterableapi/util/SinglePrimitiveArrayNestedCriteriaMatchTest.java
new file mode 100644
index 000000000..4b9ed846b
--- /dev/null
+++ b/iterableapi/src/androidTest/java/com/iterable/iterableapi/util/SinglePrimitiveArrayNestedCriteriaMatchTest.java
@@ -0,0 +1,98 @@
+package com.iterable.iterableapi.util;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import org.json.JSONArray;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class SinglePrimitiveArrayNestedCriteriaMatchTest {
+
+ static final String mockDataArrayNested = "{\n" +
+ " \"count\": 1,\n" +
+ " \"criteriaSets\": [\n" +
+ " {\n" +
+ " \"criteriaId\": \"467\",\n" +
+ " \"name\": \"Custom event - single primitive\",\n" +
+ " \"createdAt\": 1728166585122,\n" +
+ " \"updatedAt\": 1729581351423,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"field\": \"eventName\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": \"animal_found\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"field\": \"animal_found.count\",\n" +
+ " \"comparatorType\": \"DoesNotEqual\",\n" +
+ " \"value\": \"4\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+
+ private CriteriaCompletionChecker evaluator;
+
+ @Before
+ public void setUp() {
+ evaluator = new CriteriaCompletionChecker();
+ }
+
+ @Test
+ public void testSinglePrimitiveArrayNestedCriteriaMatchPass() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"count\": [5,8,9]\n" +
+ " },\n" +
+ " \"eventType\": \"customEvent\",\n" +
+ " \"eventName\": \"animal_found\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataArrayNested, jsonArray);
+ assertNotNull(result);
+ }
+
+ @Test
+ public void testSinglePrimitiveArrayNestedCriteriaMatchFail() throws Exception {
+ String jsonString = "[\n" +
+ " {\n" +
+ " \"dataFields\": {\n" +
+ " \"count\": [4, 8, 9]\n" +
+ " },\n" +
+ " \"eventType\": \"customEvent\",\n" +
+ " \"eventName\": \"animal_found\"\n" +
+ " }\n" +
+ "]";
+ JSONArray jsonArray = new JSONArray(jsonString);
+ String result = evaluator.getMatchedCriteria(mockDataArrayNested, jsonArray);
+ assertNull(result);
+ }
+}
diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/AnonymousUserManager.java b/iterableapi/src/main/java/com/iterable/iterableapi/AnonymousUserManager.java
new file mode 100644
index 000000000..c82f3d1b1
--- /dev/null
+++ b/iterableapi/src/main/java/com/iterable/iterableapi/AnonymousUserManager.java
@@ -0,0 +1,468 @@
+package com.iterable.iterableapi;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.pm.ApplicationInfo;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.app.NotificationManagerCompat;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.reflect.TypeToken;
+import com.iterable.iterableapi.util.CriteriaCompletionChecker;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Iterator;
+import java.util.List;
+import java.util.UUID;
+
+public class AnonymousUserManager {
+
+ private static final String TAG = "AnonymousUserManager";
+ private final IterableApi iterableApi = IterableApi.sharedInstance;
+
+ void updateAnonSession() {
+ IterableLogger.v(TAG, "updateAnonSession");
+
+ SharedPreferences sharedPref = IterableApi.getInstance().getMainActivityContext().getSharedPreferences(IterableConstants.SHARED_PREFS_FILE, Context.MODE_PRIVATE);
+ String previousData = sharedPref.getString(IterableConstants.SHARED_PREFS_ANON_SESSIONS, "");
+
+ try {
+ int sessionNo = 0;
+ String firstSessionDate = "";
+
+ //If previous session data exists, get previous session number and first session date
+ if (!previousData.isEmpty()) {
+ JSONObject previousDataJson = new JSONObject(previousData);
+ JSONObject sessionObject = previousDataJson.getJSONObject(IterableConstants.SHARED_PREFS_ANON_SESSIONS);
+ sessionNo = sessionObject.getInt(IterableConstants.SHARED_PREFS_SESSION_NO);
+ firstSessionDate = sessionObject.getString(IterableConstants.SHARED_PREFS_FIRST_SESSION);
+ }
+
+ //create new session data object and save it to local storage
+ JSONObject newDataObject = createNewSessionData(sessionNo, firstSessionDate);
+ saveAnonSessionData(sharedPref, newDataObject);
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private JSONObject createNewSessionData(int sessionNo, String firstSessionDate) throws JSONException {
+ JSONObject newDataObject = new JSONObject();
+ newDataObject.put(IterableConstants.SHARED_PREFS_SESSION_NO, sessionNo + 1);
+ newDataObject.put(IterableConstants.SHARED_PREFS_LAST_SESSION, getCurrentTime());
+
+ if (firstSessionDate.isEmpty()) {
+ newDataObject.put(IterableConstants.SHARED_PREFS_FIRST_SESSION, getCurrentTime());
+ } else {
+ newDataObject.put(IterableConstants.SHARED_PREFS_FIRST_SESSION, firstSessionDate);
+ }
+
+ return newDataObject;
+ }
+
+ private void saveAnonSessionData(SharedPreferences sharedPref, JSONObject newDataObject) throws JSONException {
+ JSONObject anonSessionData = new JSONObject();
+ anonSessionData.put(IterableConstants.SHARED_PREFS_ANON_SESSIONS, newDataObject);
+
+ SharedPreferences.Editor editor = sharedPref.edit();
+ editor.putString(IterableConstants.SHARED_PREFS_ANON_SESSIONS, anonSessionData.toString());
+ editor.apply();
+ }
+
+ void trackAnonEvent(String eventName, JSONObject dataFields) {
+ IterableLogger.v(TAG, "trackAnonEvent");
+
+ try {
+ JSONObject newDataObject = new JSONObject();
+ newDataObject.put(IterableConstants.KEY_EVENT_NAME, eventName);
+ newDataObject.put(IterableConstants.KEY_CREATED_AT, getCurrentTime());
+ newDataObject.put(IterableConstants.KEY_DATA_FIELDS, dataFields);
+ newDataObject.put(IterableConstants.KEY_CREATE_NEW_FIELDS, true);
+ newDataObject.put(IterableConstants.SHARED_PREFS_EVENT_TYPE, IterableConstants.TRACK_EVENT);
+ storeEventListToLocalStorage(newDataObject);
+
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ }
+
+ void trackAnonUpdateUser(JSONObject dataFields) {
+ IterableLogger.v(TAG, "updateAnonUser");
+ try {
+ JSONObject newDataObject = new JSONObject();
+ newDataObject.put(IterableConstants.KEY_DATA_FIELDS, dataFields);
+ newDataObject.put(IterableConstants.SHARED_PREFS_EVENT_TYPE, IterableConstants.UPDATE_USER);
+ storeUserUpdateToLocalStorage(newDataObject);
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ }
+
+ void trackAnonTokenRegistration(String token) {
+ IterableLogger.v(TAG, "trackAnonTokenRegistration");
+ try {
+ JSONObject newDataObject = new JSONObject();
+ newDataObject.put(IterableConstants.KEY_TOKEN, token);
+ newDataObject.put(IterableConstants.SHARED_PREFS_EVENT_TYPE, IterableConstants.TRACK_TOKEN_REGISTRATION);
+ storeEventListToLocalStorage(newDataObject);
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ }
+
+ void trackAnonPurchaseEvent(double total, @NonNull List items, @Nullable JSONObject dataFields) {
+
+ IterableLogger.v(TAG, "trackAnonPurchaseEvent");
+ try {
+ JSONObject newDataObject = new JSONObject();
+ Gson gson = new GsonBuilder().create();
+
+ newDataObject.put(IterableConstants.KEY_ITEMS, gson.toJsonTree(items).getAsJsonArray().toString());
+ newDataObject.put(IterableConstants.KEY_CREATED_AT, getCurrentTime());
+ newDataObject.put(IterableConstants.KEY_DATA_FIELDS, dataFields);
+ newDataObject.put(IterableConstants.KEY_TOTAL, total);
+ newDataObject.put(IterableConstants.SHARED_PREFS_EVENT_TYPE, IterableConstants.TRACK_PURCHASE);
+ storeEventListToLocalStorage(newDataObject);
+
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ }
+
+ void trackAnonUpdateCart(@NonNull List items) {
+
+ IterableLogger.v(TAG, "trackAnonUpdateCart");
+
+ try {
+ Gson gson = new GsonBuilder().create();
+ JSONObject newDataObject = new JSONObject();
+ newDataObject.put(IterableConstants.KEY_ITEMS, gson.toJsonTree(items).getAsJsonArray().toString());
+ newDataObject.put(IterableConstants.SHARED_PREFS_EVENT_TYPE, IterableConstants.TRACK_UPDATE_CART);
+ newDataObject.put(IterableConstants.KEY_CREATED_AT, getCurrentTime());
+ storeEventListToLocalStorage(newDataObject);
+
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ }
+
+ void getCriteria() {
+ iterableApi.apiClient.getCriteriaList(data -> {
+ if (data != null) {
+ try {
+ JSONObject mockDataObject = new JSONObject(data);
+ SharedPreferences sharedPref = IterableApi.getInstance().getMainActivityContext().getSharedPreferences(IterableConstants.SHARED_PREFS_FILE, Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = sharedPref.edit();
+ editor.putString(IterableConstants.SHARED_PREFS_CRITERIA, mockDataObject.toString());
+ editor.apply();
+
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ }
+
+ private String checkCriteriaCompletion() {
+ SharedPreferences sharedPref = IterableApi.getInstance().getMainActivityContext().getSharedPreferences(IterableConstants.SHARED_PREFS_FILE, Context.MODE_PRIVATE);
+ String criteriaData = sharedPref.getString(IterableConstants.SHARED_PREFS_CRITERIA, "");
+
+ JSONArray localStoredEventListAndUserUpdates = getEventListFromLocalStorage();
+ JSONObject localStoredUserUpdateObj = getUserUpdateObjFromLocalStorage();
+
+ localStoredEventListAndUserUpdates.put(localStoredUserUpdateObj);
+
+ try {
+ if (!criteriaData.isEmpty() && localStoredEventListAndUserUpdates.length() > 0) {
+ CriteriaCompletionChecker checker = new CriteriaCompletionChecker();
+ return checker.getMatchedCriteria(criteriaData, localStoredEventListAndUserUpdates);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ return null;
+ }
+
+ private void createAnonymousUser(String criteriaId) {
+ SharedPreferences sharedPref = IterableApi.getInstance().getMainActivityContext().getSharedPreferences(IterableConstants.SHARED_PREFS_FILE, Context.MODE_PRIVATE);
+ updateAnonSession();
+
+ //get session data
+ String userData = sharedPref.getString(IterableConstants.SHARED_PREFS_ANON_SESSIONS, "");
+
+ //generate anon user id
+ String userId = UUID.randomUUID().toString();
+
+ try {
+ if (!userData.isEmpty()) {
+ JSONObject updateUserObj = getUserUpdateObjFromLocalStorage();
+ JSONObject updateUserDataFields = null;
+ if (updateUserObj.has(IterableConstants.KEY_DATA_FIELDS)) {
+ updateUserDataFields = updateUserObj.getJSONObject(IterableConstants.KEY_DATA_FIELDS);
+ }
+ JSONObject userSessionDataJson = new JSONObject(userData);
+ JSONObject userDataJson = userSessionDataJson.getJSONObject(IterableConstants.SHARED_PREFS_ANON_SESSIONS);
+
+ //update user data
+ if (!getPushStatus().isEmpty()) {
+ userDataJson.put(IterableConstants.SHARED_PREFS_PUSH_OPT_IN, getPushStatus());
+ }
+ userDataJson.put(IterableConstants.SHARED_PREFS_CRITERIA_ID, Integer.valueOf(criteriaId));
+
+ //track anon session with new user
+ iterableApi.apiClient.trackAnonSession(getCurrentTime(), userId, userDataJson, updateUserDataFields, data -> {
+ // success handler
+ if (IterableApi.getInstance().config.iterableAnonUserHandler != null) {
+ IterableApi.getInstance().config.iterableAnonUserHandler.onAnonUserCreated(userId);
+ }
+ IterableApi.getInstance().setAnonUser(userId);
+ }, (reason, data) -> handleTrackFailure(data));
+ }
+
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private void handleTrackFailure(JSONObject data) {
+ if (data != null && data.has(IterableConstants.HTTP_STATUS_CODE)) {
+ try {
+ int statusCode = (int) data.get(IterableConstants.HTTP_STATUS_CODE);
+ if (statusCode == 409) {
+ getCriteria(); // refetch the criteria
+ }
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ void syncEventsAndUserUpdate() {
+ JSONArray trackEventList = getEventListFromLocalStorage();
+ JSONObject updateUserObj = getUserUpdateObjFromLocalStorage();
+ Gson gson = new GsonBuilder().create();
+
+ if (trackEventList.length() == 0 && !updateUserObj.has(IterableConstants.KEY_DATA_FIELDS)) return;
+
+ for (int i = 0; i < trackEventList.length(); i++) {
+ try {
+ JSONObject event = trackEventList.getJSONObject(i);
+ String eventType = event.getString(IterableConstants.SHARED_PREFS_EVENT_TYPE);
+ switch (eventType) {
+ case IterableConstants.TRACK_EVENT: {
+ handleTrackEvent(event);
+ break;
+ }
+ case IterableConstants.TRACK_PURCHASE: {
+ handleTrackPurchase(event, gson);
+ break;
+ }
+ case IterableConstants.TRACK_UPDATE_CART: {
+ handleUpdateCart(event, gson);
+ break;
+ }
+ default:
+ break;
+ }
+ } catch (JSONException e) {
+ IterableLogger.d(TAG, "Event Sync Failure");
+ }
+ }
+
+ try {
+ handleUpdateUser(updateUserObj);
+ } catch (JSONException e) {
+ IterableLogger.d(TAG, "Handle User Update Failure");
+ }
+ }
+
+ private void handleTrackEvent(JSONObject event) throws JSONException {
+ String createdAt = getStringValue(event);
+ JSONObject dataFields = getDataFields(event);
+ iterableApi.apiClient.track(event.getString(IterableConstants.KEY_EVENT_NAME), 0, 0, dataFields, createdAt);
+ }
+
+ private void handleTrackPurchase(JSONObject event, Gson gson) throws JSONException {
+ Type listType = new TypeToken>() { }.getType();
+ List list = gson.fromJson(event.getString(IterableConstants.KEY_ITEMS), listType);
+
+ long createdAt = getLongValue(event);
+ JSONObject dataFields = getDataFields(event);
+ iterableApi.apiClient.trackPurchase(event.getDouble(IterableConstants.KEY_TOTAL), list, dataFields, createdAt);
+ }
+
+ private void handleUpdateCart(JSONObject event, Gson gson) throws JSONException {
+ Type listType = new TypeToken>() { }.getType();
+ List list = gson.fromJson(event.getString(IterableConstants.KEY_ITEMS), listType);
+
+ long createdAt = getLongValue(event);
+ iterableApi.apiClient.updateCart(list, createdAt);
+ }
+
+ private void handleUpdateUser(JSONObject event) throws JSONException {
+ iterableApi.apiClient.updateUser(event.getJSONObject(IterableConstants.KEY_DATA_FIELDS), false);
+ }
+
+ private String getStringValue(JSONObject event) throws JSONException {
+ return event.has(IterableConstants.KEY_CREATED_AT) ? event.getString(IterableConstants.KEY_CREATED_AT) : "";
+ }
+
+ private long getLongValue(JSONObject event) throws JSONException {
+ return event.has(IterableConstants.KEY_CREATED_AT) ? Long.parseLong(event.getString(IterableConstants.KEY_CREATED_AT)) : 0L;
+ }
+ private JSONObject getDataFields(JSONObject event) throws JSONException {
+ return event.has(IterableConstants.KEY_DATA_FIELDS) ? new JSONObject(event.getString(IterableConstants.KEY_DATA_FIELDS)) : null;
+ }
+
+ public void clearVisitorEventsAndUserData() {
+ SharedPreferences sharedPref = IterableApi.getInstance().getMainActivityContext().getSharedPreferences(IterableConstants.SHARED_PREFS_FILE, Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = sharedPref.edit();
+ editor.putString(IterableConstants.SHARED_PREFS_ANON_SESSIONS, "");
+ editor.putString(IterableConstants.SHARED_PREFS_EVENT_LIST_KEY, "");
+ editor.putString(IterableConstants.SHARED_PREFS_USER_UPDATE_OBJECT_KEY, "");
+ editor.apply();
+ }
+
+ private void storeEventListToLocalStorage(JSONObject newDataObject) {
+ if (!iterableApi.getVisitorUsageTracked()) {
+ return;
+ }
+ JSONArray eventList = getEventListFromLocalStorage();
+
+ eventList.put(newDataObject);
+
+ eventList = enforceEventThresholdLimit(eventList);
+ saveEventListToLocalStorage(eventList);
+
+ String criteriaId = checkCriteriaCompletion();
+ Log.i("TEST_USER", "criteriaId::" + String.valueOf(criteriaId));
+
+ if (criteriaId != null) {
+ createAnonymousUser(criteriaId);
+ }
+ Log.i("criteriaId::", String.valueOf(criteriaId != null));
+ }
+
+ private void storeUserUpdateToLocalStorage(JSONObject newDataObject) throws JSONException {
+ if (!iterableApi.getVisitorUsageTracked()) {
+ return;
+ }
+
+ JSONObject userUpdateObject = getUserUpdateObjFromLocalStorage();
+ mergeUpdateUserObjects(userUpdateObject, newDataObject);
+
+ saveUserUpdateObjectToLocalStorage(userUpdateObject);
+
+ String criteriaId = checkCriteriaCompletion();
+ Log.i("TEST_USER", "criteriaId::" + String.valueOf(criteriaId));
+
+ if (criteriaId != null) {
+ createAnonymousUser(criteriaId);
+ }
+ Log.i("criteriaId::", String.valueOf(criteriaId != null));
+ }
+
+ private void mergeUpdateUserObjects(JSONObject target, JSONObject source) throws JSONException {
+ Iterator keys = source.keys();
+ while (keys.hasNext()) {
+ String key = keys.next();
+ Object value = source.get(key);
+ // if value is an object, recurse
+ if (value instanceof JSONObject && target.has(key)) {
+ mergeUpdateUserObjects(target.getJSONObject(key), (JSONObject) value);
+ } else {
+ // If the key doesn't exist in the target, just add it
+ target.put(key, value);
+ }
+ }
+ }
+
+ private JSONArray enforceEventThresholdLimit(JSONArray eventDataArray) {
+ int lengthOfData = eventDataArray.length();
+ int eventThresholdLimit = iterableApi.config.eventThresholdLimit;
+
+ if (lengthOfData > eventThresholdLimit) {
+ int difference = lengthOfData - eventThresholdLimit;
+ ArrayList eventListData = new ArrayList<>();
+ for (int i = difference; i < eventDataArray.length(); i++) {
+ try {
+ eventListData.add(eventDataArray.getJSONObject(i));
+ } catch (JSONException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return new JSONArray(eventListData);
+ }
+ return eventDataArray;
+ }
+
+ private void saveEventListToLocalStorage(JSONArray eventDataArray) {
+ SharedPreferences sharedPref = IterableApi.getInstance().getMainActivityContext().getSharedPreferences(IterableConstants.SHARED_PREFS_FILE, Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = sharedPref.edit();
+ editor.putString(IterableConstants.SHARED_PREFS_EVENT_LIST_KEY, eventDataArray.toString());
+ editor.apply();
+ }
+
+ private void saveUserUpdateObjectToLocalStorage(JSONObject userUpdateObject) {
+ SharedPreferences sharedPref = IterableApi.getInstance().getMainActivityContext().getSharedPreferences(IterableConstants.SHARED_PREFS_FILE, Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = sharedPref.edit();
+ editor.putString(IterableConstants.SHARED_PREFS_USER_UPDATE_OBJECT_KEY, userUpdateObject.toString());
+ editor.apply();
+ }
+
+ private JSONArray getEventListFromLocalStorage() {
+ SharedPreferences sharedPref = IterableApi.getInstance().getMainActivityContext().getSharedPreferences(IterableConstants.SHARED_PREFS_FILE, Context.MODE_PRIVATE);
+ String eventListJson = sharedPref.getString(IterableConstants.SHARED_PREFS_EVENT_LIST_KEY, "");
+ JSONArray eventListArray = new JSONArray();
+ try {
+ if (!eventListJson.isEmpty()) {
+ eventListArray = new JSONArray(eventListJson);
+ }
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+
+ return eventListArray;
+ }
+
+ private JSONObject getUserUpdateObjFromLocalStorage() {
+ SharedPreferences sharedPref = IterableApi.getInstance().getMainActivityContext().getSharedPreferences(IterableConstants.SHARED_PREFS_FILE, Context.MODE_PRIVATE);
+ String userUpdateJson = sharedPref.getString(IterableConstants.SHARED_PREFS_USER_UPDATE_OBJECT_KEY, "");
+ JSONObject userUpdateObject = new JSONObject();
+ try {
+ if (!userUpdateJson.isEmpty()) {
+ userUpdateObject = new JSONObject(userUpdateJson);
+ }
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+
+ return userUpdateObject;
+ }
+
+ private long getCurrentTime() {
+ return Calendar.getInstance().getTimeInMillis() / 1000;
+ }
+
+ private String getPushStatus() {
+ NotificationManagerCompat notificationManagerCompat = NotificationManagerCompat.from(IterableApi.getInstance().getMainActivityContext());
+ if (notificationManagerCompat.areNotificationsEnabled()) {
+ ApplicationInfo applicationInfo = IterableApi.getInstance().getMainActivityContext().getApplicationInfo();
+ int stringId = applicationInfo.labelRes;
+ return stringId == 0 ? applicationInfo.nonLocalizedLabel.toString() : IterableApi.getInstance().getMainActivityContext().getString(stringId);
+ } else {
+ return "";
+ }
+ }
+}
\ No newline at end of file
diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/AnonymousUserMerge.java b/iterableapi/src/main/java/com/iterable/iterableapi/AnonymousUserMerge.java
new file mode 100644
index 000000000..5d6ae1865
--- /dev/null
+++ b/iterableapi/src/main/java/com/iterable/iterableapi/AnonymousUserMerge.java
@@ -0,0 +1,26 @@
+package com.iterable.iterableapi;
+
+public class AnonymousUserMerge {
+ private static final String TAG = "AnonymousUserMerge";
+
+ void tryMergeUser(IterableApiClient apiClient, String anonymousUserId, String destinationUser, boolean isEmail, boolean merge, MergeResultCallback callback) {
+ IterableLogger.v(TAG, "tryMergeUser");
+ if (anonymousUserId != null && merge) {
+ String destinationEmail = isEmail ? destinationUser : null;
+ String destinationUserId = isEmail ? null : destinationUser;
+ apiClient.mergeUser(null, anonymousUserId, destinationEmail, destinationUserId, data -> {
+ if (callback != null) {
+ callback.onResult(IterableConstants.MERGE_SUCCESSFUL, null); // Notify success
+ }
+ }, (reason, data) -> {
+ if (callback != null) {
+ callback.onResult(null, reason); // Notify failure
+ }
+ });
+ } else {
+ if (callback != null) {
+ callback.onResult(IterableConstants.MERGE_NOTREQUIRED, null); // Return true if inputs are null as per original logic
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableAnonUserHandler.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableAnonUserHandler.java
new file mode 100644
index 000000000..2cd3343b5
--- /dev/null
+++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableAnonUserHandler.java
@@ -0,0 +1,5 @@
+package com.iterable.iterableapi;
+
+public interface IterableAnonUserHandler {
+ void onAnonUserCreated(String userId);
+}
\ No newline at end of file
diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java
index 224b60d02..d316fc1c6 100644
--- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java
+++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java
@@ -19,6 +19,7 @@
import java.util.HashMap;
import java.util.List;
+import java.util.Objects;
import java.util.UUID;
/**
@@ -35,6 +36,7 @@ public class IterableApi {
private String _apiKey;
private String _email;
private String _userId;
+ private String _userIdAnon;
private String _authToken;
private boolean _debugMode;
private Bundle _payloadData;
@@ -45,6 +47,8 @@ public class IterableApi {
private IterableHelper.FailureHandler _setUserFailureCallbackHandler;
IterableApiClient apiClient = new IterableApiClient(new IterableApiAuthProvider());
+ private static final AnonymousUserManager anonymousUserManager = new AnonymousUserManager();
+ private static final AnonymousUserMerge anonymousUserMerge = new AnonymousUserMerge();
private @Nullable IterableInAppManager inAppManager;
private @Nullable IterableEmbeddedManager embeddedManager;
private String inboxSessionId;
@@ -350,7 +354,15 @@ private void logoutPreviousUser() {
apiClient.onLogout();
}
- private void onLogin(@Nullable String authToken) {
+ private void onLogin(
+ @Nullable String authToken,
+ String userIdOrEmail,
+ boolean isEmail,
+ boolean merge,
+ boolean replay,
+ boolean isAnon,
+ @Nullable IterableHelper.FailureHandler failureHandler
+ ) {
if (!isInitialized()) {
setAuthToken(null);
return;
@@ -359,8 +371,9 @@ private void onLogin(@Nullable String authToken) {
getAuthManager().pauseAuthRetries(false);
if (authToken != null) {
setAuthToken(authToken);
+ attemptMergeAndEventReplay(userIdOrEmail, isEmail, merge, replay, isAnon, failureHandler);
} else {
- getAuthManager().requestNewAuthToken(false);
+ getAuthManager().requestNewAuthToken(false, data -> attemptMergeAndEventReplay(userIdOrEmail, isEmail, merge, replay, isAnon, failureHandler));
}
}
@@ -459,6 +472,7 @@ private void storeAuthData() {
if (iterableKeychain != null) {
iterableKeychain.saveEmail(_email);
iterableKeychain.saveUserId(_userId);
+ iterableKeychain.saveUserIdAnon(_userIdAnon);
iterableKeychain.saveAuthToken(_authToken);
} else {
IterableLogger.e(TAG, "Shared preference creation failed. ");
@@ -473,6 +487,7 @@ private void retrieveEmailAndUserId() {
if (iterableKeychain != null) {
_email = iterableKeychain.getEmail();
_userId = iterableKeychain.getUserId();
+ _userIdAnon = iterableKeychain.getUserIdAnon();
_authToken = iterableKeychain.getAuthToken();
} else {
IterableLogger.e(TAG, "retrieveEmailAndUserId: Shared preference creation failed. Could not retrieve email/userId");
@@ -501,6 +516,12 @@ public String getUserId() {
return _userId;
}
+ @Nullable
+ @Override
+ public String getUserIdAnon() {
+ return _userIdAnon;
+ }
+
@Nullable
@Override
public String getAuthToken() {
@@ -546,6 +567,12 @@ void setAuthToken(String authToken, boolean bypassAuth) {
protected void registerDeviceToken(final @Nullable String email, final @Nullable String userId, final @Nullable String authToken, final @NonNull String applicationName, final @NonNull String deviceToken, final HashMap deviceAttributes) {
if (deviceToken != null) {
+ if (!checkSDKInitialization() && _userIdAnon == null) {
+ if (sharedInstance.config.enableAnonActivation) {
+ anonymousUserManager.trackAnonTokenRegistration(deviceToken);
+ }
+ return;
+ }
final Thread registrationThread = new Thread(new Runnable() {
public void run() {
registerDeviceToken(email, userId, authToken, applicationName, deviceToken, null, deviceAttributes);
@@ -641,15 +668,21 @@ public static void initialize(@NonNull Context context, @NonNull String apiKey,
loadLastSavedConfiguration(context);
IterablePushNotificationUtil.processPendingAction(context);
+
+ if (!sharedInstance.checkSDKInitialization() && sharedInstance._userIdAnon == null && sharedInstance.config.enableAnonActivation && sharedInstance.getVisitorUsageTracked()) {
+ anonymousUserManager.updateAnonSession();
+ anonymousUserManager.getCriteria();
+ }
+
if (DeviceInfoUtils.isFireTV(context.getPackageManager())) {
try {
JSONObject dataFields = new JSONObject();
JSONObject deviceDetails = new JSONObject();
DeviceInfoUtils.populateDeviceDetails(deviceDetails, context, sharedInstance.getDeviceId());
dataFields.put(IterableConstants.KEY_FIRETV, deviceDetails);
- sharedInstance.apiClient.updateUser(dataFields, false);
+ sharedInstance.updateUser(dataFields, false);
} catch (JSONException e) {
- IterableLogger.e(TAG, "initialize: exception", e);
+ IterableLogger.e(TAG, "initialize: exception", e);
}
}
}
@@ -730,24 +763,42 @@ public IterableAttributionInfo getAttributionInfo() {
public void pauseAuthRetries(boolean pauseRetry) {
getAuthManager().pauseAuthRetries(pauseRetry);
if (!pauseRetry) { // request new auth token as soon as unpause
- getAuthManager().requestNewAuthToken(false);
+ getAuthManager().requestNewAuthToken(false, null);
}
}
public void setEmail(@Nullable String email) {
- setEmail(email, null, null, null);
+ setEmail(email, null, null, null, null);
+ }
+
+ public void setEmail(@Nullable String email, IterableIdentityResolution identityResolution) {
+ setEmail(email, null, identityResolution, null, null);
}
public void setEmail(@Nullable String email, @Nullable IterableHelper.SuccessHandler successHandler, @Nullable IterableHelper.FailureHandler failureHandler) {
- setEmail(email, null, successHandler, failureHandler);
+ setEmail(email, null, null, successHandler, failureHandler);
+ }
+
+ public void setEmail(@Nullable String email, IterableIdentityResolution identityResolution, @Nullable IterableHelper.SuccessHandler successHandler, @Nullable IterableHelper.FailureHandler failureHandler) {
+ setEmail(email, null, identityResolution, successHandler, failureHandler);
}
public void setEmail(@Nullable String email, @Nullable String authToken) {
- setEmail(email, authToken, null, null);
+ setEmail(email, authToken, null, null, null);
+ }
+
+ public void setEmail(@Nullable String email, @Nullable String authToken, IterableIdentityResolution identityResolution) {
+ setEmail(email, authToken, identityResolution, null, null);
}
public void setEmail(@Nullable String email, @Nullable String authToken, @Nullable IterableHelper.SuccessHandler successHandler, @Nullable IterableHelper.FailureHandler failureHandler) {
- //Only if passed in same non-null email
+ setEmail(email, authToken, null, successHandler, failureHandler);
+ }
+
+ public void setEmail(@Nullable String email, @Nullable String authToken, @Nullable IterableIdentityResolution iterableIdentityResolution, @Nullable IterableHelper.SuccessHandler successHandler, @Nullable IterableHelper.FailureHandler failureHandler) {
+ boolean replay = isReplay(iterableIdentityResolution);
+ boolean merge = isMerge(iterableIdentityResolution);
+
if (_email != null && _email.equals(email)) {
checkAndUpdateAuthToken(authToken);
return;
@@ -761,27 +812,59 @@ public void setEmail(@Nullable String email, @Nullable String authToken, @Nullab
_email = email;
_userId = null;
+
+ if (config.authHandler == null) {
+ attemptMergeAndEventReplay(email, true, merge, replay, false, failureHandler);
+ }
+
+ _userIdAnon = null;
+
_setUserSuccessCallbackHandler = successHandler;
_setUserFailureCallbackHandler = failureHandler;
storeAuthData();
- onLogin(authToken);
+ onLogin(authToken, email, true, merge, replay, false, failureHandler);
+ }
+
+ public void setAnonUser(@Nullable String userId) {
+ _userIdAnon = userId;
+ setUserId(userId, null, null, null, null, true);
+ storeAuthData();
}
public void setUserId(@Nullable String userId) {
- setUserId(userId, null, null, null);
+ setUserId(userId, null, null, null, null, false);
+ }
+
+ public void setUserId(@Nullable String userId, IterableIdentityResolution identityResolution) {
+ setUserId(userId, null, identityResolution, null, null, false);
}
public void setUserId(@Nullable String userId, @Nullable IterableHelper.SuccessHandler successHandler, @Nullable IterableHelper.FailureHandler failureHandler) {
- setUserId(userId, null, successHandler, failureHandler);
+ setUserId(userId, null, null, successHandler, failureHandler, false);
+ }
+
+ public void setUserId(@Nullable String userId, IterableIdentityResolution identityResolution, @Nullable IterableHelper.SuccessHandler successHandler, @Nullable IterableHelper.FailureHandler failureHandler) {
+ setUserId(userId, null, identityResolution, successHandler, failureHandler, false);
}
public void setUserId(@Nullable String userId, @Nullable String authToken) {
- setUserId(userId, authToken, null, null);
+ setUserId(userId, authToken, null, null, null, false);
+ }
+
+ public void setUserId(@Nullable String userId, @Nullable String authToken, IterableIdentityResolution identityResolution) {
+ setUserId(userId, authToken, identityResolution, null, null, false);
+
}
public void setUserId(@Nullable String userId, @Nullable String authToken, @Nullable IterableHelper.SuccessHandler successHandler, @Nullable IterableHelper.FailureHandler failureHandler) {
- //If same non null userId is passed
+ setUserId(userId, authToken, null, successHandler, failureHandler, false);
+ }
+
+ public void setUserId(@Nullable String userId, @Nullable String authToken, @Nullable IterableIdentityResolution iterableIdentityResolution, @Nullable IterableHelper.SuccessHandler successHandler, @Nullable IterableHelper.FailureHandler failureHandler, boolean isAnon) {
+ boolean replay = isReplay(iterableIdentityResolution);
+ boolean merge = isMerge(iterableIdentityResolution);
+
if (_userId != null && _userId.equals(userId)) {
checkAndUpdateAuthToken(authToken);
return;
@@ -795,11 +878,52 @@ public void setUserId(@Nullable String userId, @Nullable String authToken, @Null
_email = null;
_userId = userId;
+
+ if (config.authHandler == null) {
+ attemptMergeAndEventReplay(userId, false, merge, replay, isAnon, failureHandler);
+ }
+
_setUserSuccessCallbackHandler = successHandler;
_setUserFailureCallbackHandler = failureHandler;
storeAuthData();
- onLogin(authToken);
+ onLogin(authToken, userId, false, merge, replay, isAnon, failureHandler);
+ }
+
+ private boolean isMerge(@Nullable IterableIdentityResolution iterableIdentityResolution) {
+ return (iterableIdentityResolution != null) ? iterableIdentityResolution.getMergeOnAnonymousToKnown() : config.identityResolution.getMergeOnAnonymousToKnown();
+ }
+
+ private boolean isReplay(@Nullable IterableIdentityResolution iterableIdentityResolution) {
+ return (iterableIdentityResolution != null) ? iterableIdentityResolution.getReplayOnVisitorToKnown() : config.identityResolution.getReplayOnVisitorToKnown();
+ }
+
+ private void attemptMergeAndEventReplay(@Nullable String emailOrUserId, boolean isEmail, boolean merge, boolean replay, boolean isAnon, IterableHelper.FailureHandler failureHandler) {
+ if (config.enableAnonActivation) {
+ if (emailOrUserId != null && !emailOrUserId.equals(_userIdAnon)) {
+ attemptAndProcessMerge(emailOrUserId, isEmail, merge, failureHandler, _userIdAnon);
+ }
+
+ if (replay && (_userId != null || _email != null)) {
+ anonymousUserManager.syncEventsAndUserUpdate();
+ }
+
+ if (!isAnon) {
+ _userIdAnon = null;
+ }
+
+ anonymousUserManager.clearVisitorEventsAndUserData();
+ }
+ }
+
+ private void attemptAndProcessMerge(@NonNull String destinationUser, boolean isEmail, boolean merge, IterableHelper.FailureHandler failureHandler, String anonymousUserId) {
+ anonymousUserMerge.tryMergeUser(apiClient, anonymousUserId, destinationUser, isEmail, merge, (mergeResult, error) -> {
+ if (!(Objects.equals(mergeResult, IterableConstants.MERGE_SUCCESSFUL) || Objects.equals(mergeResult, IterableConstants.MERGE_NOTREQUIRED))) {
+ if (failureHandler != null) {
+ failureHandler.onFailure(error, null);
+ }
+ }
+ });
}
public void setAuthToken(String authToken) {
@@ -1059,7 +1183,10 @@ public void track(@NonNull String eventName, int campaignId, int templateId) {
*/
public void track(@NonNull String eventName, int campaignId, int templateId, @Nullable JSONObject dataFields) {
IterableLogger.printInfo();
- if (!checkSDKInitialization()) {
+ if (!checkSDKInitialization() && _userIdAnon == null) {
+ if (sharedInstance.config.enableAnonActivation) {
+ anonymousUserManager.trackAnonEvent(eventName, dataFields);
+ }
return;
}
@@ -1071,7 +1198,10 @@ public void track(@NonNull String eventName, int campaignId, int templateId, @Nu
* @param items
*/
public void updateCart(@NonNull List items) {
- if (!checkSDKInitialization()) {
+ if (!checkSDKInitialization() && _userIdAnon == null) {
+ if (sharedInstance.config.enableAnonActivation) {
+ anonymousUserManager.trackAnonUpdateCart(items);
+ }
return;
}
@@ -1094,13 +1224,10 @@ public void trackPurchase(double total, @NonNull List items) {
* @param dataFields a `JSONObject` containing any additional information to save along with the event
*/
public void trackPurchase(double total, @NonNull List items, @Nullable JSONObject dataFields) {
- if (!checkSDKInitialization()) {
- return;
- }
-
- apiClient.trackPurchase(total, items, dataFields, null);
+ trackPurchase(total, items, dataFields, null);
}
+
/**
* Tracks a purchase.
* @param total total purchase amount
@@ -1109,7 +1236,10 @@ public void trackPurchase(double total, @NonNull List items, @Null
* @param attributionInfo a `JSONObject` containing information about what the purchase was attributed to
*/
public void trackPurchase(double total, @NonNull List items, @Nullable JSONObject dataFields, @Nullable IterableAttributionInfo attributionInfo) {
- if (!checkSDKInitialization()) {
+ if (!checkSDKInitialization() && _userIdAnon == null) {
+ if (sharedInstance.config.enableAnonActivation) {
+ anonymousUserManager.trackAnonPurchaseEvent(total, items, dataFields);
+ }
return;
}
@@ -1161,7 +1291,7 @@ public void onSuccess(@NonNull JSONObject data) {
}
storeAuthData();
- getAuthManager().requestNewAuthToken(false);
+ getAuthManager().requestNewAuthToken(false, null);
if (successHandler != null) {
successHandler.onSuccess(data);
@@ -1184,7 +1314,10 @@ public void updateUser(@NonNull JSONObject dataFields) {
* @param mergeNestedObjects
*/
public void updateUser(@NonNull JSONObject dataFields, Boolean mergeNestedObjects) {
- if (!checkSDKInitialization()) {
+ if (!checkSDKInitialization() && _userIdAnon == null) {
+ if (sharedInstance.config.enableAnonActivation) {
+ anonymousUserManager.trackAnonUpdateUser(dataFields);
+ }
return;
}
@@ -1439,5 +1572,26 @@ public void trackEmbeddedSession(@NonNull IterableEmbeddedSession session) {
apiClient.trackEmbeddedSession(session);
}
+
+ public void setVisitorUsageTracked(@NonNull Boolean isSetVisitorUsageTracked) {
+ SharedPreferences sharedPref = sharedInstance.getMainActivityContext().getSharedPreferences(IterableConstants.SHARED_PREFS_FILE, Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = sharedPref.edit();
+ editor.putString(IterableConstants.SHARED_PREFS_ANON_SESSIONS, "");
+ editor.putString(IterableConstants.SHARED_PREFS_EVENT_LIST_KEY, "");
+ editor.putString(IterableConstants.SHARED_PREFS_USER_UPDATE_OBJECT_KEY, "");
+ editor.putString(IterableConstants.SHARED_PREFS_CRITERIA, "");
+ editor.putBoolean(IterableConstants.SHARED_PREFS_VISITOR_USAGE_TRACKED, isSetVisitorUsageTracked);
+ editor.apply();
+
+ if (isSetVisitorUsageTracked && config.enableAnonActivation) {
+ anonymousUserManager.updateAnonSession();
+ anonymousUserManager.getCriteria();
+ }
+ }
+
+ public boolean getVisitorUsageTracked() {
+ SharedPreferences sharedPreferences = sharedInstance.getMainActivityContext().getSharedPreferences(IterableConstants.SHARED_PREFS_FILE, Context.MODE_PRIVATE);
+ return sharedPreferences.getBoolean(IterableConstants.SHARED_PREFS_VISITOR_USAGE_TRACKED, false);
+ }
//endregion
}
\ No newline at end of file
diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableApiClient.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableApiClient.java
index e805c89cc..dc86f6b84 100644
--- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableApiClient.java
+++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableApiClient.java
@@ -28,6 +28,8 @@ interface AuthProvider {
@Nullable
String getUserId();
@Nullable
+ String getUserIdAnon();
+ @Nullable
String getAuthToken();
@Nullable
String getApiKey();
@@ -35,7 +37,6 @@ interface AuthProvider {
String getDeviceId();
@Nullable
Context getContext();
-
void resetAuth();
}
@@ -97,6 +98,31 @@ public void track(@NonNull String eventName, int campaignId, int templateId, @Nu
}
}
+ public void track(@NonNull String eventName, int campaignId, int templateId, @Nullable JSONObject dataFields, String createdAt) {
+ JSONObject requestJSON = new JSONObject();
+ try {
+ addEmailOrUserIdToJson(requestJSON);
+ requestJSON.put(IterableConstants.KEY_EVENT_NAME, eventName);
+
+ if (campaignId != 0) {
+ requestJSON.put(IterableConstants.KEY_CAMPAIGN_ID, campaignId);
+ }
+ if (templateId != 0) {
+ requestJSON.put(IterableConstants.KEY_TEMPLATE_ID, templateId);
+ }
+ if (dataFields != null) {
+ dataFields.remove(IterableConstants.SHARED_PREFS_EVENT_TYPE);
+ dataFields.remove(IterableConstants.KEY_EVENT_NAME);
+ }
+ requestJSON.put(IterableConstants.KEY_CREATED_AT, createdAt);
+ requestJSON.put(IterableConstants.KEY_DATA_FIELDS, dataFields);
+ requestJSON.put(IterableConstants.KEY_CREATE_NEW_FIELDS, true);
+ sendPostRequest(IterableConstants.ENDPOINT_TRACK, requestJSON);
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ }
+
public void updateCart(@NonNull List items) {
JSONObject requestJSON = new JSONObject();
@@ -118,6 +144,28 @@ public void updateCart(@NonNull List items) {
}
}
+ public void updateCart(@NonNull List items, long createdAt) {
+ JSONObject requestJSON = new JSONObject();
+
+ try {
+ JSONArray itemsArray = new JSONArray();
+ for (CommerceItem item : items) {
+ itemsArray.put(item.toJSONObject());
+ }
+
+ JSONObject userObject = new JSONObject();
+ addEmailOrUserIdToJson(userObject);
+ userObject.put(IterableConstants.KEY_PREFER_USER_ID, true);
+ requestJSON.put(IterableConstants.KEY_USER, userObject);
+ requestJSON.put(IterableConstants.KEY_ITEMS, itemsArray);
+ requestJSON.put(IterableConstants.KEY_CREATED_AT, createdAt);
+
+ sendPostRequest(IterableConstants.ENDPOINT_UPDATE_CART, requestJSON);
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ }
+
public void trackPurchase(double total, @NonNull List items, @Nullable JSONObject dataFields, @Nullable IterableAttributionInfo attributionInfo) {
JSONObject requestJSON = new JSONObject();
try {
@@ -147,6 +195,33 @@ public void trackPurchase(double total, @NonNull List items, @Null
}
}
+ public void trackPurchase(double total, @NonNull List items, @Nullable JSONObject dataFields, @NonNull long createdAt) {
+ JSONObject requestJSON = new JSONObject();
+ try {
+ JSONArray itemsArray = new JSONArray();
+ for (CommerceItem item : items) {
+ itemsArray.put(item.toJSONObject());
+ }
+
+ JSONObject userObject = new JSONObject();
+ addEmailOrUserIdToJson(userObject);
+ userObject.put(IterableConstants.KEY_PREFER_USER_ID, true);
+ requestJSON.put(IterableConstants.KEY_USER, userObject);
+
+ requestJSON.put(IterableConstants.KEY_ITEMS, itemsArray);
+ requestJSON.put(IterableConstants.KEY_TOTAL, total);
+ requestJSON.put(IterableConstants.KEY_CREATED_AT, createdAt);
+ if (dataFields != null) {
+ dataFields.remove(IterableConstants.SHARED_PREFS_EVENT_TYPE);
+ requestJSON.put(IterableConstants.KEY_DATA_FIELDS, dataFields);
+ }
+
+ sendPostRequest(IterableConstants.ENDPOINT_TRACK_PURCHASE, requestJSON);
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ }
+
public void updateEmail(final @NonNull String newEmail, final @Nullable IterableHelper.SuccessHandler successHandler, @Nullable IterableHelper.FailureHandler failureHandler) {
JSONObject requestJSON = new JSONObject();
@@ -171,7 +246,7 @@ public void updateUser(@NonNull JSONObject dataFields, Boolean mergeNestedObject
addEmailOrUserIdToJson(requestJSON);
// Create the user by userId if it doesn't exist
- if (authProvider.getEmail() == null && authProvider.getUserId() != null) {
+ if (authProvider.getEmail() == null && (authProvider.getUserIdAnon() != null || authProvider.getUserId() != null)) {
requestJSON.put(IterableConstants.KEY_PREFER_USER_ID, true);
}
@@ -586,7 +661,11 @@ private void addEmailOrUserIdToJson(JSONObject requestJSON) {
if (authProvider.getEmail() != null) {
requestJSON.put(IterableConstants.KEY_EMAIL, authProvider.getEmail());
} else {
- requestJSON.put(IterableConstants.KEY_USER_ID, authProvider.getUserId());
+ if (authProvider.getUserIdAnon() != null) {
+ requestJSON.put(IterableConstants.KEY_USER_ID, authProvider.getUserIdAnon());
+ } else {
+ requestJSON.put(IterableConstants.KEY_USER_ID, authProvider.getUserId());
+ }
}
} catch (JSONException e) {
e.printStackTrace();
@@ -684,4 +763,51 @@ void onLogout() {
getRequestProcessor().onLogout(authProvider.getContext());
authProvider.resetAuth();
}
+
+ void mergeUser(String sourceEmail, String sourceUserId, String destinationEmail, String destinationUserId, @Nullable IterableHelper.SuccessHandler successHandler, @Nullable IterableHelper.FailureHandler failureHandler) {
+ JSONObject requestJson = new JSONObject();
+ try {
+ if (sourceEmail != null && !sourceEmail.isEmpty()) {
+ requestJson.put(IterableConstants.SOURCE_EMAIL, sourceEmail);
+ }
+ if (sourceUserId != null && !sourceUserId.isEmpty()) {
+ requestJson.put(IterableConstants.SOURCE_USER_ID, sourceUserId);
+ }
+ if (destinationEmail != null && !destinationEmail.isEmpty()) {
+ requestJson.put(IterableConstants.DESTINATION_EMAIL, destinationEmail);
+ }
+ if (destinationUserId != null && !destinationUserId.isEmpty()) {
+ requestJson.put(IterableConstants.DESTINATION_USER_ID, destinationUserId);
+ }
+ sendPostRequest(IterableConstants.ENDPOINT_MERGE_USER, requestJson, successHandler, failureHandler);
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ }
+
+ void getCriteriaList(@Nullable IterableHelper.IterableActionHandler actionHandler) {
+ sendGetRequest(IterableConstants.ENDPOINT_CRITERIA_LIST, new JSONObject(), actionHandler);
+ }
+
+ void trackAnonSession(long createdAt, String userId, @NonNull JSONObject requestJson, JSONObject updateUserTrack, @NonNull IterableHelper.SuccessHandler onSuccess, @NonNull IterableHelper.FailureHandler onFailure) {
+ try {
+ JSONObject requestObject = new JSONObject();
+
+ JSONObject userObject = new JSONObject();
+ userObject.put(IterableConstants.KEY_USER_ID, userId);
+ userObject.put(IterableConstants.KEY_PREFER_USER_ID, true);
+ userObject.put(IterableConstants.KEY_MERGE_NESTED_OBJECTS, true);
+ userObject.put(IterableConstants.KEY_CREATE_NEW_FIELDS, true);
+ if (updateUserTrack != null) {
+ userObject.put(IterableConstants.KEY_DATA_FIELDS, updateUserTrack);
+ }
+ requestObject.put(IterableConstants.KEY_USER, userObject);
+ requestObject.put(IterableConstants.KEY_CREATED_AT, createdAt);
+ requestObject.put(IterableConstants.KEY_DEVICE_INFO, getDeviceInfoJson());
+ requestObject.put(IterableConstants.KEY_ANON_SESSION_CONTEXT, requestJson);
+ sendPostRequest(IterableConstants.ENDPOINT_TRACK_ANON_SESSION, requestObject, onSuccess, onFailure);
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ }
}
diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableAuthManager.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableAuthManager.java
index 2005eb2f5..06382e76d 100644
--- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableAuthManager.java
+++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableAuthManager.java
@@ -39,8 +39,8 @@ public class IterableAuthManager {
this.expiringAuthTokenRefreshPeriod = expiringAuthTokenRefreshPeriod;
}
- public synchronized void requestNewAuthToken(boolean hasFailedPriorAuth) {
- requestNewAuthToken(hasFailedPriorAuth, null, true);
+ public synchronized void requestNewAuthToken(boolean hasFailedPriorAuth, IterableHelper.SuccessHandler successCallback) {
+ requestNewAuthToken(hasFailedPriorAuth, successCallback, true);
}
public void pauseAuthRetries(boolean pauseRetry) {
diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableConfig.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableConfig.java
index ebc640b9a..8a99e2a28 100644
--- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableConfig.java
+++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableConfig.java
@@ -60,6 +60,9 @@ public class IterableConfig {
*/
final IterableAuthHandler authHandler;
+
+ final IterableAnonUserHandler iterableAnonUserHandler;
+
/**
* Duration prior to an auth expiration that a new auth token should be requested.
*/
@@ -86,12 +89,27 @@ public class IterableConfig {
* By default, the SDK will save in-apps to disk.
*/
final boolean useInMemoryStorageForInApps;
+
+ final boolean encryptionEnforced;
+
+ final boolean enableAnonActivation;
+
+ final int eventThresholdLimit;
+
/**
* Allows for fetching embedded messages.
*/
final boolean enableEmbeddedMessaging;
/**
+
+ /**
+ * This controls whether the SDK should allow event replay from local storage to logged in profile
+ * and merging between the generated anonymous profile and the logged in profile by default.
+ */
+ final IterableIdentityResolution identityResolution;
+
+ /**
* Handler for decryption failures of PII information.
* Before calling this handler, the SDK will clear the PII information and create new encryption keys
*/
@@ -112,7 +130,12 @@ private IterableConfig(Builder builder) {
allowedProtocols = builder.allowedProtocols;
dataRegion = builder.dataRegion;
useInMemoryStorageForInApps = builder.useInMemoryStorageForInApps;
+ encryptionEnforced = builder.encryptionEnforced;
+ enableAnonActivation = builder.enableAnonActivation;
enableEmbeddedMessaging = builder.enableEmbeddedMessaging;
+ eventThresholdLimit = builder.eventThresholdLimit;
+ identityResolution = builder.identityResolution;
+ iterableAnonUserHandler = builder.iterableAnonUserHandler;
decryptionFailureHandler = builder.decryptionFailureHandler;
}
@@ -131,8 +154,21 @@ public static class Builder {
private String[] allowedProtocols = new String[0];
private IterableDataRegion dataRegion = IterableDataRegion.US;
private boolean useInMemoryStorageForInApps = false;
+ private IterableDecryptionFailureHandler decryptionFailureHandler;
+
+ private boolean encryptionEnforced = false;
+ private boolean enableAnonActivation = false;
private boolean enableEmbeddedMessaging = false;
- private IterableDecryptionFailureHandler decryptionFailureHandler;
+ private int eventThresholdLimit = 100;
+ private IterableIdentityResolution identityResolution = new IterableIdentityResolution();
+ private IterableAnonUserHandler iterableAnonUserHandler;
+ private IterableDecryptionFailureHandler decryptionFailureHandler;
+
+ @NonNull
+ public Builder setIterableAnonUserHandler(@NonNull IterableAnonUserHandler iterableAnonUserHandler) {
+ this.iterableAnonUserHandler = iterableAnonUserHandler;
+ return this;
+ }
public Builder() {}
@@ -285,6 +321,21 @@ public Builder setUseInMemoryStorageForInApps(boolean useInMemoryStorageForInApp
return this;
}
+ /**
+ * Set whether the SDK should track events for anonymous users. Set this to `true`
+ * if you want to track all events when users are not logged into the application.
+ * @param enableAnonActivation `true` will track events for anonymous users.
+ */
+ public Builder setEnableAnonActivation(boolean enableAnonActivation) {
+ this.enableAnonActivation = enableAnonActivation;
+ return this;
+ }
+
+ public Builder setEventThresholdLimit(int eventThresholdLimit) {
+ this.eventThresholdLimit = eventThresholdLimit;
+ return this;
+ }
+
/**
* Allows for fetching embedded messages.
* @param enableEmbeddedMessaging `true` will allow automatically fetching embedded messaging.
@@ -295,6 +346,19 @@ public Builder setEnableEmbeddedMessaging(boolean enableEmbeddedMessaging) {
}
/**
+
+ /**
+ * Set whether the SDK should replay events from local storage to the logged in profile
+ * and set whether the SDK should merge the generated anonymous profile and the logged in profile.
+ * This can be overwritten by a parameter passed into setEmail or setUserId.
+ * @param identityResolution
+ * @return
+ */
+
+ public Builder setIdentityResolution(IterableIdentityResolution identityResolution) {
+ this.identityResolution = identityResolution;
+
+ /**
* Set a handler for decryption failures that can be used to handle data recovery
* @param handler Decryption failure handler provided by the app
*/
diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableConstants.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableConstants.java
index 2ef39a467..6da931df4 100644
--- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableConstants.java
+++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableConstants.java
@@ -16,6 +16,7 @@ public final class IterableConstants {
//API Fields
public static final String HEADER_API_KEY = "Api-Key";
+ public static final String HTTP_STATUS_CODE = "httpStatusCode";
public static final String HEADER_SDK_PLATFORM = "SDK-Platform";
public static final String HEADER_SDK_VERSION = "SDK-Version";
public static final String HEADER_SDK_AUTHORIZATION = "Authorization";
@@ -56,6 +57,8 @@ public final class IterableConstants {
public static final String KEY_EMBEDDED_SESSION_ID = "id";
public static final String KEY_OFFLINE_MODE = "offlineMode";
public static final String KEY_FIRETV = "FireTV";
+ public static final String KEY_CREATE_NEW_FIELDS = "createNewFields";
+ public static final String KEY_ANON_SESSION_CONTEXT = "anonSessionContext";
//API Endpoint Key Constants
public static final String ENDPOINT_DISABLE_DEVICE = "users/disableDevice";
@@ -80,6 +83,11 @@ public final class IterableConstants {
public static final String ENDPOINT_TRACK_EMBEDDED_RECEIVED = "embedded-messaging/events/received";
public static final String ENDPOINT_TRACK_EMBEDDED_CLICK = "embedded-messaging/events/click";
public static final String ENDPOINT_TRACK_EMBEDDED_SESSION = "embedded-messaging/events/session";
+ public static final String ENDPOINT_GET_USER_BY_USERID = "users/byUserId";
+ public static final String ENDPOINT_GET_USER_BY_EMAIL = "users/getByEmail";
+ public static final String ENDPOINT_MERGE_USER = "users/merge";
+ public static final String ENDPOINT_CRITERIA_LIST = "anonymoususer/list";
+ public static final String ENDPOINT_TRACK_ANON_SESSION = "anonymoususer/events/session";
public static final String PUSH_APP_ID = "IterableAppId";
public static final String PUSH_GCM_PROJECT_NUMBER = "GCMProjectNumber";
@@ -107,6 +115,7 @@ public final class IterableConstants {
public static final String SHARED_PREFS_FILE = "com.iterable.iterableapi";
public static final String SHARED_PREFS_EMAIL_KEY = "itbl_email";
public static final String SHARED_PREFS_USERID_KEY = "itbl_userid";
+ public static final String SHARED_PREFS_USERIDANON_KEY = "itbl_userid_anon";
public static final String SHARED_PREFS_DEVICEID_KEY = "itbl_deviceid";
public static final String SHARED_PREFS_AUTH_TOKEN_KEY = "itbl_authtoken";
public static final String SHARED_PREFS_EXPIRATION_SUFFIX = "_expiration";
@@ -118,6 +127,17 @@ public final class IterableConstants {
public static final String SHARED_PREFS_FCM_MIGRATION_DONE_KEY = "itbl_fcm_migration_done";
public static final String SHARED_PREFS_SAVED_CONFIGURATION = "itbl_saved_configuration";
public static final String SHARED_PREFS_OFFLINE_MODE_KEY = "itbl_offline_mode";
+ public static final String SHARED_PREFS_EVENT_LIST_KEY = "itbl_event_list";
+ public static final String SHARED_PREFS_USER_UPDATE_OBJECT_KEY = "itbl_user_update_object";
+ public static final String SHARED_PREFS_ANON_SESSIONS = "itbl_anon_sessions";
+ public static final String SHARED_PREFS_SESSION_NO = "totalAnonSessionCount";
+ public static final String SHARED_PREFS_LAST_SESSION = "lastAnonSession";
+ public static final String SHARED_PREFS_FIRST_SESSION = "firstAnonSession";
+ public static final String SHARED_PREFS_EVENT_TYPE = "eventType";
+ public static final String SHARED_PREFS_CRITERIA = "criteria";
+ public static final String SHARED_PREFS_CRITERIA_ID = "matchedCriteriaId";
+ public static final String SHARED_PREFS_PUSH_OPT_IN = "mobilePushOptIn";
+ public static final String SHARED_PREFS_VISITOR_USAGE_TRACKED = "itbl_visitor_usage_track";
public static final String SHARED_PREFS_DEVICE_NOTIFICATIONS_ENABLED = "itbl_notifications_enabled";
//Action buttons
@@ -159,6 +179,7 @@ public final class IterableConstants {
public static final String REQUEST_CODE = "requestCode";
public static final String ACTION_IDENTIFIER = "actionIdentifier";
public static final String USER_INPUT = "userInput";
+ public static final String DATA_REPLACE = "dataReplace";
//Firebase
public static final String FIREBASE_SENDER_ID = "gcm_defaultSenderId";
@@ -298,4 +319,38 @@ public final class IterableConstants {
public static final String NO_MESSAGES_TITLE = "noMessagesTitle";
public static final String NO_MESSAGES_BODY = "noMessagesBody";
+
+ // Criteria constants
+ public static final String CRITERIA_SETS = "criteriaSets";
+ public static final String SEARCH_QUERIES = "searchQueries";
+ public static final String SEARCH_QUERY = "searchQuery";
+ public static final String CRITERIA_ID = "criteriaId";
+ public static final String COMBINATOR = "combinator";
+ public static final String SEARCH_COMBO = "searchCombo";
+ public static final String FIELD = "field";
+ public static final String VALUE = "value";
+ public static final String VALUES = "values";
+ public static final String DATA_TYPE = "dataType";
+ public static final String COMPARATOR_TYPE = "comparatorType";
+ public static final String UPDATECART_ITEM_PREFIX = "updateCart.updatedShoppingCartItems.";
+ public static final String PURCHASE_ITEM = "shoppingCartItems";
+ public static final String PURCHASE_ITEM_PREFIX = PURCHASE_ITEM + ".";
+ public static final String MIN_MATCH = "minMatch";
+
+
+ //Tracking types
+ public static final String TRACK_EVENT = "customEvent";
+ public static final String TRACK_PURCHASE = "purchase";
+ public static final String TRACK_UPDATE_CART = "cartUpdate";
+ public static final String UPDATE_CART = "updateCart";
+ public static final String TRACK_TOKEN_REGISTRATION = "tokenRegistration";
+ public static final String UPDATE_USER = "user";
+ public static final String SOURCE_EMAIL = "sourceEmail";
+ public static final String SOURCE_USER_ID = "sourceUserId";
+ public static final String DESTINATION_EMAIL = "destinationEmail";
+ public static final String DESTINATION_USER_ID = "destinationUserId";
+
+ // Merge user constants
+ public static final String MERGE_SUCCESSFUL = "merge_successful";
+ public static final String MERGE_NOTREQUIRED = "merge_notrequired";
}
\ No newline at end of file
diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableIdentityResolution.kt b/iterableapi/src/main/java/com/iterable/iterableapi/IterableIdentityResolution.kt
new file mode 100644
index 000000000..acf38d085
--- /dev/null
+++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableIdentityResolution.kt
@@ -0,0 +1,6 @@
+package com.iterable.iterableapi
+
+data class IterableIdentityResolution (
+ val replayOnVisitorToKnown: Boolean = true,
+ val mergeOnAnonymousToKnown: Boolean = true
+)
\ No newline at end of file
diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableRequestTask.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableRequestTask.java
index 9fecbc807..f052da780 100644
--- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableRequestTask.java
+++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableRequestTask.java
@@ -381,7 +381,13 @@ private void handleSuccessResponse(IterableApiResponse response) {
private void handleErrorResponse(IterableApiResponse response) {
if (iterableApiRequest.failureCallback != null) {
- iterableApiRequest.failureCallback.onFailure(response.errorMessage, response.responseJson);
+ JSONObject responseJson = response.responseJson;
+ if (responseJson != null) {
+ try {
+ responseJson.put(IterableConstants.HTTP_STATUS_CODE, response.responseCode);
+ } catch (JSONException e) {}
+ }
+ iterableApiRequest.failureCallback.onFailure(response.errorMessage, responseJson);
}
}
diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/MergeResultCallback.java b/iterableapi/src/main/java/com/iterable/iterableapi/MergeResultCallback.java
new file mode 100644
index 000000000..c3f1e731f
--- /dev/null
+++ b/iterableapi/src/main/java/com/iterable/iterableapi/MergeResultCallback.java
@@ -0,0 +1,5 @@
+package com.iterable.iterableapi;
+
+public interface MergeResultCallback {
+ void onResult(String mergeResult, String error);
+}
diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/OnlineRequestProcessor.java b/iterableapi/src/main/java/com/iterable/iterableapi/OnlineRequestProcessor.java
index 2766a4f97..f972ae740 100644
--- a/iterableapi/src/main/java/com/iterable/iterableapi/OnlineRequestProcessor.java
+++ b/iterableapi/src/main/java/com/iterable/iterableapi/OnlineRequestProcessor.java
@@ -39,7 +39,13 @@ public void onLogout(Context context) {
JSONObject addCreatedAtToJson(JSONObject jsonObject) {
try {
- jsonObject.put(IterableConstants.KEY_CREATED_AT, new Date().getTime() / 1000);
+ long createdAt;
+ if (jsonObject.has(IterableConstants.KEY_CREATED_AT)) {
+ createdAt = Long.parseLong(jsonObject.getString(IterableConstants.KEY_CREATED_AT));
+ } else {
+ createdAt = new Date().getTime() / 1000;
+ }
+ jsonObject.put(IterableConstants.KEY_CREATED_AT, createdAt);
} catch (JSONException e) {
IterableLogger.e(TAG, "Could not add createdAt timestamp to json object");
}
diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/ddl/Criteria.java b/iterableapi/src/main/java/com/iterable/iterableapi/ddl/Criteria.java
new file mode 100644
index 000000000..0ad9ffa5e
--- /dev/null
+++ b/iterableapi/src/main/java/com/iterable/iterableapi/ddl/Criteria.java
@@ -0,0 +1,55 @@
+package com.iterable.iterableapi.ddl;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class Criteria {
+ public final String criteriaId;
+ public final List criteriaList;
+
+ public Criteria(String criteriaId, List criteriaList) {
+ this.criteriaId = criteriaId;
+ this.criteriaList = criteriaList;
+ }
+
+ public static Criteria fromJSONObject(JSONObject json) throws JSONException {
+
+ List criteriaList = new ArrayList<>();
+ JSONArray jsonArray = json.getJSONArray("criteriaList");
+ for (int i = 0; i < jsonArray.length(); i++) {
+ criteriaList.add(CriteriaList.fromJSONObject(jsonArray.getJSONObject(i)));
+ }
+ return new Criteria(
+ json.getString("criteriaId"),
+ criteriaList
+ );
+ }
+
+ public static class CriteriaList {
+ public final String criteriaType;
+ public final String comparator;
+ public final String name;
+ public final int aggregateCount;
+
+ public CriteriaList(String criteriaType, String comparator, String name, int aggregateCount) {
+ this.criteriaType = criteriaType;
+ this.comparator = comparator;
+ this.name = name;
+ this.aggregateCount = aggregateCount;
+ }
+
+ static CriteriaList fromJSONObject(JSONObject json) throws JSONException {
+ return new CriteriaList(
+ json.getString("criteriaType"),
+ json.getString("comparator"),
+ json.getString("name"),
+ json.getInt("aggregateCount")
+ );
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/util/CriteriaCompletionChecker.java b/iterableapi/src/main/java/com/iterable/iterableapi/util/CriteriaCompletionChecker.java
new file mode 100644
index 000000000..49057a9a0
--- /dev/null
+++ b/iterableapi/src/main/java/com/iterable/iterableapi/util/CriteriaCompletionChecker.java
@@ -0,0 +1,824 @@
+package com.iterable.iterableapi.util;
+
+import androidx.annotation.NonNull;
+import com.iterable.iterableapi.IterableConstants;
+import com.iterable.iterableapi.IterableLogger;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+import java.util.Map;
+
+public class CriteriaCompletionChecker {
+
+ private JSONArray localStoredEventList;
+
+ public String getMatchedCriteria(String criteriaData, JSONArray localStoredEventList) {
+ this.localStoredEventList = localStoredEventList;
+ String criteriaId = null;
+
+ try {
+ JSONObject json = new JSONObject(criteriaData);
+ if (json.has(IterableConstants.CRITERIA_SETS)) {
+ JSONArray criteriaList = json.getJSONArray(IterableConstants.CRITERIA_SETS);
+ criteriaId = findMatchedCriteria(criteriaList);
+ }
+ } catch (JSONException e) {
+ handleJSONException(e);
+ }
+
+ return criteriaId;
+ }
+
+ private String findMatchedCriteria(JSONArray criteriaList) {
+ String criteriaId = null;
+ JSONArray eventsToProcess = prepareEventsToProcess();
+
+ for (int i = 0; i < criteriaList.length(); i++) {
+ try {
+ JSONObject criteria = criteriaList.getJSONObject(i);
+ if (criteria.has(IterableConstants.SEARCH_QUERY) && criteria.has(IterableConstants.CRITERIA_ID)) {
+ JSONObject searchQuery = criteria.getJSONObject(IterableConstants.SEARCH_QUERY);
+ String currentCriteriaId = criteria.getString(IterableConstants.CRITERIA_ID);
+ boolean result = evaluateTree(searchQuery, eventsToProcess);
+ if (result) {
+ criteriaId = currentCriteriaId;
+ break;
+ }
+ }
+ } catch (JSONException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ return criteriaId;
+ }
+
+ private JSONArray prepareEventsToProcess() {
+ JSONArray eventsToProcess = getEventsWithCartItems();
+ JSONArray nonPurchaseEvents = getNonCartEvents();
+
+ for (int i = 0; i < nonPurchaseEvents.length(); i++) {
+ try {
+ eventsToProcess.put(nonPurchaseEvents.getJSONObject(i));
+ } catch (JSONException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ return eventsToProcess;
+ }
+
+ private JSONArray getEventsWithCartItems() {
+ JSONArray processedEvents = new JSONArray();
+ try {
+ for (int i = 0; i < localStoredEventList.length(); i++) {
+ JSONObject localEventData = localStoredEventList.getJSONObject(i);
+ if (localEventData.has(IterableConstants.SHARED_PREFS_EVENT_TYPE) && (
+ localEventData.get(IterableConstants.SHARED_PREFS_EVENT_TYPE).equals(IterableConstants.TRACK_PURCHASE))) {
+ JSONObject updatedItem = new JSONObject();
+
+ if (localEventData.has(IterableConstants.KEY_ITEMS)) {
+ final JSONArray items = new JSONArray(localEventData.getString(IterableConstants.KEY_ITEMS));
+ final JSONArray processedItems = new JSONArray();
+ for (int j = 0; j < items.length(); j++) {
+ JSONObject processedItem = new JSONObject();
+ JSONObject item = items.getJSONObject(j);
+ Iterator keys = item.keys();
+ while (keys.hasNext()) {
+ String key = keys.next();
+ processedItem.put(IterableConstants.PURCHASE_ITEM_PREFIX + key, item.get(key));
+ }
+ processedItems.put(processedItem);
+ }
+ updatedItem.put(IterableConstants.PURCHASE_ITEM, processedItems);
+ }
+
+ if (localEventData.has(IterableConstants.KEY_DATA_FIELDS)) {
+ JSONObject dataFields = localEventData.getJSONObject(IterableConstants.KEY_DATA_FIELDS);
+ Iterator fieldKeys = dataFields.keys();
+ while (fieldKeys.hasNext()) {
+ String key = fieldKeys.next();
+ updatedItem.put(key, dataFields.get(key));
+ }
+ }
+
+ Iterator keys = localEventData.keys();
+ while (keys.hasNext()) {
+ String key = keys.next();
+ if (!key.equals(IterableConstants.KEY_ITEMS) && !key.equals(IterableConstants.KEY_DATA_FIELDS)) {
+ updatedItem.put(key, localEventData.get(key));
+ }
+ }
+ processedEvents.put(updatedItem);
+ } else if (localEventData.has(IterableConstants.SHARED_PREFS_EVENT_TYPE) && (
+ localEventData.get(IterableConstants.SHARED_PREFS_EVENT_TYPE).equals(IterableConstants.TRACK_UPDATE_CART))) {
+ JSONObject updatedItem = new JSONObject();
+ updatedItem.put(IterableConstants.KEY_EVENT_NAME, IterableConstants.UPDATE_CART);
+
+ if (localEventData.has(IterableConstants.KEY_ITEMS)) {
+ final JSONArray items = new JSONArray(localEventData.getString(IterableConstants.KEY_ITEMS));
+ final JSONArray processedItems = new JSONArray();
+ for (int j = 0; j < items.length(); j++) {
+ JSONObject processedItem = new JSONObject();
+ JSONObject item = items.getJSONObject(j);
+ Iterator keys = item.keys();
+ while (keys.hasNext()) {
+ String key = keys.next();
+ processedItem.put(IterableConstants.UPDATECART_ITEM_PREFIX + key, item.get(key));
+ }
+ processedItems.put(processedItem);
+ }
+ updatedItem.put(IterableConstants.KEY_ITEMS, processedItems);
+ }
+
+ if (localEventData.has(IterableConstants.KEY_DATA_FIELDS)) {
+ JSONObject dataFields = localEventData.getJSONObject(IterableConstants.KEY_DATA_FIELDS);
+ Iterator fieldKeys = dataFields.keys();
+ while (fieldKeys.hasNext()) {
+ String key = fieldKeys.next();
+ updatedItem.put(key, dataFields.get(key));
+ }
+ }
+
+ Iterator localEventDataKeys = localEventData.keys();
+ while (localEventDataKeys.hasNext()) {
+ String key = localEventDataKeys.next();
+ if (!key.equals(IterableConstants.KEY_ITEMS) && !key.equals(IterableConstants.KEY_DATA_FIELDS)) {
+ if (key.equals(IterableConstants.SHARED_PREFS_EVENT_TYPE)) {
+ updatedItem.put(key, IterableConstants.TRACK_EVENT);
+ } else {
+ updatedItem.put(key, localEventData.get(key));
+ }
+ }
+ }
+
+ processedEvents.put(updatedItem);
+ }
+ }
+ } catch (JSONException e) {
+ handleJSONException(e);
+ }
+ return processedEvents;
+ }
+
+ private JSONArray getNonCartEvents() {
+ JSONArray nonPurchaseEvents = new JSONArray();
+ try {
+ for (int i = 0; i < localStoredEventList.length(); i++) {
+ JSONObject localEventData = localStoredEventList.getJSONObject(i);
+ if (localEventData.has(IterableConstants.SHARED_PREFS_EVENT_TYPE)
+ && !localEventData.get(IterableConstants.SHARED_PREFS_EVENT_TYPE).equals(IterableConstants.TRACK_PURCHASE)
+ && !localEventData.get(IterableConstants.SHARED_PREFS_EVENT_TYPE).equals(IterableConstants.TRACK_UPDATE_CART)) {
+
+ JSONObject updatedItem = new JSONObject(localEventData.toString());
+ if (localEventData.has(IterableConstants.KEY_DATA_FIELDS)) {
+ JSONObject dataFields = localEventData.getJSONObject(IterableConstants.KEY_DATA_FIELDS);
+ Iterator fieldKeys = dataFields.keys();
+ while (fieldKeys.hasNext()) {
+ String key = fieldKeys.next();
+ updatedItem.put(key, dataFields.get(key));
+ }
+ }
+ nonPurchaseEvents.put(updatedItem);
+ }
+ }
+ } catch (JSONException e) {
+ handleJSONException(e);
+ }
+ return nonPurchaseEvents;
+ }
+
+ public boolean evaluateTree(JSONObject node, JSONArray localEventData) {
+ try {
+ if (node.has(IterableConstants.SEARCH_QUERIES)) {
+ String combinator = node.getString(IterableConstants.COMBINATOR);
+ JSONArray searchQueries = node.getJSONArray(IterableConstants.SEARCH_QUERIES);
+ if (combinator.equals("And")) {
+ for (int i = 0; i < searchQueries.length(); i++) {
+ if (!evaluateTree(searchQueries.getJSONObject(i), localEventData)) {
+ return false;
+ }
+ }
+ return true;
+ } else if (combinator.equals("Or")) {
+ for (int i = 0; i < searchQueries.length(); i++) {
+ if (evaluateTree(searchQueries.getJSONObject(i), localEventData)) {
+ return true;
+ }
+ }
+ return false;
+ } else if (combinator.equals("Not")) {
+ for (int i = 0; i < searchQueries.length(); i++) {
+ searchQueries.getJSONObject(i).put("isNot", true);
+ if (evaluateTree(searchQueries.getJSONObject(i), localEventData)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ } else if (node.has(IterableConstants.SEARCH_COMBO)) {
+ return evaluateSearchQueries(node, localEventData);
+ }
+ } catch (Exception e) {
+ handleException(e);
+ }
+ return false;
+ }
+
+ private boolean evaluateSearchQueries(JSONObject node, @NonNull JSONArray localEventData) throws JSONException {
+ for (int i = 0; i < localEventData.length(); i++) {
+ JSONObject eventData = localEventData.getJSONObject(i);
+ String trackingType = eventData.getString(IterableConstants.SHARED_PREFS_EVENT_TYPE);
+ String dataType = node.getString(IterableConstants.DATA_TYPE);
+ if (dataType.equals(trackingType)) {
+ JSONObject searchCombo = node.getJSONObject(IterableConstants.SEARCH_COMBO);
+ JSONArray searchQueries = searchCombo.getJSONArray(IterableConstants.SEARCH_QUERIES);
+ String combinator = searchCombo.getString(IterableConstants.COMBINATOR);
+ boolean isNot = node.has("isNot");
+
+ if (evaluateEvent(searchQueries, eventData, combinator)) {
+ if (node.has(IterableConstants.MIN_MATCH)) {
+ int minMatch = node.getInt(IterableConstants.MIN_MATCH) - 1;
+ node.put(IterableConstants.MIN_MATCH, minMatch);
+ if (minMatch > 0) {
+ continue;
+ }
+ }
+ if (isNot && !(i + 1 == localEventData.length())) {
+ continue;
+ }
+ return true;
+ } else if (isNot) {
+ return false;
+ }
+
+ }
+ }
+ return false;
+ }
+
+ private boolean evaluateEvent(JSONArray searchQueries, JSONObject eventData, String combinator) throws JSONException {
+ if (combinator.equals("And")) {
+ if (!evaluateFieldLogic(searchQueries, eventData)) {
+ return false;
+ }
+ return true;
+ } else if (combinator.equals("Or")) {
+ if (evaluateFieldLogic(searchQueries, eventData)) {
+ return true;
+ }
+ } else if (combinator.equals("Not")) {
+ return !evaluateFieldLogic(searchQueries, eventData);
+ }
+
+ return false;
+ }
+
+ private boolean evaluateFieldLogic(JSONArray searchQueries, JSONObject eventData) throws JSONException {
+ boolean itemMatchResult = false;
+ String itemKey = null;
+ if (eventData.has(IterableConstants.KEY_ITEMS)) {
+ itemKey = IterableConstants.KEY_ITEMS;
+ } else if (eventData.has(IterableConstants.PURCHASE_ITEM)) {
+ itemKey = IterableConstants.PURCHASE_ITEM;
+ }
+
+ if (itemKey != null) {
+ boolean result = false;
+ JSONArray items = new JSONArray(eventData.getString(itemKey));
+ for (int j = 0; j < items.length(); j++) {
+ JSONObject item = items.getJSONObject(j);
+ if (doesItemMatchQueries(searchQueries, item)) {
+ result = true;
+ break;
+ }
+ }
+ if (!result && doesItemCriteriaExists(searchQueries)) {
+ return false;
+ }
+ itemMatchResult = result;
+ }
+
+ ArrayList filteredDataKeys = new ArrayList<>();
+ Iterator localEventDataKeys = eventData.keys();
+ while (localEventDataKeys.hasNext()) {
+ String localEventDataKey = localEventDataKeys.next();
+ if (!localEventDataKey.equals(IterableConstants.KEY_ITEMS)) {
+ filteredDataKeys.add(localEventDataKey);
+ }
+ }
+
+ if (filteredDataKeys.size() == 0) {
+ return itemMatchResult;
+ }
+
+ JSONArray filteredSearchQueries = new JSONArray();
+ for (int i = 0; i < searchQueries.length(); i++) {
+ JSONObject searchQuery = searchQueries.getJSONObject(i);
+ String field = searchQuery.getString(IterableConstants.FIELD);
+ if (!field.startsWith(IterableConstants.PURCHASE_ITEM_PREFIX) && !field.startsWith(IterableConstants.UPDATECART_ITEM_PREFIX)) {
+ filteredSearchQueries.put(searchQuery);
+ }
+ }
+ if (filteredSearchQueries.length() == 0) {
+ return itemMatchResult;
+ }
+ boolean matchResult = false;
+ for (int k = 0; k < filteredSearchQueries.length(); k++) {
+ JSONObject searchQuery = filteredSearchQueries.getJSONObject(k);
+ String field = searchQuery.getString(IterableConstants.FIELD);
+ boolean isKeyExists = false;
+ if (searchQuery.getString(IterableConstants.DATA_TYPE).equals(IterableConstants.TRACK_EVENT) && searchQuery.getString("fieldType").equals("object") && searchQuery.getString(IterableConstants.COMPARATOR_TYPE).equals(MatchComparator.IS_SET)) {
+ final String eventName = eventData.getString(IterableConstants.KEY_EVENT_NAME);
+ if ((eventName.equals(IterableConstants.UPDATE_CART) && field.equals(eventName)) || field.equals(eventName)) {
+ matchResult = true;
+ continue;
+ }
+ } else {
+ for (String filteredDataKey : filteredDataKeys) {
+ if (field.equals(filteredDataKey)) {
+ isKeyExists = true;
+ }
+ }
+ }
+
+ // if field is a nested field
+ if (field.contains(".")) {
+ // separate the sub-fields into an array
+ String[] splitString = field.split("\\.");
+ // if event type is a custom event and event name equals the top-level sub-field
+ if ((eventData.has(IterableConstants.SHARED_PREFS_EVENT_TYPE) && eventData.get(IterableConstants.SHARED_PREFS_EVENT_TYPE).equals(IterableConstants.TRACK_EVENT))
+ && (eventData.has(IterableConstants.KEY_EVENT_NAME) && eventData.get(IterableConstants.KEY_EVENT_NAME).equals(splitString[0]))) {
+ // remove the event name from the separated sub-fields array
+ splitString = Arrays.copyOfRange(splitString, 1, splitString.length);
+ }
+
+ JSONObject fieldValue = eventData;
+ boolean isSubFieldArray = false;
+ boolean isSubMatch = false;
+
+ // loop through the separated fields array
+ for (String subField : splitString) {
+ // check if the current sub-field exists in the event data
+ if (fieldValue.has(subField)) {
+ // get the value of the current sub-field
+ Object subFieldValue = fieldValue.get(subField);
+ // check if the value is a JSONArray
+ if (subFieldValue instanceof JSONArray && ((JSONArray) subFieldValue).get(0) instanceof JSONObject) {
+ isSubFieldArray = true;
+ JSONArray subFieldValueArray = (JSONArray) subFieldValue;
+ // loop through the JSONArray
+ for (int i = 0; i < subFieldValueArray.length(); i++) {
+ // get the value of the current item in the JSONArray
+ Object item = subFieldValueArray.get(i);
+ JSONObject data = new JSONObject();
+
+ // loop through the separated fields array
+ // process array to allow individual items to be checked
+ for (int j = splitString.length - 1; j >= 0; j--) {
+ String split = splitString[j];
+ if (split.equals(subField)) {
+ data.put(split, item);
+ } else {
+ JSONObject temp = new JSONObject(data.toString());
+ data = new JSONObject();
+ data.put(split, temp);
+ }
+ }
+ // check if the current item matches the search queries
+ if (evaluateFieldLogic(searchQueries, mergeEventData(eventData, data))) {
+ // if item matches, set to true and break the loop
+ isSubMatch = true;
+ break;
+ }
+ }
+
+ } else if (subFieldValue instanceof JSONObject) {
+ // set field value to the JSONObject for next iteration
+ fieldValue = (JSONObject) subFieldValue;
+ }
+
+ // return result if sub-field is an array
+ if (isSubFieldArray) {
+ return isSubMatch;
+ }
+ }
+ }
+ Object valueFromObj = getFieldValue(eventData, field);
+ if (valueFromObj != null) {
+ matchResult = evaluateComparison(
+ searchQuery.getString(IterableConstants.COMPARATOR_TYPE),
+ valueFromObj,
+ searchQuery.has(IterableConstants.VALUES) ?
+ searchQuery.getJSONArray(IterableConstants.VALUES) :
+ searchQuery.getString(IterableConstants.VALUE)
+ );
+ if (matchResult) {
+ continue;
+ } else {
+ break;
+ }
+ }
+ } else if (isKeyExists) {
+ if (evaluateComparison(searchQuery.getString(IterableConstants.COMPARATOR_TYPE),
+ eventData.get(field),
+ searchQuery.has(IterableConstants.VALUES) ?
+ searchQuery.getJSONArray(IterableConstants.VALUES) :
+ searchQuery.getString(IterableConstants.VALUE))) {
+ matchResult = true;
+ continue;
+ }
+ }
+ matchResult = false;
+ break;
+ }
+ return matchResult;
+ }
+
+ private JSONObject mergeEventData(JSONObject eventData, JSONObject data) throws JSONException {
+ Iterator keys = data.keys();
+ while (keys.hasNext()) {
+ String key = keys.next();
+ eventData.put(key, data.get(key));
+ }
+ return eventData;
+ }
+
+ private Object getFieldValue(JSONObject data, String field) {
+ String[] fields = field.split("\\.");
+ try {
+ String eventType = data.getString(IterableConstants.SHARED_PREFS_EVENT_TYPE);
+
+ if (eventType.equals(IterableConstants.TRACK_EVENT)) {
+ String eventName = data.getString(IterableConstants.KEY_EVENT_NAME);
+ // if first field equals event name
+ if (fields[0].equals(eventName)) {
+ // remove the event name from the fields array
+ String[] newArray = new String[fields.length - 1];
+ System.arraycopy(fields, 1, newArray, 0, newArray.length);
+ fields = newArray;
+ }
+ }
+
+ JSONObject value = data;
+ Object fieldValue = null;
+
+ // loop through the fields array
+ for (String currentField : fields) {
+ // check if the current field exists event data
+ if (value.has(currentField)) {
+ // get the value of the current field
+ Object dataValue = value.get(currentField);
+ // check if the value is a JSONObject
+ if (dataValue instanceof JSONObject) {
+ // set the value to the JSONObject
+ value = value.getJSONObject(currentField);
+ } else {
+ // if the value is not a JSONObject, set the returned value to value of the current field
+ fieldValue = value.get(currentField);
+ }
+ } else {
+ break;
+ }
+ }
+ return fieldValue;
+ } catch (JSONException e) {
+ return null;
+ }
+ }
+
+ private boolean doesItemCriteriaExists(JSONArray searchQueries) throws JSONException {
+ for (int i = 0; i < searchQueries.length(); i++) {
+ String field = searchQueries.getJSONObject(i).getString(IterableConstants.FIELD);
+ if (field.startsWith(IterableConstants.UPDATECART_ITEM_PREFIX) || field.startsWith(IterableConstants.PURCHASE_ITEM_PREFIX)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ private boolean doesItemMatchQueries(JSONArray searchQueries, JSONObject item) throws JSONException {
+ JSONArray filterSearchQueries = new JSONArray();
+ for (int i = 0; i < searchQueries.length(); i++) {
+ JSONObject searchQuery = searchQueries.getJSONObject(i);
+ String field = searchQuery.getString(IterableConstants.FIELD);
+ if (field.startsWith(IterableConstants.UPDATECART_ITEM_PREFIX) || field.startsWith(IterableConstants.PURCHASE_ITEM_PREFIX)) {
+ if (!item.has(field)) {
+ return false;
+ }
+ filterSearchQueries.put(searchQuery);
+ }
+ }
+
+ if (filterSearchQueries.length() == 0) {
+ return false;
+ }
+
+ for (int j = 0; j < filterSearchQueries.length(); j++) {
+ JSONObject query = filterSearchQueries.getJSONObject(j);
+ String field = query.getString(IterableConstants.FIELD);
+ if (item.has(field)) {
+ if (!evaluateComparison(query.getString(IterableConstants.COMPARATOR_TYPE),
+ item.get(field),
+ query.has(IterableConstants.VALUES) ?
+ query.getJSONArray(IterableConstants.VALUES) :
+ query.getString(IterableConstants.VALUE))) {
+ return false;
+ }
+ }
+ }
+
+ if (filterSearchQueries.length() > 0) {
+ return true;
+ }
+
+ return false;
+ }
+
+ public static String formattedDoubleValue(double d) {
+ if (d == (long) d)
+ return String.format("%d", (long) d);
+ else
+ return String.format("%s", d);
+ }
+
+ //
+ // comparison functions
+ //
+
+ private boolean evaluateComparison(String comparatorType, Object matchObj, Object valueToCompare) throws JSONException {
+ if (valueToCompare == null && !comparatorType.equals(MatchComparator.IS_SET)) {
+ return false;
+ }
+
+ valueToCompare = formatValueToCompare(valueToCompare);
+
+ switch (comparatorType) {
+ case MatchComparator.EQUALS:
+ return compareValueEquality(matchObj, valueToCompare);
+ case MatchComparator.DOES_NOT_EQUALS:
+ return !compareValueEquality(matchObj, valueToCompare);
+ case MatchComparator.IS_SET:
+ return issetCheck(matchObj);
+ case MatchComparator.GREATER_THAN:
+ case MatchComparator.LESS_THAN:
+ case MatchComparator.GREATER_THAN_OR_EQUAL_TO:
+ case MatchComparator.LESS_THAN_OR_EQUAL_TO:
+ return compareNumeric(matchObj, valueToCompare, comparatorType);
+ case MatchComparator.CONTAINS:
+ return compareContains(matchObj, String.valueOf(valueToCompare));
+ case MatchComparator.STARTS_WITH:
+ return compareStartsWith(matchObj, String.valueOf(valueToCompare));
+ case MatchComparator.MATCHES_REGEX:
+ return compareWithRegex(matchObj, String.valueOf(valueToCompare));
+ default:
+ return false;
+ }
+ }
+
+ private Object formatValueToCompare(Object valueToCompare) {
+ if (valueToCompare instanceof String && isDouble((String) valueToCompare)) {
+ return formattedDoubleValue(Double.parseDouble((String) valueToCompare));
+ }
+ return valueToCompare;
+ }
+
+ private boolean compareNumeric(Object matchObj, Object valueToCompare, String comparatorType) throws JSONException {
+ String comparisonOperator = getComparisonOperator(comparatorType);
+ return compareNumericValues(matchObj, String.valueOf(valueToCompare), comparisonOperator);
+ }
+
+ private String getComparisonOperator(String comparatorType) {
+ switch (comparatorType) {
+ case MatchComparator.GREATER_THAN:
+ return " > ";
+ case MatchComparator.LESS_THAN:
+ return " < ";
+ case MatchComparator.GREATER_THAN_OR_EQUAL_TO:
+ return " >= ";
+ case MatchComparator.LESS_THAN_OR_EQUAL_TO:
+ return " <= ";
+ default:
+ throw new IllegalArgumentException("Invalid comparator type: " + comparatorType);
+ }
+ }
+
+ private boolean issetCheck(Object matchObj) {
+ if (matchObj instanceof Object[]) {
+ return ((Object[]) matchObj).length > 0;
+ } else if (matchObj instanceof Map) {
+ return !((Map, ?>) matchObj).isEmpty();
+ } else {
+ return matchObj != null && !matchObj.equals("");
+ }
+ }
+
+ private boolean compareValueEquality(Object sourceTo, Object stringValue) throws JSONException {
+ if (sourceTo instanceof JSONArray) {
+ return compareWithJSONArray((JSONArray) sourceTo, stringValue);
+ } else if (stringValue instanceof JSONArray) {
+ return compareWithJSONArray((JSONArray) stringValue, sourceTo);
+ } else if (sourceTo instanceof String || stringValue instanceof String) {
+ return compareWithParsedValues(sourceTo, stringValue);
+ } else {
+ return sourceTo.equals(stringValue);
+ }
+ }
+
+ private boolean compareWithJSONArray(JSONArray jsonArray, Object valueToCompare) throws JSONException {
+ for (int i = 0; i < jsonArray.length(); i++) {
+ if (compareValueEquality(jsonArray.get(i), valueToCompare)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean compareWithParsedValues(Object sourceTo, Object stringValue) {
+ String stringVal = stringValue.toString();
+
+ if (sourceTo instanceof Double && isDouble(stringVal)) {
+ return sourceTo.equals(Double.parseDouble(stringVal));
+ } else if (sourceTo instanceof Integer && isInteger(stringVal)) {
+ return sourceTo.equals(Integer.parseInt(stringVal));
+ } else if (sourceTo instanceof Long && isLong(stringVal)) {
+ return sourceTo.equals(Long.parseLong(stringVal));
+ } else if (sourceTo instanceof Boolean && isBoolean(stringVal)) {
+ return sourceTo.equals(Boolean.parseBoolean(stringVal));
+ } else {
+ return sourceTo.equals(stringValue);
+ }
+ }
+
+ private boolean compareArrayNumericValue(Object sourceTo, String stringValue, String compareOperator) throws JSONException {
+ JSONArray jsonArraySourceTo = (JSONArray) sourceTo;
+ boolean isMatched = false;
+ for (int i = 0; i < jsonArraySourceTo.length(); i++) {
+ if (compareNumericValues(jsonArraySourceTo.get(i), stringValue, compareOperator)) {
+ isMatched = true;
+ }
+ }
+ return isMatched;
+ }
+
+ private boolean compareNumericValues(Object sourceTo, String stringValue, String compareOperator) throws JSONException {
+ if (sourceTo instanceof JSONArray) {
+ return compareArrayNumericValue(sourceTo, stringValue, compareOperator);
+ } else if (isDouble(stringValue)) {
+ double sourceNumber = getDoubleValue(sourceTo);
+ double numericValue = Double.parseDouble(stringValue);
+ switch (compareOperator.trim()) {
+ case ">":
+ return sourceNumber > numericValue;
+ case "<":
+ return sourceNumber < numericValue;
+ case ">=":
+ return sourceNumber >= numericValue;
+ case "<=":
+ return sourceNumber <= numericValue;
+ default:
+ return false;
+ }
+ }
+ return false;
+ }
+
+ private boolean compareContains(Object sourceTo, String stringValue) throws JSONException {
+ if (sourceTo instanceof JSONArray) {
+ JSONArray jsonArraySourceTo = (JSONArray) sourceTo;
+
+ if (jsonArraySourceTo.get(0) instanceof String) {
+ // check if any string in the array contains the string
+ return arrayContains(jsonArraySourceTo, stringValue);
+ }
+ } else if (sourceTo instanceof String) {
+ return ((String) sourceTo).contains(stringValue);
+ }
+ return false;
+ }
+
+ private boolean arrayContains(JSONArray jsonArray, String stringValue) throws JSONException {
+ for (int i = 0; i < jsonArray.length(); i++) {
+ if (jsonArray.getString(i).contains(stringValue)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean compareStartsWith(Object sourceTo, String stringValue) throws JSONException {
+ if (sourceTo instanceof JSONArray) {
+ JSONArray jsonArraySourceTo = (JSONArray) sourceTo;
+
+ if (jsonArraySourceTo.get(0) instanceof String) {
+ // check if any string in the array starts with string
+ return anyStartsWith(jsonArraySourceTo, stringValue);
+ }
+ } else if (sourceTo instanceof String) {
+ return ((String) sourceTo).startsWith(stringValue);
+ }
+
+ return false;
+ }
+
+ private boolean anyStartsWith(JSONArray jsonArray, String stringValue) throws JSONException {
+ for (int i = 0; i < jsonArray.length(); i++) {
+ if (jsonArray.getString(i).startsWith(stringValue)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean compareWithRegex(Object sourceTo, String pattern) throws JSONException {
+ try {
+ Pattern regexPattern = Pattern.compile(pattern);
+
+ // If the source is a JSONArray
+ if (sourceTo instanceof JSONArray) {
+ JSONArray jsonArraySourceTo = (JSONArray) sourceTo;
+
+ if (jsonArraySourceTo.get(0) instanceof String) {
+ // check if any string in the array matches the regex
+ return anyMatchesRegex(jsonArraySourceTo, regexPattern);
+ }
+ } else if (sourceTo instanceof String) {
+ return regexPattern.matcher((String) sourceTo).matches();
+ }
+
+ } catch (PatternSyntaxException e) {
+ e.printStackTrace();
+ }
+
+ return false;
+ }
+
+ private boolean anyMatchesRegex(JSONArray jsonArray, Pattern regexPattern) throws JSONException {
+ for (int i = 0; i < jsonArray.length(); i++) {
+ if (regexPattern.matcher(jsonArray.getString(i)).matches()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private double getDoubleValue(Object value) {
+ if (value instanceof Double) {
+ return (Double) value;
+ } else if (value instanceof Integer) {
+ return (double) (Integer) value;
+ } else if (value instanceof Long) {
+ return (double) (Long) value;
+ } else if (value instanceof String && isDouble((String) value)) {
+ return Double.parseDouble((String) value);
+ }
+ return 0.0;
+ }
+
+ private boolean isDouble(String value) {
+ try {
+ Double.parseDouble(value);
+ return true;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+
+ private boolean isInteger(String value) {
+ try {
+ Integer.parseInt(value);
+ return true;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+
+ private boolean isLong(String value) {
+ try {
+ Long.parseLong(value);
+ return true;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+
+ private boolean isBoolean(String value) {
+ return "true".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value);
+ }
+
+ private void handleException(Exception e) {
+ IterableLogger.e("Exception occurred", e.toString());
+ e.printStackTrace();
+ }
+
+ private void handleJSONException(JSONException e) {
+ IterableLogger.e("JSONException occurred", e.toString());
+ e.printStackTrace();
+ }
+}
\ No newline at end of file
diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/util/IterableJwtGenerator.java b/iterableapi/src/main/java/com/iterable/iterableapi/util/IterableJwtGenerator.java
new file mode 100644
index 000000000..9eeb8128b
--- /dev/null
+++ b/iterableapi/src/main/java/com/iterable/iterableapi/util/IterableJwtGenerator.java
@@ -0,0 +1,88 @@
+package com.iterable.iterableapi.util;
+
+import android.os.Build;
+
+import androidx.annotation.RequiresApi;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.util.Base64;
+
+@RequiresApi(api = Build.VERSION_CODES.O)
+public class IterableJwtGenerator {
+
+ static Base64.Encoder encoder = Base64.getUrlEncoder().withoutPadding();
+ private static final String algorithm = "HmacSHA256";
+ // Iterable enforces a 1-year maximum token lifetime
+ private static final Duration maxTokenLifetime = Duration.ofDays(365);
+ private static long millisToSeconds(long millis) {
+ return millis / 1000;
+ }
+ private static final String encodedHeader = encoder.encodeToString(
+ "{\"alg\":\"HS256\",\"typ\":\"JWT\"}".getBytes(StandardCharsets.UTF_8)
+ );
+ /**
+ * Generates a JWT from the provided secret, header, and payload. Does not
+ * validate the header or payload.
+ *
+ * @param secret Your organization's shared secret with Iterable
+ * @param payload The JSON payload
+ *
+ * @return a signed JWT
+ */
+ public static String generateToken(String secret, String payload) {
+ try {
+ String encodedPayload = encoder.encodeToString(
+ payload.getBytes(StandardCharsets.UTF_8)
+ );
+ String encodedHeaderAndPayload = encodedHeader + "." + encodedPayload;
+ // HMAC setup
+ Mac hmac = Mac.getInstance(algorithm);
+ SecretKeySpec keySpec = new SecretKeySpec(
+ secret.getBytes(StandardCharsets.UTF_8), algorithm
+ );
+ hmac.init(keySpec);
+ String signature = encoder.encodeToString(
+ hmac.doFinal(
+ encodedHeaderAndPayload.getBytes(StandardCharsets.UTF_8)
+ )
+ );
+ return encodedHeaderAndPayload + "." + signature;
+ } catch (Exception e) {
+ throw new RuntimeException(e.getMessage());
+ }
+ }
+ /**
+ * Generates a JWT (issued now, expires after the provided duration).
+ *
+ * @param secret Your organization's shared secret with Iterable.
+ * @param duration The token's expiration time. Up to one year.
+ * @param email The email to included in the token, or null.
+ * @param userId The userId to include in the token, or null.
+ *
+ * @return A JWT string
+ */
+ public static String generateToken(
+ String secret, Duration duration, String email, String userId) {
+ if (duration.compareTo(maxTokenLifetime) > 0) {
+ throw new IllegalArgumentException("Duration must be one year or less.");
+ }
+ if ((userId != null && email != null) || (userId == null && email == null)) {
+ throw new IllegalArgumentException("The token must include a userId or email, but not both.");
+ }
+ long now = millisToSeconds(System.currentTimeMillis());
+ String payload;
+ if (userId != null) {
+ payload = String.format(
+ "{ \"userId\": \"%s\", \"iat\": %d, \"exp\": %d }",
+ userId, now, now + millisToSeconds(duration.toMillis()));
+ } else {
+ payload = String.format(
+ "{ \"email\": \"%s\", \"iat\": %d, \"exp\": %d }",
+ email, now, now + millisToSeconds(duration.toMillis()));
+ }
+ return generateToken(secret, payload);
+ }
+}
\ No newline at end of file
diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/util/MatchComparator.java b/iterableapi/src/main/java/com/iterable/iterableapi/util/MatchComparator.java
new file mode 100644
index 000000000..b05af7a4f
--- /dev/null
+++ b/iterableapi/src/main/java/com/iterable/iterableapi/util/MatchComparator.java
@@ -0,0 +1,14 @@
+package com.iterable.iterableapi.util;
+
+public class MatchComparator {
+ public static final String EQUALS = "Equals";
+ public static final String DOES_NOT_EQUALS = "DoesNotEqual";
+ public static final String IS_SET = "IsSet";
+ public static final String GREATER_THAN = "GreaterThan";
+ public static final String LESS_THAN = "LessThan";
+ public static final String GREATER_THAN_OR_EQUAL_TO = "GreaterThanOrEqualTo";
+ public static final String LESS_THAN_OR_EQUAL_TO = "LessThanOrEqualTo";
+ public static final String CONTAINS = "Contains";
+ public static final String STARTS_WITH = "StartsWith";
+ public static final String MATCHES_REGEX = "MatchesRegex";
+}
\ No newline at end of file
diff --git a/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiAuthJWTTests.java b/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiAuthJWTTests.java
new file mode 100644
index 000000000..8aef52aa1
--- /dev/null
+++ b/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiAuthJWTTests.java
@@ -0,0 +1,267 @@
+package com.iterable.iterableapi;
+
+import static android.os.Looper.getMainLooper;
+import static com.iterable.iterableapi.IterableConstants.HEADER_SDK_AUTH_FORMAT;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import com.iterable.iterableapi.unit.PathBasedQueueDispatcher;
+
+import org.json.JSONException;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import okhttp3.mockwebserver.MockResponse;
+import okhttp3.mockwebserver.MockWebServer;
+import okhttp3.mockwebserver.RecordedRequest;
+
+public class IterableApiAuthJWTTests extends BaseTest {
+
+ private MockWebServer server;
+ private IterableAuthHandler authHandler;
+ private PathBasedQueueDispatcher dispatcher;
+
+ private final String validJWT = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAiZW1haWwiOiAidGVzdEBleGFtcGxlLmNvbSIsICJpYXQiOiAxNzI5MjUyNDE3LCAiZXhwIjogMTcyOTg1NzIxNyB9.m-O6ksCv9OR-cF0RdiHB8VW_NwWJHVXChipbcFmIChg";
+ private final String newJWT = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE5MTYyMzkwMjJ9.dMD3MLuHTiO-Qy9PvOoMchNM4CzFIgI7jKVrRtlqlM0";
+
+ private final String criteriaMockData = "{\n" +
+ " \"count\":2,\n" +
+ " \"criteriaSets\":[\n" +
+ " {\n" +
+ " \"criteriaId\":43,\n" +
+ " \"searchQuery\":{\n" +
+ " \"combinator\":\"Or\",\n" +
+ " \"searchQueries\":[\n" +
+ " {\n" +
+ " \"combinator\":\"And\",\n" +
+ " \"searchQueries\":[\n" +
+ " {\n" +
+ " \"dataType\":\"purchase\",\n" +
+ " \"searchCombo\":{\n" +
+ " \"combinator\":\"Or\",\n" +
+ " \"searchQueries\":[\n" +
+ " {\n" +
+ " \"field\":\"shoppingCartItems.price\",\n" +
+ " \"fieldType\":\"double\",\n" +
+ " \"comparatorType\":\"Equals\",\n" +
+ " \"dataType\":\"purchase\",\n" +
+ " \"id\":2,\n" +
+ " \"value\":\"4.67\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"field\":\"shoppingCartItems.quantity\",\n" +
+ " \"fieldType\":\"long\",\n" +
+ " \"comparatorType\":\"GreaterThanOrEqualTo\",\n" +
+ " \"dataType\":\"purchase\",\n" +
+ " \"id\":3,\n" +
+ " \"valueLong\":2,\n" +
+ " \"value\":\"2\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"criteriaId\":5678,\n" +
+ " \"searchQuery\":{\n" +
+ " \"combinator\":\"Or\",\n" +
+ " \"searchQueries\":[\n" +
+ " {\n" +
+ " \"combinator\":\"Or\",\n" +
+ " \"searchQueries\":[\n" +
+ " {\n" +
+ " \"dataType\":\"user\",\n" +
+ " \"searchCombo\":{\n" +
+ " \"combinator\":\"And\",\n" +
+ " \"searchQueries\":[\n" +
+ " {\n" +
+ " \"field\":\"itblInternal.emailDomain\",\n" +
+ " \"fieldType\":\"string\",\n" +
+ " \"comparatorType\":\"Equals\",\n" +
+ " \"dataType\":\"user\",\n" +
+ " \"id\":6,\n" +
+ " \"value\":\"gmail.com\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\":\"customEvent\",\n" +
+ " \"searchCombo\":{\n" +
+ " \"combinator\":\"And\",\n" +
+ " \"searchQueries\":[\n" +
+ " {\n" +
+ " \"field\":\"eventName\",\n" +
+ " \"fieldType\":\"string\",\n" +
+ " \"comparatorType\":\"Equals\",\n" +
+ " \"dataType\":\"customEvent\",\n" +
+ " \"id\":9,\n" +
+ " \"value\":\"processing_cancelled\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"field\":\"createdAt\",\n" +
+ " \"fieldType\":\"date\",\n" +
+ " \"comparatorType\":\"GreaterThan\",\n" +
+ " \"dataType\":\"customEvent\",\n" +
+ " \"id\":10,\n" +
+ " \"dateRange\":{\n" +
+ " \n" +
+ " },\n" +
+ " \"isRelativeDate\":false,\n" +
+ " \"value\":\"1688194800000\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+ @Before
+ public void setUp() throws IOException {
+ server = new MockWebServer();
+ dispatcher = new PathBasedQueueDispatcher();
+ server.setDispatcher(dispatcher);
+ IterableApi.overrideURLEndpointPath(server.url("").toString());
+ reInitIterableApi();
+ }
+
+ @After
+ public void tearDown() throws IOException {
+ server.shutdown();
+ server = null;
+ }
+
+ private void reInitIterableApi() {
+ IterableApi.sharedInstance = new IterableApi();
+ authHandler = mock(IterableAuthHandler.class);
+ IterableTestUtils.createIterableApiNew(builder -> builder.setAuthHandler(authHandler), null);
+ IterableConfig iterableConfig = new IterableConfig.Builder().setEnableAnonActivation(true).setAuthHandler(authHandler).build();
+ IterableApi.initialize(getContext(), "fake_key", iterableConfig);
+ IterableApi.getInstance().setVisitorUsageTracked(true);
+ setCriteria(criteriaMockData);
+ }
+
+ private void setCriteria(String criteria) {
+ SharedPreferences sharedPref = getContext().getSharedPreferences(IterableConstants.SHARED_PREFS_FILE, Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = sharedPref.edit();
+ editor.putString(IterableConstants.SHARED_PREFS_CRITERIA, criteria);
+ editor.apply();
+ }
+
+ private void addResponse(String endPoint) {
+ dispatcher.enqueueResponse("/" + endPoint, new MockResponse().setResponseCode(200).setBody("{}"));
+ }
+
+ private String getEventData() {
+ SharedPreferences sharedPref = getContext().getSharedPreferences(IterableConstants.SHARED_PREFS_FILE, Context.MODE_PRIVATE);
+ return sharedPref.getString(IterableConstants.SHARED_PREFS_EVENT_LIST_KEY, "");
+ }
+
+ private void triggerTrackPurchaseEvent(String id, String name, double price, int quantity) throws JSONException {
+ List items = new ArrayList<>();
+ items.add(new CommerceItem(id, name, price, quantity));
+ IterableApi.getInstance().trackPurchase(4, items);
+ }
+
+ @Test
+ public void testCriteriaUserIdTokenCheckPass() throws Exception {
+ String userId = "testUserId";
+ IterableApi.getInstance().setUserId(userId);
+ assertEquals(userId, IterableApi.getInstance().getUserId());
+ assertNull(IterableApi.getInstance().getAuthToken());
+
+ while (server.takeRequest(1, TimeUnit.SECONDS) != null) {
+ }
+ final String userId1 = "testUser1";
+
+ doReturn(validJWT).when(authHandler).onAuthTokenRequested();
+ IterableApi.getInstance().setUserId(userId1);
+ RecordedRequest recordedRequest = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNotNull(recordedRequest);
+ shadowOf(getMainLooper()).idle();
+ assertEquals(userId1, IterableApi.getInstance().getUserId());
+ assertEquals(HEADER_SDK_AUTH_FORMAT + validJWT, recordedRequest.getHeader("Authorization"));
+
+ while (server.takeRequest(1, TimeUnit.SECONDS) != null) {
+ }
+ addResponse(IterableConstants.ENDPOINT_TRACK_ANON_SESSION);
+ addResponse(IterableConstants.ENDPOINT_MERGE_USER);
+ triggerTrackPurchaseEvent("test", "keyboard", 4.67, 3);
+ shadowOf(getMainLooper()).idle();
+ assertEquals("", getEventData());
+
+ while (server.takeRequest(1, TimeUnit.SECONDS) != null) {
+ }
+ final String userId2 = "testUser2";
+
+ doReturn(newJWT).when(authHandler).onAuthTokenRequested();
+ IterableApi.getInstance().setUserId(userId2);
+ RecordedRequest mergeRequest = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNotNull(mergeRequest);
+ shadowOf(getMainLooper()).idle();
+ assertEquals(userId2, IterableApi.getInstance().getUserId());
+ assertEquals(HEADER_SDK_AUTH_FORMAT + newJWT, mergeRequest.getHeader("Authorization"));
+ }
+
+ @Test
+ public void testCriteriaEmailIdTokenCheckPass() throws Exception {
+ String emailId = "testUserId@example.com";
+ IterableApi.getInstance().setEmail(emailId);
+ assertEquals(emailId, IterableApi.getInstance().getEmail());
+ assertNull(IterableApi.getInstance().getAuthToken());
+
+ while (server.takeRequest(1, TimeUnit.SECONDS) != null) {
+ }
+ final String emailId1 = "testUser1@example.com";
+
+ doReturn(validJWT).when(authHandler).onAuthTokenRequested();
+ IterableApi.getInstance().setEmail(emailId1);
+ RecordedRequest recordedRequest = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNotNull(recordedRequest);
+ shadowOf(getMainLooper()).idle();
+ assertEquals(emailId1, IterableApi.getInstance().getEmail());
+ assertEquals(HEADER_SDK_AUTH_FORMAT + validJWT, recordedRequest.getHeader("Authorization"));
+
+ while (server.takeRequest(1, TimeUnit.SECONDS) != null) {
+ }
+ addResponse(IterableConstants.ENDPOINT_TRACK_ANON_SESSION);
+ addResponse(IterableConstants.ENDPOINT_MERGE_USER);
+ triggerTrackPurchaseEvent("test", "keyboard", 4.67, 3);
+ shadowOf(getMainLooper()).idle();
+ assertEquals("", getEventData());
+
+ while (server.takeRequest(1, TimeUnit.SECONDS) != null) {
+ }
+ final String emailId2 = "testUser2@example.com";
+
+ doReturn(newJWT).when(authHandler).onAuthTokenRequested();
+ IterableApi.getInstance().setEmail(emailId2);
+ RecordedRequest mergeRequest = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNotNull(mergeRequest);
+ shadowOf(getMainLooper()).idle();
+ assertEquals(emailId2, IterableApi.getInstance().getEmail());
+ assertEquals(HEADER_SDK_AUTH_FORMAT + newJWT, mergeRequest.getHeader("Authorization"));
+ }
+}
diff --git a/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiCustomEventTests.java b/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiCustomEventTests.java
new file mode 100644
index 000000000..96bd21a4b
--- /dev/null
+++ b/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiCustomEventTests.java
@@ -0,0 +1,176 @@
+package com.iterable.iterableapi;
+
+import static android.os.Looper.getMainLooper;
+import static junit.framework.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import com.iterable.iterableapi.unit.PathBasedQueueDispatcher;
+
+import org.json.JSONObject;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+import okhttp3.mockwebserver.MockResponse;
+import okhttp3.mockwebserver.MockWebServer;
+import okhttp3.mockwebserver.RecordedRequest;
+
+public class IterableApiCustomEventTests extends BaseTest {
+ private MockWebServer server;
+ private PathBasedQueueDispatcher dispatcher;
+
+ @Before
+ public void setUp() {
+ server = new MockWebServer();
+ dispatcher = new PathBasedQueueDispatcher();
+ server.setDispatcher(dispatcher);
+ reInitIterableApi();
+ IterableApi.overrideURLEndpointPath(server.url("").toString());
+ IterableConfig iterableConfig = new IterableConfig.Builder().setEnableAnonActivation(true).build();
+ IterableApi.initialize(getContext(), "apiKey", iterableConfig);
+
+ String criteriaMockData = "{\n" +
+ " \"count\": 1,\n" +
+ " \"criteriaSets\": [\n" +
+ " {\n" +
+ " \"criteriaId\": \"423\",\n" +
+ " \"name\": \"animal-found Test Cases\",\n" +
+ " \"createdAt\": 1726648931809,\n" +
+ " \"updatedAt\": 1726648931809,\n" +
+ " \"searchQuery\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"searchCombo\": {\n" +
+ " \"combinator\": \"And\",\n" +
+ " \"searchQueries\": [\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"field\": \"animal-found.count\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": \"6\",\n" +
+ " \"fieldType\": \"long\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"field\": \"animal-found.type\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": \"cat\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"field\": \"animal-found.vaccinated\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": \"true\",\n" +
+ " \"fieldType\": \"boolean\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\": \"customEvent\",\n" +
+ " \"field\": \"eventName\",\n" +
+ " \"comparatorType\": \"Equals\",\n" +
+ " \"value\": \"animal-found\",\n" +
+ " \"fieldType\": \"string\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+ setCriteria(criteriaMockData);
+ }
+
+
+ private void reInitIterableApi() {
+ IterableApi.sharedInstance = new IterableApi();
+ }
+
+ @After
+ public void tearDown() throws IOException {
+ server.shutdown();
+ server = null;
+ clearEventData();
+ IterableApi.getInstance().setUserId(null);
+ IterableApi.getInstance().setEmail(null);
+ }
+
+ private void clearEventData() {
+ SharedPreferences sharedPref = getContext().getSharedPreferences(IterableConstants.SHARED_PREFS_FILE, Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = sharedPref.edit();
+ editor.putString(IterableConstants.SHARED_PREFS_EVENT_LIST_KEY, "");
+ editor.apply();
+ }
+ private void setCriteria(String criteria) {
+ SharedPreferences sharedPref = getContext().getSharedPreferences(IterableConstants.SHARED_PREFS_FILE, Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = sharedPref.edit();
+ editor.putString(IterableConstants.SHARED_PREFS_CRITERIA, criteria);
+ editor.apply();
+ }
+ private void addResponse(String endPoint) {
+ dispatcher.enqueueResponse("/" + endPoint, new MockResponse().setResponseCode(200).setBody("{}"));
+ }
+
+ @Test
+ public void testCustomEventTrackApi() throws Exception {
+ while (server.takeRequest(1, TimeUnit.SECONDS) != null) { }
+ addResponse(IterableConstants.ENDPOINT_TRACK_ANON_SESSION);
+ addResponse(IterableConstants.ENDPOINT_TRACK);
+ final String userId = "testUser2";
+
+ IterableIdentityResolution identityRes = new IterableIdentityResolution(true, false);
+ IterableApi.getInstance().setUserId(userId, identityRes);
+
+ while (server.takeRequest(1, TimeUnit.SECONDS) != null) { }
+ JSONObject customEventItem = new JSONObject("{\n" +
+ " \"dataFields\": " +
+ " {\n" +
+ " \"type\":\"cat\",\n" +
+ " \"count\":6,\n" +
+ " \"vaccinated\":true\n" +
+ " }\n" +
+ "}");
+
+ JSONObject items = new JSONObject(String.valueOf(customEventItem.getJSONObject(IterableConstants.KEY_DATA_FIELDS)));
+ IterableApi.getInstance().track("animal-found", items);
+ RecordedRequest mergeRequest = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNotNull(mergeRequest);
+ shadowOf(getMainLooper()).idle();
+ JSONObject requestJson = new JSONObject(mergeRequest.getBody().readUtf8());
+
+ assertEquals("/" + IterableConstants.ENDPOINT_TRACK, mergeRequest.getPath());
+ assertTrue("dataField should be set in the request", requestJson.has(IterableConstants.KEY_DATA_FIELDS));
+
+ JSONObject dataFieldObject = requestJson.getJSONObject(IterableConstants.KEY_DATA_FIELDS);
+
+ assertTrue(dataFieldObject.has("type"));
+ assertTrue(dataFieldObject.has("count"));
+ assertTrue(dataFieldObject.has("vaccinated"));
+
+ assertFalse(dataFieldObject.has("animal-found.type"));
+ assertFalse(dataFieldObject.has("animal-found.count"));
+ assertFalse(dataFieldObject.has("animal-found.vaccinated"));
+
+ assertEquals(dataFieldObject.getString("type"), "cat");
+ assertEquals(dataFieldObject.getInt("count"), 6);
+ assertTrue(dataFieldObject.getBoolean("vaccinated"));
+ }
+}
diff --git a/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiMergeUserEmailTests.java b/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiMergeUserEmailTests.java
new file mode 100644
index 000000000..b5d8839f4
--- /dev/null
+++ b/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiMergeUserEmailTests.java
@@ -0,0 +1,948 @@
+package com.iterable.iterableapi;
+
+import static android.os.Looper.getMainLooper;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import com.iterable.iterableapi.unit.PathBasedQueueDispatcher;
+
+import org.json.JSONException;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import okhttp3.mockwebserver.MockResponse;
+import okhttp3.mockwebserver.MockWebServer;
+import okhttp3.mockwebserver.RecordedRequest;
+
+public class IterableApiMergeUserEmailTests extends BaseTest {
+ private MockWebServer server;
+ private PathBasedQueueDispatcher dispatcher;
+ private final String criteriaMockData = "{\n" +
+ " \"count\":2,\n" +
+ " \"criteriaSets\":[\n" +
+ " {\n" +
+ " \"criteriaId\":43,\n" +
+ " \"searchQuery\":{\n" +
+ " \"combinator\":\"Or\",\n" +
+ " \"searchQueries\":[\n" +
+ " {\n" +
+ " \"combinator\":\"And\",\n" +
+ " \"searchQueries\":[\n" +
+ " {\n" +
+ " \"dataType\":\"purchase\",\n" +
+ " \"searchCombo\":{\n" +
+ " \"combinator\":\"Or\",\n" +
+ " \"searchQueries\":[\n" +
+ " {\n" +
+ " \"field\":\"shoppingCartItems.price\",\n" +
+ " \"fieldType\":\"double\",\n" +
+ " \"comparatorType\":\"Equals\",\n" +
+ " \"dataType\":\"purchase\",\n" +
+ " \"id\":2,\n" +
+ " \"value\":\"4.67\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"field\":\"shoppingCartItems.quantity\",\n" +
+ " \"fieldType\":\"long\",\n" +
+ " \"comparatorType\":\"GreaterThanOrEqualTo\",\n" +
+ " \"dataType\":\"purchase\",\n" +
+ " \"id\":3,\n" +
+ " \"valueLong\":2,\n" +
+ " \"value\":\"2\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"criteriaId\":5678,\n" +
+ " \"searchQuery\":{\n" +
+ " \"combinator\":\"Or\",\n" +
+ " \"searchQueries\":[\n" +
+ " {\n" +
+ " \"combinator\":\"Or\",\n" +
+ " \"searchQueries\":[\n" +
+ " {\n" +
+ " \"dataType\":\"user\",\n" +
+ " \"searchCombo\":{\n" +
+ " \"combinator\":\"And\",\n" +
+ " \"searchQueries\":[\n" +
+ " {\n" +
+ " \"field\":\"itblInternal.emailDomain\",\n" +
+ " \"fieldType\":\"string\",\n" +
+ " \"comparatorType\":\"Equals\",\n" +
+ " \"dataType\":\"user\",\n" +
+ " \"id\":6,\n" +
+ " \"value\":\"gmail.com\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " },\n" +
+ " {\n" +
+ " \"dataType\":\"customEvent\",\n" +
+ " \"searchCombo\":{\n" +
+ " \"combinator\":\"And\",\n" +
+ " \"searchQueries\":[\n" +
+ " {\n" +
+ " \"field\":\"eventName\",\n" +
+ " \"fieldType\":\"string\",\n" +
+ " \"comparatorType\":\"Equals\",\n" +
+ " \"dataType\":\"customEvent\",\n" +
+ " \"id\":9,\n" +
+ " \"value\":\"processing_cancelled\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"field\":\"createdAt\",\n" +
+ " \"fieldType\":\"date\",\n" +
+ " \"comparatorType\":\"GreaterThan\",\n" +
+ " \"dataType\":\"customEvent\",\n" +
+ " \"id\":10,\n" +
+ " \"dateRange\":{\n" +
+ " \n" +
+ " },\n" +
+ " \"isRelativeDate\":false,\n" +
+ " \"value\":\"1688194800000\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+ @Before
+ public void setUp() {
+ server = new MockWebServer();
+ dispatcher = new PathBasedQueueDispatcher();
+ server.setDispatcher(dispatcher);
+ reInitIterableApi();
+ IterableApi.overrideURLEndpointPath(server.url("").toString());
+ IterableConfig iterableConfig = new IterableConfig.Builder().setEnableAnonActivation(true).build();
+ IterableApi.initialize(getContext(), "apiKey", iterableConfig);
+ IterableApi.getInstance().setVisitorUsageTracked(true);
+ setCriteria(criteriaMockData);
+ }
+
+ private void reInitIterableApi() {
+ IterableApi.sharedInstance = new IterableApi();
+ }
+
+ @After
+ public void tearDown() throws IOException {
+ server.shutdown();
+ server = null;
+ clearEventData();
+ IterableApi.getInstance().setUserId(null);
+ IterableApi.getInstance().setEmail(null);
+ }
+
+ private String getEventData() {
+ SharedPreferences sharedPref = getContext().getSharedPreferences(IterableConstants.SHARED_PREFS_FILE, Context.MODE_PRIVATE);
+ return sharedPref.getString(IterableConstants.SHARED_PREFS_EVENT_LIST_KEY, "");
+ }
+ private void clearEventData() {
+ SharedPreferences sharedPref = getContext().getSharedPreferences(IterableConstants.SHARED_PREFS_FILE, Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = sharedPref.edit();
+ editor.putString(IterableConstants.SHARED_PREFS_EVENT_LIST_KEY, "");
+ editor.apply();
+ }
+ private void setCriteria(String criteria) {
+ SharedPreferences sharedPref = getContext().getSharedPreferences(IterableConstants.SHARED_PREFS_FILE, Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = sharedPref.edit();
+ editor.putString(IterableConstants.SHARED_PREFS_CRITERIA, criteria);
+ editor.apply();
+ }
+ private void triggerTrackPurchaseEvent(String id, String name, double price, int quantity) throws JSONException {
+ List items = new ArrayList<>();
+ items.add(new CommerceItem(id, name, price, quantity));
+ IterableApi.getInstance().trackPurchase(4, items);
+ }
+ private void addResponse(String endPoint) {
+ dispatcher.enqueueResponse("/" + endPoint, new MockResponse().setResponseCode(200).setBody("{}"));
+ }
+
+ // all userId tests
+ @Test
+ public void testCriteriaNotMetUserIdDefault() throws Exception {
+ // clear any pending requests
+ while (server.takeRequest(1, TimeUnit.SECONDS) != null) { }
+
+ // mock anon session response and track purchase response
+ addResponse(IterableConstants.ENDPOINT_TRACK_ANON_SESSION);
+ addResponse(IterableConstants.ENDPOINT_TRACK_PURCHASE);
+
+ // trigger track purchase event
+ triggerTrackPurchaseEvent("test", "keyboard", 5, 1);
+ shadowOf(getMainLooper()).idle();
+
+ // check that request was not sent to anon session endpoint
+ RecordedRequest anonSessionRequest = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNull("There should not be an anon session request", anonSessionRequest);
+
+ // check that request was not sent to track purchase endpoint
+ RecordedRequest purchaseRequest1 = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNull("There should not be a purchase request", purchaseRequest1);
+
+ // clear any pending requests
+ while (server.takeRequest(1, TimeUnit.SECONDS) != null) { }
+
+ // mock merge response
+ addResponse(IterableConstants.ENDPOINT_MERGE_USER);
+
+ // set user id
+ final String userId = "testUser2";
+ IterableApi.getInstance().setUserId(userId);
+
+ // check that request was sent to purchase endpoint on event replay
+ RecordedRequest purchaseRequest2 = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNotNull(purchaseRequest2);
+ assertEquals(("/" + IterableConstants.ENDPOINT_TRACK_PURCHASE), purchaseRequest2.getPath());
+
+ // check that request was not sent to merge endpoint
+ RecordedRequest mergeRequest = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNotNull(mergeRequest);
+ assertNotEquals(("/" + IterableConstants.ENDPOINT_MERGE_USER), mergeRequest.getPath());
+
+ // check that user id was set
+ assertEquals(userId, IterableApi.getInstance().getUserId());
+ }
+
+ @Test
+ public void testCriteriaNotMetUserIdReplayTrueMergeFalse() throws Exception {
+ // clear any pending requests
+ while (server.takeRequest(1, TimeUnit.SECONDS) != null) { }
+
+ // mock anon session response and track purchase response
+ addResponse(IterableConstants.ENDPOINT_TRACK_ANON_SESSION);
+ addResponse(IterableConstants.ENDPOINT_TRACK_PURCHASE);
+
+ // trigger track purchase event
+ triggerTrackPurchaseEvent("test", "keyboard", 5, 1);
+ shadowOf(getMainLooper()).idle();
+
+ // check that request was not sent to anon session endpoint
+ RecordedRequest anonSessionRequest = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNull("There should not be an anon session request", anonSessionRequest);
+
+ // check that request was not sent to track purchase endpoint
+ RecordedRequest purchaseRequest1 = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNull("There should not be a purchase request", purchaseRequest1);
+
+ // clear any pending requests
+ while (server.takeRequest(1, TimeUnit.SECONDS) != null) { }
+
+ // mock merge response
+ addResponse(IterableConstants.ENDPOINT_MERGE_USER);
+
+ // set user id
+ final String userId = "testUser2";
+ IterableIdentityResolution identityResolution = new IterableIdentityResolution(true, false);
+ IterableApi.getInstance().setUserId(userId, identityResolution);
+
+ // check that request was sent to purchase endpoint on event replay
+ RecordedRequest purchaseRequest2 = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNotNull(purchaseRequest2);
+ assertEquals(("/" + IterableConstants.ENDPOINT_TRACK_PURCHASE), purchaseRequest2.getPath());
+
+ // check that request was not sent to merge endpoint
+ RecordedRequest mergeRequest = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNotNull(mergeRequest);
+ assertNotEquals(("/" + IterableConstants.ENDPOINT_MERGE_USER), mergeRequest.getPath());
+
+ // check that user id was set
+ assertEquals(userId, IterableApi.getInstance().getUserId());
+ }
+
+ @Test
+ public void testCriteriaNotMetUserIdReplayFalseMergeFalse() throws Exception {
+ // clear any pending requests
+ while (server.takeRequest(1, TimeUnit.SECONDS) != null) { }
+
+ // mock anon session response and track purchase response
+ addResponse(IterableConstants.ENDPOINT_TRACK_ANON_SESSION);
+ addResponse(IterableConstants.ENDPOINT_TRACK_PURCHASE);
+
+ // trigger track purchase event
+ triggerTrackPurchaseEvent("test", "keyboard", 5, 1);
+ shadowOf(getMainLooper()).idle();
+
+ // check that request was not sent to anon session endpoint
+ RecordedRequest anonSessionRequest = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNull("There should not be an anon session request", anonSessionRequest);
+
+ // check that request was not sent to track purchase endpoint
+ RecordedRequest purchaseRequest1 = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNull("There should not be a purchase request", purchaseRequest1);
+
+ // clear any pending requests
+ while (server.takeRequest(1, TimeUnit.SECONDS) != null) { }
+
+ // mock merge response
+ addResponse(IterableConstants.ENDPOINT_MERGE_USER);
+
+ // set user id
+ final String userId = "testUser2";
+ IterableIdentityResolution identityResolution = new IterableIdentityResolution(false, false);
+ IterableApi.getInstance().setUserId(userId, identityResolution);
+
+ // check that request was not sent to merge endpoint
+ RecordedRequest mergeRequest = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNotNull(mergeRequest);
+ assertNotEquals(("/" + IterableConstants.ENDPOINT_MERGE_USER), mergeRequest.getPath());
+
+ // check that user id was set
+ assertEquals(userId, IterableApi.getInstance().getUserId());
+ }
+
+ @Test
+ public void testCriteriaNotMetUserIdReplayFalseMergeTrue() throws Exception {
+ // clear any pending requests
+ while (server.takeRequest(1, TimeUnit.SECONDS) != null) { }
+
+ // mock anon session response and track purchase response
+ addResponse(IterableConstants.ENDPOINT_TRACK_ANON_SESSION);
+ addResponse(IterableConstants.ENDPOINT_TRACK_PURCHASE);
+
+ // trigger track purchase event
+ triggerTrackPurchaseEvent("test", "keyboard", 5, 1);
+ shadowOf(getMainLooper()).idle();
+
+ // check that request was not sent to anon session endpoint
+ RecordedRequest anonSessionRequest = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNull("There should not be an anon session request", anonSessionRequest);
+
+ // check that request was not sent to track purchase endpoint
+ RecordedRequest purchaseRequest1 = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNull("There should not be a purchase request", purchaseRequest1);
+
+ // clear any pending requests
+ while (server.takeRequest(1, TimeUnit.SECONDS) != null) { }
+
+ // mock merge response
+ addResponse(IterableConstants.ENDPOINT_MERGE_USER);
+
+ // set user id
+ final String userId = "testUser2";
+ IterableIdentityResolution identityResolution = new IterableIdentityResolution(false, true);
+ IterableApi.getInstance().setUserId(userId, identityResolution);
+
+ // check that request was not sent to merge endpoint
+ RecordedRequest mergeRequest = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNotNull(mergeRequest);
+ assertNotEquals(("/" + IterableConstants.ENDPOINT_MERGE_USER), mergeRequest.getPath());
+
+ // check that user id was set
+ assertEquals(userId, IterableApi.getInstance().getUserId());
+ }
+
+ @Test
+ public void testCriteriaMetUserIdDefault() throws Exception {
+ // clear any pending requests
+ while (server.takeRequest(1, TimeUnit.SECONDS) != null) { }
+
+ // mock anon session response and track purchase response
+ addResponse(IterableConstants.ENDPOINT_TRACK_ANON_SESSION);
+ addResponse(IterableConstants.ENDPOINT_TRACK_PURCHASE);
+
+ // trigger track purchase event
+ triggerTrackPurchaseEvent("test", "keyboard", 4.67, 3);
+ shadowOf(getMainLooper()).idle();
+
+ // check if request was sent to anon session endpoint
+ RecordedRequest anonSessionRequest = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNotNull("Anon session request should not be null", anonSessionRequest);
+ assertEquals("/" + IterableConstants.ENDPOINT_TRACK_ANON_SESSION, anonSessionRequest.getPath());
+
+ // check if request was sent to track purchase endpoint
+ RecordedRequest purchaseRequest = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNotNull("Purchase request should not be null", purchaseRequest);
+ assertEquals("/" + IterableConstants.ENDPOINT_TRACK_PURCHASE, purchaseRequest.getPath());
+
+ // clear any pending requests
+ while (server.takeRequest(1, TimeUnit.SECONDS) != null) { }
+
+ // mock merge response
+ addResponse(IterableConstants.ENDPOINT_MERGE_USER);
+
+ // set user id
+ final String userId = "testUser2";
+ IterableApi.getInstance().setUserId(userId);
+
+ // check that request was sent to merge endpoint
+ RecordedRequest mergeRequest = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNotNull(mergeRequest);
+ assertEquals("/" + IterableConstants.ENDPOINT_MERGE_USER, mergeRequest.getPath());
+
+ // check that user id was set
+ assertEquals(userId, IterableApi.getInstance().getUserId());
+ }
+
+ @Test
+ public void testCriteriaMetUserIdMergeFalse() throws Exception {
+ // clear any pending requests
+ while (server.takeRequest(1, TimeUnit.SECONDS) != null) { }
+
+ // mock anon session response and track purchase response
+ addResponse(IterableConstants.ENDPOINT_TRACK_ANON_SESSION);
+ addResponse(IterableConstants.ENDPOINT_TRACK_PURCHASE);
+
+ // trigger track purchase event
+ triggerTrackPurchaseEvent("test", "keyboard", 4.67, 3);
+ shadowOf(getMainLooper()).idle();
+
+ // check if request was sent to anon session endpoint
+ RecordedRequest anonSessionRequest = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNotNull("Anon session request should not be null", anonSessionRequest);
+ assertEquals("/" + IterableConstants.ENDPOINT_TRACK_ANON_SESSION, anonSessionRequest.getPath());
+
+ // check if request was sent to track purchase endpoint
+ RecordedRequest purchaseRequest = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNotNull("Purchase request should not be null", purchaseRequest);
+ assertEquals("/" + IterableConstants.ENDPOINT_TRACK_PURCHASE, purchaseRequest.getPath());
+
+ // clear any pending requests
+ while (server.takeRequest(1, TimeUnit.SECONDS) != null) { }
+
+ // set user id
+ final String userId = "testUser2";
+ IterableIdentityResolution identityResolution = new IterableIdentityResolution(true, false);
+ IterableApi.getInstance().setUserId(userId, identityResolution);
+
+ // check that request was not sent to merge endpoint
+ RecordedRequest mergeRequest = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNotNull(mergeRequest);
+ assertNotEquals("/" + IterableConstants.ENDPOINT_MERGE_USER, mergeRequest.getPath());
+
+ // check that user id was set
+ assertEquals(userId, IterableApi.getInstance().getUserId());
+ }
+
+ @Test
+ public void testCriteriaMetUserIdMergeTrue() throws Exception {
+ // clear any pending requests
+ while (server.takeRequest(1, TimeUnit.SECONDS) != null) { }
+
+ // mock anon session response and track purchase response
+ addResponse(IterableConstants.ENDPOINT_TRACK_ANON_SESSION);
+ addResponse(IterableConstants.ENDPOINT_TRACK_PURCHASE);
+
+ // trigger track purchase event
+ triggerTrackPurchaseEvent("test", "keyboard", 4.67, 3);
+ shadowOf(getMainLooper()).idle();
+
+ // check if request was sent to anon session endpoint
+ RecordedRequest anonSessionRequest = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNotNull("Anon session request should not be null", anonSessionRequest);
+ assertEquals("/" + IterableConstants.ENDPOINT_TRACK_ANON_SESSION, anonSessionRequest.getPath());
+
+ // check if request was sent to track purchase endpoint
+ RecordedRequest purchaseRequest = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNotNull("Purchase request should not be null", purchaseRequest);
+ assertEquals("/" + IterableConstants.ENDPOINT_TRACK_PURCHASE, purchaseRequest.getPath());
+
+ // clear any pending requests
+ while (server.takeRequest(1, TimeUnit.SECONDS) != null) { }
+
+ // set user id
+ final String userId = "testUser2";
+ IterableIdentityResolution identityResolution = new IterableIdentityResolution(true, true);
+ IterableApi.getInstance().setUserId(userId, identityResolution);
+
+ // check that request was sent to merge endpoint
+ RecordedRequest mergeRequest = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNotNull(mergeRequest);
+ assertEquals("/" + IterableConstants.ENDPOINT_MERGE_USER, mergeRequest.getPath());
+
+ // check that user id was set
+ assertEquals(userId, IterableApi.getInstance().getUserId());
+ }
+
+ @Test
+ public void testIdentifiedUserIdDefault() throws Exception {
+ addResponse(IterableConstants.ENDPOINT_MERGE_USER);
+
+ // set user id
+ final String userId1 = "testUser1";
+ IterableApi.getInstance().setUserId(userId1);
+
+ // check that user id was set
+ assertEquals(userId1, IterableApi.getInstance().getUserId());
+
+ // clear any pending requests
+ while (server.takeRequest(1, TimeUnit.SECONDS) != null) { }
+
+ // set different user id
+ final String userId2 = "testUser2";
+ IterableApi.getInstance().setUserId(userId2);
+
+ // check that request was not sent to merge endpoint
+ RecordedRequest mergeRequest = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNotNull(mergeRequest);
+ assertNotEquals(("/" + IterableConstants.ENDPOINT_MERGE_USER), mergeRequest.getPath());
+
+ // check that user id was set
+ assertEquals(userId2, IterableApi.getInstance().getUserId());
+ }
+
+ @Test
+ public void testIdentifiedUserIdMergeFalse() throws Exception {
+ addResponse(IterableConstants.ENDPOINT_MERGE_USER);
+
+ // set user id
+ final String userId1 = "testUser1";
+ IterableIdentityResolution identityResolution = new IterableIdentityResolution(true, false);
+ IterableApi.getInstance().setUserId(userId1, identityResolution);
+
+ // check that user id was set
+ assertEquals(userId1, IterableApi.getInstance().getUserId());
+
+ // clear any pending requests
+ while (server.takeRequest(1, TimeUnit.SECONDS) != null) { }
+
+ // set different user id
+ final String userId2 = "testUser2";
+ IterableIdentityResolution identityResolution2 = new IterableIdentityResolution(true, false);
+ IterableApi.getInstance().setUserId(userId2, identityResolution2);
+
+ // check that request was not sent to merge endpoint
+ RecordedRequest mergeRequest = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNotNull(mergeRequest);
+ assertNotEquals(("/" + IterableConstants.ENDPOINT_MERGE_USER), mergeRequest.getPath());
+
+ // check that user id was set
+ assertEquals(userId2, IterableApi.getInstance().getUserId());
+ }
+
+ @Test
+ public void testIdentifiedUserIdMergeTrue() throws Exception {
+ addResponse(IterableConstants.ENDPOINT_MERGE_USER);
+
+ // set user id
+ final String userId1 = "testUser1";
+ IterableIdentityResolution identityResolution = new IterableIdentityResolution(true, true);
+ IterableApi.getInstance().setUserId(userId1, identityResolution);
+
+ // check that user id was set
+ assertEquals(userId1, IterableApi.getInstance().getUserId());
+
+ // clear any pending requests
+ while (server.takeRequest(1, TimeUnit.SECONDS) != null) { }
+
+ // set different user id
+ final String userId2 = "testUser2";
+ IterableIdentityResolution identityResolution2 = new IterableIdentityResolution(true, true);
+ IterableApi.getInstance().setUserId(userId2, identityResolution2);
+
+ // check that request was not sent to merge endpoint
+ RecordedRequest mergeRequest = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNotNull(mergeRequest);
+ assertNotEquals("/" + IterableConstants.ENDPOINT_MERGE_USER, mergeRequest.getPath());
+
+ // check that user id was set
+ assertEquals(userId2, IterableApi.getInstance().getUserId());
+ }
+
+ // all email tests
+ @Test
+ public void testCriteriaNotMetEmailDefault() throws Exception {
+ // clear any pending requests
+ while (server.takeRequest(1, TimeUnit.SECONDS) != null) { }
+
+ // mock anon session response and track purchase response
+ addResponse(IterableConstants.ENDPOINT_TRACK_ANON_SESSION);
+ addResponse(IterableConstants.ENDPOINT_TRACK_PURCHASE);
+
+ // trigger track purchase event
+ triggerTrackPurchaseEvent("test", "keyboard", 5, 1);
+ shadowOf(getMainLooper()).idle();
+
+ // check that request was not sent to anon session endpoint
+ RecordedRequest anonSessionRequest = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNull("There should not be an anon session request", anonSessionRequest);
+
+ // check that request was not sent to track purchase endpoint
+ RecordedRequest purchaseRequest1 = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNull("There should not be a purchase request", purchaseRequest1);
+
+ // clear any pending requests
+ while (server.takeRequest(1, TimeUnit.SECONDS) != null) { }
+
+ // mock merge response
+ addResponse(IterableConstants.ENDPOINT_MERGE_USER);
+
+ // set email
+ final String email = "testUser2@gmail.com";
+ IterableApi.getInstance().setEmail(email);
+
+ // check that request was sent to purchase endpoint on event replay
+ RecordedRequest purchaseRequest2 = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNotNull(purchaseRequest2);
+ assertEquals(("/" + IterableConstants.ENDPOINT_TRACK_PURCHASE), purchaseRequest2.getPath());
+
+ // check that request was not sent to merge endpoint
+ RecordedRequest mergeRequest = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNotNull(mergeRequest);
+ assertNotEquals(("/" + IterableConstants.ENDPOINT_MERGE_USER), mergeRequest.getPath());
+
+ // check that email was set
+ assertEquals(email, IterableApi.getInstance().getEmail());
+ }
+
+ @Test
+ public void testCriteriaNotMetEmailReplayTrueMergeFalse() throws Exception {
+ // clear any pending requests
+ while (server.takeRequest(1, TimeUnit.SECONDS) != null) { }
+
+ // mock anon session response and track purchase response
+ addResponse(IterableConstants.ENDPOINT_TRACK_ANON_SESSION);
+ addResponse(IterableConstants.ENDPOINT_TRACK_PURCHASE);
+
+ // trigger track purchase event
+ triggerTrackPurchaseEvent("test", "keyboard", 5, 1);
+ shadowOf(getMainLooper()).idle();
+
+ // check that request was not sent to anon session endpoint
+ RecordedRequest anonSessionRequest = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNull("There should not be an anon session request", anonSessionRequest);
+
+ // check that request was not sent to track purchase endpoint
+ RecordedRequest purchaseRequest1 = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNull("There should not be a purchase request", purchaseRequest1);
+
+ // clear any pending requests
+ while (server.takeRequest(1, TimeUnit.SECONDS) != null) { }
+
+ // mock merge response
+ addResponse(IterableConstants.ENDPOINT_MERGE_USER);
+
+ // set email
+ final String email = "testUser@gmail.com";
+ IterableIdentityResolution identityResolution = new IterableIdentityResolution(true, false);
+ IterableApi.getInstance().setEmail(email, identityResolution);
+
+ // check that request was sent to purchase endpoint on event replay
+ RecordedRequest purchaseRequest2 = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNotNull(purchaseRequest2);
+ assertEquals(("/" + IterableConstants.ENDPOINT_TRACK_PURCHASE), purchaseRequest2.getPath());
+
+ // check that request was not sent to merge endpoint
+ RecordedRequest mergeRequest = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNotNull(mergeRequest);
+ assertNotEquals(("/" + IterableConstants.ENDPOINT_MERGE_USER), mergeRequest.getPath());
+
+ // check that email was set
+ assertEquals(email, IterableApi.getInstance().getEmail());
+ }
+
+ @Test
+ public void testCriteriaNotMetEmailReplayFalseMergeFalse() throws Exception {
+ // clear any pending requests
+ while (server.takeRequest(1, TimeUnit.SECONDS) != null) { }
+
+ // mock anon session response and track purchase response
+ addResponse(IterableConstants.ENDPOINT_TRACK_ANON_SESSION);
+ addResponse(IterableConstants.ENDPOINT_TRACK_PURCHASE);
+
+ // trigger track purchase event
+ triggerTrackPurchaseEvent("test", "keyboard", 5, 1);
+ shadowOf(getMainLooper()).idle();
+
+ // check that request was not sent to anon session endpoint
+ RecordedRequest anonSessionRequest = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNull("There should not be an anon session request", anonSessionRequest);
+
+ // check that request was not sent to track purchase endpoint
+ RecordedRequest purchaseRequest1 = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNull("There should not be a purchase request", purchaseRequest1);
+
+ // clear any pending requests
+ while (server.takeRequest(1, TimeUnit.SECONDS) != null) { }
+
+ // mock merge response
+ addResponse(IterableConstants.ENDPOINT_MERGE_USER);
+
+ // set email
+ final String email = "testUser@gmail.com";
+ IterableIdentityResolution identityResolution = new IterableIdentityResolution(false, false);
+ IterableApi.getInstance().setEmail(email, identityResolution);
+
+ // check that request was not sent to merge endpoint
+ RecordedRequest mergeRequest = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNotNull(mergeRequest);
+ assertNotEquals(("/" + IterableConstants.ENDPOINT_MERGE_USER), mergeRequest.getPath());
+
+ // check that email was set
+ assertEquals(email, IterableApi.getInstance().getEmail());
+ }
+
+ @Test
+ public void testCriteriaNotMetEmailReplayFalseMergeTrue() throws Exception {
+ // clear any pending requests
+ while (server.takeRequest(1, TimeUnit.SECONDS) != null) { }
+
+ // mock anon session response and track purchase response
+ addResponse(IterableConstants.ENDPOINT_TRACK_ANON_SESSION);
+ addResponse(IterableConstants.ENDPOINT_TRACK_PURCHASE);
+
+ // trigger track purchase event
+ triggerTrackPurchaseEvent("test", "keyboard", 5, 1);
+ shadowOf(getMainLooper()).idle();
+
+ // check that request was not sent to anon session endpoint
+ RecordedRequest anonSessionRequest = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNull("There should not be an anon session request", anonSessionRequest);
+
+ // check that request was not sent to track purchase endpoint
+ RecordedRequest purchaseRequest1 = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNull("There should not be a purchase request", purchaseRequest1);
+
+ // clear any pending requests
+ while (server.takeRequest(1, TimeUnit.SECONDS) != null) { }
+
+ // mock merge response
+ addResponse(IterableConstants.ENDPOINT_MERGE_USER);
+
+ // set email
+ final String email = "testUser@gmail.com";
+ IterableIdentityResolution identityResolution = new IterableIdentityResolution(false, true);
+ IterableApi.getInstance().setEmail(email, identityResolution);
+
+ // check that request was not sent to merge endpoint
+ RecordedRequest mergeRequest = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNotNull(mergeRequest);
+ assertNotEquals(("/" + IterableConstants.ENDPOINT_MERGE_USER), mergeRequest.getPath());
+
+ // check that email was set
+ assertEquals(email, IterableApi.getInstance().getEmail());
+ }
+
+ @Test
+ public void testCriteriaMetEmailDefault() throws Exception {
+ // clear any pending requests
+ while (server.takeRequest(1, TimeUnit.SECONDS) != null) { }
+
+ // mock anon session response and track purchase response
+ addResponse(IterableConstants.ENDPOINT_TRACK_ANON_SESSION);
+ addResponse(IterableConstants.ENDPOINT_TRACK_PURCHASE);
+
+ // trigger track purchase event
+ triggerTrackPurchaseEvent("test", "keyboard", 4.67, 3);
+ shadowOf(getMainLooper()).idle();
+
+ // check if request was sent to anon session endpoint
+ RecordedRequest anonSessionRequest = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNotNull("Anon session request should not be null", anonSessionRequest);
+ assertEquals("/" + IterableConstants.ENDPOINT_TRACK_ANON_SESSION, anonSessionRequest.getPath());
+
+ // check if request was sent to track purchase endpoint
+ RecordedRequest purchaseRequest = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNotNull("Purchase request should not be null", purchaseRequest);
+ assertEquals("/" + IterableConstants.ENDPOINT_TRACK_PURCHASE, purchaseRequest.getPath());
+
+ // clear any pending requests
+ while (server.takeRequest(1, TimeUnit.SECONDS) != null) { }
+
+ // mock merge response
+ addResponse(IterableConstants.ENDPOINT_MERGE_USER);
+
+ // set email to trigger merging
+ final String email = "testUser2@gmail.com";
+ IterableApi.getInstance().setEmail(email);
+
+ // check if request was sent to merge endpoint
+ RecordedRequest mergeRequest = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNotNull(mergeRequest);
+ assertEquals("/" + IterableConstants.ENDPOINT_MERGE_USER, mergeRequest.getPath());
+
+ // check that email was set
+ assertEquals(email, IterableApi.getInstance().getEmail());
+ }
+
+ @Test
+ public void testCriteriaMetEmailMergeFalse() throws Exception {
+ // clear any pending requests
+ while (server.takeRequest(1, TimeUnit.SECONDS) != null) { }
+
+ // mock anon session response and track purchase response
+ addResponse(IterableConstants.ENDPOINT_TRACK_ANON_SESSION);
+ addResponse(IterableConstants.ENDPOINT_TRACK_PURCHASE);
+
+ // trigger track purchase event
+ triggerTrackPurchaseEvent("test", "keyboard", 4.67, 3);
+ shadowOf(getMainLooper()).idle();
+
+ // check if request was sent to anon session endpoint
+ RecordedRequest anonSessionRequest = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNotNull("Anon session request should not be null", anonSessionRequest);
+ assertEquals("/" + IterableConstants.ENDPOINT_TRACK_ANON_SESSION, anonSessionRequest.getPath());
+
+ // check if request was sent to track purchase endpoint
+ RecordedRequest purchaseRequest = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNotNull("Purchase request should not be null", purchaseRequest);
+ assertEquals("/" + IterableConstants.ENDPOINT_TRACK_PURCHASE, purchaseRequest.getPath());
+
+ // clear any pending requests
+ while (server.takeRequest(1, TimeUnit.SECONDS) != null) { }
+
+ // mock merge response
+ addResponse(IterableConstants.ENDPOINT_MERGE_USER);
+
+ // set email
+ final String email = "testUser@gmail.com";
+ IterableIdentityResolution identityResolution = new IterableIdentityResolution(true, false);
+ IterableApi.getInstance().setEmail(email, identityResolution);
+
+ // check if request was not sent to merge endpoint
+ RecordedRequest mergeRequest = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNotNull(mergeRequest);
+ assertNotEquals(("/" + IterableConstants.ENDPOINT_MERGE_USER), mergeRequest.getPath());
+
+ // check that email was set
+ assertEquals(email, IterableApi.getInstance().getEmail());
+ }
+
+ @Test
+ public void testCriteriaMetEmailMergeTrue() throws Exception {
+ // clear any pending requests
+ while (server.takeRequest(1, TimeUnit.SECONDS) != null) { }
+
+ // mock anon session response and track purchase response
+ addResponse(IterableConstants.ENDPOINT_TRACK_ANON_SESSION);
+ addResponse(IterableConstants.ENDPOINT_TRACK_PURCHASE);
+
+ // trigger track purchase event
+ triggerTrackPurchaseEvent("test", "keyboard", 4.67, 3);
+ shadowOf(getMainLooper()).idle();
+
+ // check if request was sent to anon session endpoint
+ RecordedRequest anonSessionRequest = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNotNull("Anon session request should not be null", anonSessionRequest);
+ assertEquals("/" + IterableConstants.ENDPOINT_TRACK_ANON_SESSION, anonSessionRequest.getPath());
+
+ // check if request was sent to track purchase endpoint
+ RecordedRequest purchaseRequest = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNotNull("Purchase request should not be null", purchaseRequest);
+ assertEquals("/" + IterableConstants.ENDPOINT_TRACK_PURCHASE, purchaseRequest.getPath());
+
+ // clear any pending requests
+ while (server.takeRequest(1, TimeUnit.SECONDS) != null) { }
+
+ // mock merge response
+ addResponse(IterableConstants.ENDPOINT_MERGE_USER);
+
+ // set email
+ final String email = "testUser@gmail.com";
+ IterableIdentityResolution identityResolution = new IterableIdentityResolution(true, true);
+ IterableApi.getInstance().setEmail(email, identityResolution);
+
+ // check if request was sent to merge endpoint
+ RecordedRequest mergeRequest = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNotNull(mergeRequest);
+ assertEquals("/" + IterableConstants.ENDPOINT_MERGE_USER, mergeRequest.getPath());
+
+ // check that email was set
+ assertEquals(email, IterableApi.getInstance().getEmail());
+ }
+
+ @Test
+ public void testIdentifiedEmailDefault() throws Exception {
+ addResponse(IterableConstants.ENDPOINT_MERGE_USER);
+
+ // set email
+ final String email1 = "testUser1@gmail.com";
+ IterableApi.getInstance().setEmail(email1);
+
+ // check that email was set
+ assertEquals(email1, IterableApi.getInstance().getEmail());
+
+ // clear any pending requests
+ while (server.takeRequest(1, TimeUnit.SECONDS) != null) { }
+
+ // set different email
+ final String email2 = "testUser2@gmail.com";
+ IterableApi.getInstance().setEmail(email2);
+
+ // check that request was not sent to merge endpoint
+ RecordedRequest mergeRequest = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNotNull(mergeRequest);
+ assertNotEquals(("/" + IterableConstants.ENDPOINT_MERGE_USER), mergeRequest.getPath());
+
+ // check that email was set
+ assertEquals(email2, IterableApi.getInstance().getEmail());
+ }
+
+ @Test
+ public void testIdentifiedEmailMergeFalse() throws Exception {
+ addResponse(IterableConstants.ENDPOINT_MERGE_USER);
+
+ // set email
+ final String email1 = "testUser1@gmail.com";
+ IterableIdentityResolution identityResolution = new IterableIdentityResolution(true, false);
+ IterableApi.getInstance().setEmail(email1, identityResolution);
+
+ // check that email was set
+ assertEquals(email1, IterableApi.getInstance().getEmail());
+
+ // clear any pending requests
+ while (server.takeRequest(1, TimeUnit.SECONDS) != null) { }
+
+ // set different email
+ final String email2 = "testUser2@gmail.com";
+ IterableApi.getInstance().setEmail(email2, identityResolution);
+
+ // check that request was not sent to merge endpoint
+ RecordedRequest mergeRequest = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNotNull(mergeRequest);
+ assertNotEquals(("/" + IterableConstants.ENDPOINT_MERGE_USER), mergeRequest.getPath());
+
+ // check that email was set
+ assertEquals(email2, IterableApi.getInstance().getEmail());
+ }
+
+ @Test
+ public void testIdentifiedEmailMergeTrue() throws Exception {
+ addResponse(IterableConstants.ENDPOINT_MERGE_USER);
+
+ // set email
+ final String email1 = "testUser1@gmail.com";
+ IterableIdentityResolution identityResolution = new IterableIdentityResolution(true, true);
+ IterableApi.getInstance().setEmail(email1, identityResolution);
+
+ // check that email was set
+ assertEquals(email1, IterableApi.getInstance().getEmail());
+
+ // clear any pending requests
+ while (server.takeRequest(1, TimeUnit.SECONDS) != null) { }
+
+ // set different email
+ final String email2 = "testUser2@gmail.com";
+ IterableApi.getInstance().setEmail(email2, identityResolution);
+
+ // check that request was not sent to merge endpoint
+ RecordedRequest mergeRequest = server.takeRequest(1, TimeUnit.SECONDS);
+ assertNotNull(mergeRequest);
+ assertNotEquals("/" + IterableConstants.ENDPOINT_MERGE_USER, mergeRequest.getPath());
+
+ // check that email was set
+ assertEquals(email2, IterableApi.getInstance().getEmail());
+ }
+}
diff --git a/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiTest.java b/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiTest.java
index 0863e9472..267a1fa48 100644
--- a/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiTest.java
+++ b/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiTest.java
@@ -172,7 +172,7 @@ public void testSetEmailWithCallback() {
IterableApi.initialize(getContext(), "apiKey");
String email = "test@example.com";
- IterableApi.getInstance().setEmail(email, null, new IterableHelper.SuccessHandler() {
+ IterableApi.getInstance().setEmail(email, new IterableHelper.SuccessHandler() {
@Override
public void onSuccess(@NonNull JSONObject data) {
assertTrue(true); // callback should be called with success
@@ -190,7 +190,7 @@ public void testSetUserIdWithCallback() {
IterableApi.initialize(getContext(), "apiKey");
String userId = "test_user_id";
- IterableApi.getInstance().setUserId(userId, null, new IterableHelper.SuccessHandler() {
+ IterableApi.getInstance().setUserId(userId, new IterableHelper.SuccessHandler() {
@Override
public void onSuccess(@NonNull JSONObject data) {
assertTrue(true); // callback should be called with success
diff --git a/iterableapi/src/test/java/com/iterable/iterableapi/IterableUtilRule.java b/iterableapi/src/test/java/com/iterable/iterableapi/IterableUtilRule.java
index 5d17ff967..4ff34bf3b 100644
--- a/iterableapi/src/test/java/com/iterable/iterableapi/IterableUtilRule.java
+++ b/iterableapi/src/test/java/com/iterable/iterableapi/IterableUtilRule.java
@@ -1,10 +1,10 @@
package com.iterable.iterableapi;
+import static org.mockito.Mockito.spy;
+
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
-import static org.mockito.Mockito.spy;
-
public class IterableUtilRule extends TestWatcher {
private IterableUtilImpl originalIterableUtil;
public IterableUtilImpl iterableUtilSpy;
diff --git a/sample-apps/inbox-customization/app/build.gradle b/sample-apps/inbox-customization/app/build.gradle
index 177b9ee54..e0932f03e 100644
--- a/sample-apps/inbox-customization/app/build.gradle
+++ b/sample-apps/inbox-customization/app/build.gradle
@@ -33,8 +33,8 @@ dependencies {
implementation 'androidx.navigation:navigation-ui-ktx:2.1.0'
implementation 'com.google.android.material:material:1.1.0'
- implementation 'com.iterable:iterableapi:3.5.8'
- implementation 'com.iterable:iterableapi-ui:3.5.8'
+ implementation 'com.iterable:iterableapi:3.6.0-beta1'
+ implementation 'com.iterable:iterableapi-ui:3.6.0-beta1'
implementation 'com.squareup.okhttp3:mockwebserver:4.2.2'
testImplementation 'junit:junit:4.12'