Skip to content

Commit

Permalink
feat: split configuration into core and kits (mParticle#103)
Browse files Browse the repository at this point in the history
  • Loading branch information
willpassidomo committed Mar 3, 2022
1 parent a5a934a commit 766e0d3
Show file tree
Hide file tree
Showing 12 changed files with 471 additions and 67 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -163,9 +163,17 @@ public void setPushRegistration(PushRegistrationHelper.PushRegistration pushRegi
//For enablePushNotifications() to set the push registration, we need to mimic
//the Firebase dependency, and clear the push-fetched flags
TestingUtils.setFirebasePresent(true, pushRegistration.instanceId);
ConfigManager.getInstance(mContext).clearPushRegistration();

MParticle.getInstance().Messaging().enablePushNotifications(pushRegistration.senderId);
//this method setting push is async, so wait for confirmation before continuing
ConfigManager configManager = ConfigManager.getInstance(mContext);
while (!configManager.isPushEnabled()) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
TestingUtils.setFirebasePresent(false, null);
}

@Override
Expand Down Expand Up @@ -249,7 +257,7 @@ public void clearPush() {

@Override
public String getName() {
return null;
return "startMParticle(MParticleOptions.builder(mContext).pushRegistration(null, null))";
}
}
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
package com.mparticle.internal;

import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertFalse;
import static junit.framework.TestCase.assertTrue;
import static org.junit.Assert.fail;

import com.mparticle.Configuration;
import com.mparticle.MParticle;
import com.mparticle.MParticleOptions;
import com.mparticle.testutils.AndroidUtils;
import com.mparticle.testutils.BaseAbstractTest;
import com.mparticle.testutils.MPLatch;
import com.mparticle.testutils.TestingUtils;

import org.json.JSONException;
import org.json.JSONObject;
import org.junit.Test;

import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertFalse;
import static junit.framework.TestCase.assertTrue;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

public class ConfigManagerInstrumentedTest extends BaseAbstractTest {

Expand Down Expand Up @@ -49,7 +59,7 @@ public void testSetMpidCurrentUserState() throws InterruptedException {
}

@Test
public void testConfigParsing() throws JSONException, InterruptedException {
public void testConfigResponseParsing() throws JSONException, InterruptedException {
String token = mRandomUtils.getAlphaNumericString(20);
int aliasMaxWindow = ran.nextInt();

Expand All @@ -58,17 +68,125 @@ public void testConfigParsing() throws JSONException, InterruptedException {
.put(ConfigManager.ALIAS_MAX_WINDOW, aliasMaxWindow);

mServer.setupConfigResponse(config.toString());
BothConfigsLoadedListener configLoadedListener = new BothConfigsLoadedListener();
MPLatch latch = configLoadedListener.latch;

startMParticle(MParticleOptions.builder(mContext).configuration(new AddConfigListener(configLoadedListener)));
latch.await();

startMParticle();
assertEquals(token, MParticle.getInstance().Internal().getConfigManager().getWorkspaceToken());
assertEquals(aliasMaxWindow, MParticle.getInstance().Internal().getConfigManager().getAliasMaxWindow());

//test set defaults when fields are not present
MParticle.setInstance(null);
mServer.setupConfigResponse(new JSONObject().toString());
startMParticle();
configLoadedListener = new BothConfigsLoadedListener();
latch = configLoadedListener.latch;

startMParticle(MParticleOptions.builder(mContext).configuration(new AddConfigListener(configLoadedListener)));
latch.await();

assertEquals("", MParticle.getInstance().Internal().getConfigManager().getWorkspaceToken());
assertEquals(90, MParticle.getInstance().Internal().getConfigManager().getAliasMaxWindow());
}

@Test
public void cachedConfigLoadedExactlyOnce() throws InterruptedException, JSONException {
MPLatch latch = new MPLatch(1);
AndroidUtils.Mutable<Boolean> loadedCoreLocal = new AndroidUtils.Mutable<>(false);
AndroidUtils.Mutable<Boolean> loadedKitLocal = new AndroidUtils.Mutable<>(false);

setCachedConfig(getSimpleConfigWithKits());
mServer.setupConfigDeferred();
ConfigManager.ConfigLoadedListener configLoadedListener = new ConfigManager.ConfigLoadedListener() {
@Override
public void onConfigUpdated(ConfigManager.ConfigType configType, boolean isNew) {
if (!isNew) {
switch (configType) {
case CORE:
if (loadedCoreLocal.value) {
fail("core config already loaded");
} else {
Logger.error("LOADED CACHED Core");
loadedCoreLocal.value = true;
}
case KIT:
if (loadedKitLocal.value) {
fail("kit config already loaded");
} else {
Logger.error("LOADED CACHED Kit");
loadedKitLocal.value = true;
}
}
}
if (loadedCoreLocal.value && loadedKitLocal.value) {
latch.countDown();

}
Logger.error("KIT = " + loadedKitLocal.value + " Core: " + loadedCoreLocal.value);
}
};
MParticleOptions options = MParticleOptions.builder(mContext)
.credentials("key", "secret")
.configuration(new AddConfigListener(configLoadedListener))
.build();
MParticle.start(options);

//wait until both local configs are loaded
latch.await();

//try to coerce another load...
new ConfigManager(mContext);
MParticle instance = MParticle.getInstance();
instance.logEvent(TestingUtils.getInstance().getRandomMPEventSimple());
instance.setOptOut(true);
instance.setOptOut(false);

//and finally, load remote config
mServer.setupConfigResponse(getSimpleConfigWithKits().toString());
fetchConfig();
BothConfigsLoadedListener bothConfigsLoadedListener = new BothConfigsLoadedListener();
MPLatch reloadLatch = bothConfigsLoadedListener.latch;
MParticle.getInstance().Internal().getConfigManager().addConfigUpdatedListener(bothConfigsLoadedListener);
reloadLatch.await();
}

class BothConfigsLoadedListener implements ConfigManager.ConfigLoadedListener {
Set<ConfigManager.ConfigType> types;
MPLatch latch = new MPLatch(1);

public BothConfigsLoadedListener(ConfigManager.ConfigType... configTypes) {
if (configTypes == null || configTypes.length == 0) {
configTypes = new ConfigManager.ConfigType[]{ConfigManager.ConfigType.CORE};
}
types = this.types = new HashSet<ConfigManager.ConfigType>(Arrays.asList(configTypes));
}
@Override
public void onConfigUpdated(ConfigManager.ConfigType configType, boolean isNew) {
if (isNew) {
types.remove(configType);
}
if (types.size() == 0) {
latch.countDown();
}
}
}

class AddConfigListener implements Configuration<ConfigManager> {
ConfigManager.ConfigLoadedListener configLoadedListener;

public AddConfigListener(ConfigManager.ConfigLoadedListener configLoadedListener) {
this.configLoadedListener = configLoadedListener;
}

@Override
public Class<ConfigManager> configures() {
return ConfigManager.class;
}

@Override
public void apply(ConfigManager configManager) {
configManager.addConfigUpdatedListener(configLoadedListener);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package com.mparticle.internal

import com.mparticle.MParticle
import com.mparticle.MParticleOptions
import com.mparticle.testutils.BaseCleanInstallEachTest
import org.json.JSONObject
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertNull

class ConfigMigrationTest : BaseCleanInstallEachTest() {
private val oldConfigSharedprefsKey = "json"

@Test
fun testConfigContentsMigratedProperly() {
// store config in the old place
val oldConfig = JSONObject().put("foo", "bar")
setOldConfigState(oldConfig)

// double check that there is nothing stored in the new place
assertOldConfigState(oldConfig)

// initialize ConfigManager
val configManager = ConfigManager(mContext)

// make sure the config is in the new place and gone from the old
assertNewConfigState(oldConfig)

// make sure config is also available via ConfigManager api
assertEquals(oldConfig.toString(), configManager.config)
}

@Test
fun testConfigMetadataRemainsDuringMigration() {
val oldConfig = JSONObject().put("foo", "bar")
val testTimestamp = System.currentTimeMillis() - 1000L
val testEtag = "some etag"
val testIfModified = "foo bar"

// set metadata + config in the old place
ConfigManager(mContext).apply {
saveConfigJson(JSONObject(), null, testEtag, testIfModified, testTimestamp)
setOldConfigState(oldConfig)
}

assertOldConfigState(oldConfig)

// trigger migration, check that config metadata remained the same
ConfigManager(mContext).apply {
assertNewConfigState(oldConfig)
assertEquals(oldConfig.toString(), config)
assertEquals(testTimestamp, configTimestamp)
assertEquals(testEtag, etag)
assertEquals(testIfModified, ifModified)
}
}

@Test
fun testMigratingStaleConfig() {
val oldConfig = JSONObject().put("foo", "bar")
val testTimestamp = System.currentTimeMillis() - 1000L
val testEtag = "some etag"
val testIfModified = "foo bar"

// set metadata + config in the old place
ConfigManager(mContext).apply {
saveConfigJson(JSONObject(), null, testEtag, testIfModified, testTimestamp)
setOldConfigState(oldConfig)
}

assertOldConfigState(oldConfig)

MParticleOptions.builder(mContext)
.credentials("key", "secret")
.configMaxAgeSeconds(0)
.build()
.let {
// start it the simple way, we don't want to block or anything so we can test config state
MParticle.start(it)
}

// make sure config is deleted
MParticle.getInstance()!!.Internal().configManager.let {
assertNull(it.config)
assertNull(it.configTimestamp)
assertNull(it.etag)
assertNull(it.ifModified)
}
}

@Test
fun testMigratingNotStaleConfig() {
val oldConfig = JSONObject().put("foo", "bar")
val testTimestamp = System.currentTimeMillis() - 1000L
val testEtag = "some etag"
val testIfModified = "foo bar"

// set metadata + config in the old place
ConfigManager(mContext).apply {
saveConfigJson(JSONObject(), null, testEtag, testIfModified, testTimestamp)
setOldConfigState(oldConfig)
}

assertOldConfigState(oldConfig)

MParticleOptions.builder(mContext)
.credentials("key", "secret")
.configMaxAgeSeconds(Integer.MAX_VALUE)
.build()
.let {
// start it the simple way, we don't want to block or anything so we can test config state
MParticle.start(it)
}

// make sure config is deleted
MParticle.getInstance()!!.Internal().configManager.let {
assertEquals(oldConfig.toString(), it.config)
assertEquals(testTimestamp, it.configTimestamp)
assertEquals(testEtag, it.etag)
assertEquals(testIfModified, it.ifModified)
}
}

private fun setOldConfigState(config: JSONObject) {
ConfigManager.getInstance(mContext).getKitConfigPreferences().edit().remove(ConfigManager.KIT_CONFIG_KEY)
ConfigManager.getPreferences(mContext).edit().putString(oldConfigSharedprefsKey, config.toString()).apply()
}

private fun assertOldConfigState(config: JSONObject) {
assertNull(ConfigManager.getInstance(mContext).getKitConfigPreferences().getString(ConfigManager.KIT_CONFIG_KEY, null))
assertEquals(config.toString(), ConfigManager.getPreferences(mContext).getString(oldConfigSharedprefsKey, null))
}

private fun assertNewConfigState(config: JSONObject) {
val configString = ConfigManager.getPreferences(mContext).getString(ConfigManager.CONFIG_JSON, null)
assertNull(JSONObject(configString).optJSONArray(ConfigManager.KEY_EMBEDDED_KITS))
assertEquals(config.optString(ConfigManager.KEY_EMBEDDED_KITS, null), ConfigManager.getInstance(mContext).kitConfigPreferences.getString(ConfigManager.KIT_CONFIG_KEY, null))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ class ConfigStalenessCheckTest : BaseCleanInstallEachTest() {
latch.await()

// after config has been fetched, we should see config2
assertEquals(config2.toString(), MParticle.getInstance()?.Internal()?.configManager?.config)
assertEquals(config2.toString(), configManager.config)
}

private fun randomJson(size: Int) =
Expand All @@ -188,8 +188,8 @@ class ConfigStalenessCheckTest : BaseCleanInstallEachTest() {
fun MParticleOptions.Builder.addCredentials() = this.credentials("apiKey", "apiSecret")

fun ConfigManager.onNewConfig(callback: () -> Unit) {
addConfigUpdatedListener { configType, config ->
if (configType == ConfigManager.ConfigType.NEW) {
addConfigUpdatedListener { configType, isNew ->
if (isNew && configType == ConfigManager.ConfigType.CORE) {
callback()
}
}
Expand Down
1 change: 1 addition & 0 deletions android-core/src/main/java/com/mparticle/MParticle.java
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ private static MParticle getInstance(@NonNull Context context, @NonNull MParticl
instance.mConfigManager.clearPushRegistrationBackground();
}
}
instance.mConfigManager.onMParticleStarted();
}
}
}
Expand Down
Loading

0 comments on commit 766e0d3

Please sign in to comment.