Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Identity API response caching #513

Open
wants to merge 12 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import android.os.Looper
import com.mparticle.MParticle
import com.mparticle.MParticle.IdentityType
import com.mparticle.internal.ConfigManager
import com.mparticle.internal.Logger
import com.mparticle.networking.Matcher
import com.mparticle.testutils.AndroidUtils
import com.mparticle.testutils.BaseCleanStartedEachTest
Expand Down Expand Up @@ -96,8 +97,9 @@ class IdentityApiTest : BaseCleanStartedEachTest() {
val user2Called = AndroidUtils.Mutable(false)
val user3Called = AndroidUtils.Mutable(false)
val latch: CountDownLatch = MPLatch(3)

MParticle.getInstance()!!.Identity().addIdentityStateListener { user, previousUser ->
if (user != null && user.id == mpid1) {
if (user != null && user.id == mStartingMpid) {
user1Called.value = true
latch.countDown()
}
Expand All @@ -111,26 +113,27 @@ class IdentityApiTest : BaseCleanStartedEachTest() {

// test that change actually took place
result.addSuccessListener { identityApiResult ->
Assert.assertEquals(identityApiResult.user.id, mpid1)
Assert.assertEquals(identityApiResult.previousUser!!.id, mStartingMpid.toLong())
Assert.assertEquals(identityApiResult.user.id, mStartingMpid)
// After Adding Identity caching, it uses previous response
// Assert.assertEquals(identityApiResult.previousUser!!.id, mStartingMpid.toLong())
Mansi-mParticle marked this conversation as resolved.
Show resolved Hide resolved
}
com.mparticle.internal.AccessUtils.awaitUploadHandler()
request = IdentityApiRequest.withEmptyUser().build()
result = MParticle.getInstance()!!.Identity().identify(request)
result.addSuccessListener { identityApiResult ->
Assert.assertEquals(identityApiResult.user.id, mpid2)
Assert.assertEquals(identityApiResult.user.id, mStartingMpid)
Assert.assertEquals(
identityApiResult.user.id,
MParticle.getInstance()!!
.Identity().currentUser!!.id
)
Assert.assertEquals(identityApiResult.previousUser!!.id, mpid1)
// Assert.assertEquals(identityApiResult.previousUser!!.id, mpid1)
latch.countDown()
user3Called.value = true
}
latch.await()
Assert.assertTrue(user1Called.value)
Assert.assertTrue(user2Called.value)
// Assert.assertTrue(user1Called.value)
Assert.assertTrue(user3Called.value)
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.mparticle.identity

import android.os.Handler
import android.os.Looper
import android.util.MutableBoolean
import com.mparticle.MParticle
import com.mparticle.internal.ConfigManager
Expand All @@ -16,6 +17,8 @@ import org.junit.Assert
import org.junit.Before
import org.junit.Test
import java.io.IOException
import java.lang.reflect.Field
import java.lang.reflect.Method
import java.util.concurrent.CountDownLatch

class MParticleIdentityClientImplTest : BaseCleanStartedEachTest() {
Expand Down Expand Up @@ -57,6 +60,202 @@ class MParticleIdentityClientImplTest : BaseCleanStartedEachTest() {
Assert.assertTrue(called.value)
}

@Test
@Throws(Exception::class)
fun testLoginWithTwoDifferentUsers() {
// clear existing catch
clearIdentityCache()
val latch: CountDownLatch = MPLatch(2)
val handler = Handler(Looper.getMainLooper())
handler.postDelayed({ Assert.fail("Process not complete") }, (10 * 1000).toLong())
val called = AndroidUtils.Mutable(false)
val identityRequest = IdentityApiRequest.withEmptyUser()
.email("[email protected]")
.customerId("TestUser777777")
.build()
// Login with First User
MParticle.getInstance()?.Identity()?.login(identityRequest)?.addSuccessListener {
latch.countDown()
}
// Login With second User
MParticle.getInstance()?.Identity()?.login(IdentityApiRequest.withEmptyUser().build())?.addSuccessListener {
val currentLoginRequestCount = mServer.Requests().login.size
Assert.assertEquals(2, currentLoginRequestCount)
called.value = true
latch.countDown()
}

latch.await()
Assert.assertTrue(called.value)
}

@Test
@Throws(Exception::class)
fun testLoginWithTwoSameUsers() {
// clear existing catch
clearIdentityCache()
val latch: CountDownLatch = MPLatch(2)
val handler = Handler(Looper.getMainLooper())
handler.postDelayed({ Assert.fail("Process not complete") }, (10 * 1000).toLong())
val called = AndroidUtils.Mutable(false)
val identityRequest = IdentityApiRequest.withEmptyUser()
.email("[email protected]")
.customerId("TestUser777777")
.build()
// Login with First User
Thread {
MParticle.getInstance()?.Identity()?.login(identityRequest)?.addSuccessListener {
latch.countDown()
}
// Login With same User
MParticle.getInstance()?.Identity()?.login(identityRequest)?.addSuccessListener {
val currentLoginRequestCount = mServer.Requests().login.size
Assert.assertEquals(1, currentLoginRequestCount)
called.value = true
latch.countDown()
}
latch.await()
Assert.assertTrue(called.value)
}.start()
}

@Test
@Throws(Exception::class)
fun testLoginWithTwoSameUsers_withLogout() {
// clear existing catch
clearIdentityCache()
val latch: CountDownLatch = MPLatch(3)
val handler = Handler(Looper.getMainLooper())
handler.postDelayed({ Assert.fail("Process not complete") }, (10 * 1000).toLong())
val called = AndroidUtils.Mutable(false)
val identityRequest = IdentityApiRequest.withEmptyUser()
.email("[email protected]")
.customerId("TestUser777777")
.build()
// Login with First User
MParticle.getInstance()?.Identity()?.login(identityRequest)?.addSuccessListener {
latch.countDown()
}
MParticle.getInstance()?.Identity()?.logout()?.addSuccessListener {
latch.countDown()
}
// Login With same User
MParticle.getInstance()?.Identity()?.login(identityRequest)?.addSuccessListener {
val currentLoginRequestCount = mServer.Requests().login.size
Assert.assertEquals(2, currentLoginRequestCount)
called.value = true
latch.countDown()
}
latch.await()
Assert.assertTrue(called.value)
}

@Test
@Throws(Exception::class)
fun testLoginAndIdentitySameUser() {
// clear existing catch
clearIdentityCache()
val latch: CountDownLatch = MPLatch(2)
val handler = Handler(Looper.getMainLooper())
handler.postDelayed({ Assert.fail("Process not complete") }, (10 * 1000).toLong())
val called = AndroidUtils.Mutable(false)
val identityRequest = IdentityApiRequest.withEmptyUser()
.email("[email protected]")
.customerId("TestUser777777")
.build()
// Login with First User
MParticle.getInstance()?.Identity()?.identify(identityRequest)?.addSuccessListener {
latch.countDown()
}
// Login With same User
MParticle.getInstance()?.Identity()?.login(identityRequest)?.addSuccessListener {
val currentLoginRequestCount = mServer.Requests().login.size
Assert.assertEquals(1, currentLoginRequestCount)
val currentIdentityRequestCount = mServer.Requests().login.size
Assert.assertEquals(1, currentIdentityRequestCount)
called.value = true
latch.countDown()
}
latch.await()
Assert.assertTrue(called.value)
}

@Test
@Throws(Exception::class)
fun testTwoIdentitySameUser_WithModify() {
// clear existing catch
clearIdentityCache()
val latch: CountDownLatch = MPLatch(3)
val handler = Handler(Looper.getMainLooper())
handler.postDelayed({ Assert.fail("Process not complete") }, (10 * 1000).toLong())
val called = AndroidUtils.Mutable(false)
val identityRequest = IdentityApiRequest.withEmptyUser()
.email("[email protected]")
.customerId("TestUser777777")
.build()
val identityRequestModify = IdentityApiRequest.withEmptyUser()
.email("[email protected]")
.customerId("TestUser777777")
.build()

// Identity with First User
MParticle.getInstance()?.Identity()?.identify(identityRequest)?.addSuccessListener {
latch.countDown()
}
MParticle.getInstance()?.Identity()?.modify(identityRequestModify)?.addSuccessListener {
latch.countDown()
}
// Identity With same User
MParticle.getInstance()?.Identity()?.identify(identityRequest)?.addSuccessListener {
val currentIdentityRequestCount = mServer.Requests().identify.size
Assert.assertEquals(3, currentIdentityRequestCount)
called.value = true
latch.countDown()
}
latch.await()
Assert.assertTrue(called.value)
}

@Test
@Throws(Exception::class)
fun testLoginWithTwoSameUsers_WithTimeout() {
// clear existing catch
val mParticleIdentityClient = MParticleIdentityClientImpl(
mContext,
mConfigManager,
MParticle.OperatingSystem.ANDROID
)
clearIdentityCache()
val latch: CountDownLatch = MPLatch(2)
val handler = Handler(Looper.getMainLooper())
handler.postDelayed({ Assert.fail("Process not complete") }, (10 * 1000).toLong())
val called = AndroidUtils.Mutable(false)
val identityRequest = IdentityApiRequest.withEmptyUser()
.email("[email protected]")
.customerId("TestUser777777")
.build()
// Login with First User
Thread {
MParticle.getInstance()?.Identity()?.login(identityRequest)?.addSuccessListener {
latch.countDown()
}

val field: Field =
MParticleIdentityClientImpl::class.java.getDeclaredField("identityCacheTime")
field.isAccessible = true
field.set(mParticleIdentityClient, 0L)
// Login With same User
MParticle.getInstance()?.Identity()?.login(identityRequest)?.addSuccessListener {
val currentLoginRequestCount = mServer.Requests().login.size
Assert.assertEquals(2, currentLoginRequestCount)
called.value = true
latch.countDown()
}
latch.await()
Assert.assertTrue(called.value)
}.start()
}

@Test
@Throws(Exception::class)
fun testIdentifyMessage() {
Expand Down Expand Up @@ -309,6 +508,17 @@ class MParticleIdentityClientImplTest : BaseCleanStartedEachTest() {
}
MParticle.getInstance()?.Identity()?.apiClient = mApiClient
}
private fun clearIdentityCache() {
val mParticleIdentityClient = MParticleIdentityClientImpl(
mContext,
mConfigManager,
MParticle.OperatingSystem.ANDROID
)

val method: Method = MParticleIdentityClientImpl::class.java.getDeclaredMethod("clearCatch")
method.isAccessible = true
method.invoke(mParticleIdentityClient)
}

@Throws(JSONException::class)
private fun checkStaticsAndRemove(knowIdentites: JSONObject) {
Expand Down
22 changes: 22 additions & 0 deletions android-core/src/main/java/com/mparticle/identity/IdentityApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import com.mparticle.internal.MessageManager;
import com.mparticle.internal.listeners.ApiClass;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
Comment on lines +24 to +25
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

Looks like these imports aren't required

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
Expand Down Expand Up @@ -348,6 +350,26 @@ private void reset() {
}
}

private String generateHash(String input) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hashBytes = digest.digest(input.getBytes());

// Convert byte array to hex string
StringBuilder hexString = new StringBuilder();
for (byte b : hashBytes) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) hexString.append('0');
hexString.append(hex);
}
return hexString.toString();
} catch (NoSuchAlgorithmException e) {
//throw new RuntimeException("Hashing algorithm not found", e);
Mansi-mParticle marked this conversation as resolved.
Show resolved Hide resolved
}
return null;

}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

Also if we removed this extra line change, the whole file will be removed from the PR as it's not actually being changed

private BaseIdentityTask makeIdentityRequest(IdentityApiRequest request, final IdentityNetworkRequestRunnable networkRequest) {
if (request == null) {
request = IdentityApiRequest.withEmptyUser().build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@
import com.mparticle.internal.Logger;
import com.mparticle.internal.MPUtility;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
* Class that represents observed changes in user state, can be used as a parameter in an Identity Request.
Expand Down Expand Up @@ -211,4 +214,47 @@ public Builder userAliasHandler(@Nullable UserAliasHandler userAliasHandler) {
return this;
}
}

@Override
public boolean equals(Object obj) {
if (this == obj) return true; // Check if the same object
if (obj == null || getClass() != obj.getClass()) return false; // Check for null and class match

IdentityApiRequest that = (IdentityApiRequest) obj; // Cast to IdentityApiRequest

// Compare all relevant fields
return Objects.equals(userIdentities, that.userIdentities) &&
Objects.equals(otherOldIdentities, that.otherOldIdentities) &&
Objects.equals(otherNewIdentities, that.otherNewIdentities) &&
Objects.equals(userAliasHandler, that.userAliasHandler) &&
Objects.equals(mpid, that.mpid);
}

@NonNull
@Override
public String toString() {
return "userIdentities"+userIdentities+" otherOldIdentities " +otherOldIdentities+" otherNewIdentities "+otherNewIdentities
+" userAliasHandler "+userAliasHandler+" mpid "+String.valueOf(mpid);
}

public String convertString(){
return "";
}

public String objectToHash() {
String input =this.toString();
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] hashBytes = md.digest(input.getBytes());
StringBuilder hexString = new StringBuilder();
for (byte b : hashBytes) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) hexString.append('0');
hexString.append(hex);
}
return hexString.toString().substring(0, 16); // Shorten to first 16 characters
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
Mansi-mParticle marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
Loading
Loading