diff --git a/api/src/main/java/io/split/android/client/events/SplitEventTask.java b/api/src/main/java/io/split/android/client/events/SplitEventTask.java index 5a5dd6db9..f880e0fe1 100644 --- a/api/src/main/java/io/split/android/client/events/SplitEventTask.java +++ b/api/src/main/java/io/split/android/client/events/SplitEventTask.java @@ -1,17 +1,113 @@ package io.split.android.client.events; +import androidx.annotation.Nullable; + import io.split.android.client.SplitClient; +import io.split.android.client.api.EventMetadata; /** - * Created by sarrubia on 3/26/18. + * Base class for handling Split SDK events. + *

+ * Extend this class and override the methods you need to handle specific SDK events. + * You can implement both the metadata-enabled and versions of the methods; + * if both are implemented, both will be called (metadata version first). + *

+ * Threading: + *

+ *

+ * Metadata: + *

+ *

+ * Example usage: + *

{@code
+ * client.on(SplitEvent.SDK_UPDATE, new SplitEventTask() {
+ *     @Override
+ *     public void onPostExecution(SplitClient client, EventMetadata metadata) {
+ *         List updatedFlags = (List) metadata.get("updatedFlags");
+ *         // Handle update with metadata
+ *     }
+ *
+ *     @Override
+ *     public void onPostExecution(SplitClient client) {
+ *         // Legacy handling (also called if both are implemented)
+ *     }
+ * });
+ * }
*/ - public class SplitEventTask { + /** + * Called when an event occurs, executed on a background thread. + *

+ * Override this method to handle events on a background thread without metadata. + * This method is executed immediately and is faster than {@link #onPostExecutionView(SplitClient)}. + * + * @param client the Split client instance + * @throws SplitEventTaskMethodNotImplementedException if not overridden (default behavior) + */ public void onPostExecution(SplitClient client) { throw new SplitEventTaskMethodNotImplementedException(); } + /** + * Called when an event occurs, executed on the main/UI thread. + *

+ * Override this method to handle events on the main thread without metadata. + * Use this when you need to update UI components. + *

+ * Note: This method is queued on the main looper, so execution may be delayed + * compared to {@link #onPostExecution(SplitClient)}. + * + * @param client the Split client instance + * @throws SplitEventTaskMethodNotImplementedException if not overridden (default behavior) + */ public void onPostExecutionView(SplitClient client) { throw new SplitEventTaskMethodNotImplementedException(); } + + /** + * Called when an event occurs with metadata, executed on a background thread. + *

+ * Override this method to handle events on a background thread with access to event metadata. + * The metadata contains event-specific information such as updated flag names for SDK_UPDATE events. + * This method is executed immediately and is faster than {@link #onPostExecutionView(SplitClient, EventMetadata)}. + *

+ * If both this method and {@link #onPostExecution(SplitClient)} are implemented, + * both will be called (this method first). + * + * @param client the Split client instance + * @param metadata the event metadata, may be {@code null} for some events + * @throws SplitEventTaskMethodNotImplementedException if not overridden (default behavior) + */ + public void onPostExecution(SplitClient client, @Nullable EventMetadata metadata) { + throw new SplitEventTaskMethodNotImplementedException(); + } + + /** + * Called when an event occurs with metadata, executed on the main/UI thread. + *

+ * Override this method to handle events on the main thread with access to event metadata. + * The metadata contains event-specific information such as updated flag names for SDK_UPDATE events. + * Use this when you need to update UI components based on event metadata. + *

+ * Note: This method is queued on the main looper, so execution may be delayed + * compared to {@link #onPostExecution(SplitClient, EventMetadata)}. + *

+ * If both this method and {@link #onPostExecutionView(SplitClient)} are implemented, + * both will be called (this method first). + * + * @param client the Split client instance + * @param metadata the event metadata, may be {@code null} for some events + * @throws SplitEventTaskMethodNotImplementedException if not overridden (default behavior) + */ + public void onPostExecutionView(SplitClient client, @Nullable EventMetadata metadata) { + throw new SplitEventTaskMethodNotImplementedException(); + } } diff --git a/events-domain/src/main/java/io/split/android/client/events/SplitEventsManager.java b/events-domain/src/main/java/io/split/android/client/events/SplitEventsManager.java index 3dca17c88..cc417efe2 100644 --- a/events-domain/src/main/java/io/split/android/client/events/SplitEventsManager.java +++ b/events-domain/src/main/java/io/split/android/client/events/SplitEventsManager.java @@ -145,30 +145,66 @@ public void run() { } private EventHandler createBackgroundHandler(final SplitEventTask task) { - return new EventHandler() { + return createEventHandler(task, "background", new TaskMethodCaller() { @Override - public void handle(SplitEvent event, EventMetadata metadata) { - try { - task.onPostExecution(mResources.getSplitClient()); - } catch (SplitEventTaskMethodNotImplementedException e) { - // Method not implemented by client, ignore - } catch (Exception e) { - Logger.e("Error executing background event task: " + e.getMessage()); - } + public void callWithMetadata(EventMetadata metadata) { + task.onPostExecution(mResources.getSplitClient(), metadata); } - }; + + @Override + public void callWithoutMetadata() { + task.onPostExecution(mResources.getSplitClient()); + } + }); } private EventHandler createMainThreadHandler(final SplitEventTask task) { + return createEventHandler(task, "main thread", new TaskMethodCaller() { + @Override + public void callWithMetadata(EventMetadata metadata) { + task.onPostExecutionView(mResources.getSplitClient(), metadata); + } + + @Override + public void callWithoutMetadata() { + task.onPostExecutionView(mResources.getSplitClient()); + } + }); + } + + /** + * Helper interface for calling task methods. + */ + private interface TaskMethodCaller { + void callWithMetadata(EventMetadata metadata) throws Exception; + void callWithoutMetadata() throws Exception; + } + + private EventHandler createEventHandler( + final SplitEventTask task, + final String threadType, + final TaskMethodCaller caller) { return new EventHandler() { @Override public void handle(SplitEvent event, EventMetadata metadata) { + executeTaskMethod(metadata, true, threadType, caller); + executeTaskMethod(metadata, false, threadType, caller); + } + + private void executeTaskMethod(EventMetadata metadata, boolean withMetadata, String threadType, TaskMethodCaller caller) { try { - task.onPostExecutionView(mResources.getSplitClient()); + if (withMetadata) { + caller.callWithMetadata(metadata); + } else { + caller.callWithoutMetadata(); + } } catch (SplitEventTaskMethodNotImplementedException e) { // Method not implemented by client, ignore } catch (Exception e) { - Logger.e("Error executing main thread event task: " + e.getMessage()); + String errorPrefix = withMetadata + ? "Error executing " + threadType + " event task (with metadata): " + : "Error executing " + threadType + " event task: "; + Logger.e(errorPrefix + e.getMessage()); } } }; diff --git a/main/src/androidTest/java/tests/service/EventsManagerTest.java b/main/src/androidTest/java/tests/service/EventsManagerTest.java index 6462168bc..0ff18385a 100644 --- a/main/src/androidTest/java/tests/service/EventsManagerTest.java +++ b/main/src/androidTest/java/tests/service/EventsManagerTest.java @@ -1,17 +1,26 @@ package tests.service; +import android.os.Looper; + import org.junit.Assert; import org.junit.Test; +import java.util.Arrays; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; import fake.SplitEventExecutorResourcesMock; import helper.TestingHelper; +import io.split.android.client.SplitClient; import io.split.android.client.SplitClientConfig; +import io.split.android.client.api.EventMetadata; import io.split.android.client.events.SplitEvent; +import io.split.android.client.events.SplitEventTask; import io.split.android.client.events.SplitEventsManager; import io.split.android.client.events.SplitInternalEvent; +import io.split.android.client.events.metadata.EventMetadataHelpers; import io.split.android.client.service.executor.SplitTaskExecutorImpl; public class EventsManagerTest { @@ -232,4 +241,95 @@ public void testTimeoutMySegmentsUpdated() throws InterruptedException { Assert.assertFalse(updateTask.onExecutedCalled); Assert.assertTrue(timeoutTask.onExecutedCalled); } + + @Test + public void testAllFourCallbackMethodsAreCalledWithCorrectThreadContext() throws InterruptedException { + SplitClientConfig cfg = SplitClientConfig.builder().build(); + SplitEventsManager eventManager = new SplitEventsManager(new SplitTaskExecutorImpl(), cfg.blockUntilReady()); + eventManager.setExecutionResources(new SplitEventExecutorResourcesMock()); + + CountDownLatch readyLatch = new CountDownLatch(1); + CountDownLatch allCalledLatch = new CountDownLatch(4); // Expect 4 calls + + AtomicBoolean backgroundMetadataCalled = new AtomicBoolean(false); + AtomicBoolean backgroundLegacyCalled = new AtomicBoolean(false); + AtomicBoolean mainThreadMetadataCalled = new AtomicBoolean(false); + AtomicBoolean mainThreadLegacyCalled = new AtomicBoolean(false); + + AtomicBoolean backgroundMetadataOnMainThread = new AtomicBoolean(true); // Should be false + AtomicBoolean backgroundLegacyOnMainThread = new AtomicBoolean(true); // Should be false + AtomicBoolean mainThreadMetadataOnMainThread = new AtomicBoolean(false); // Should be true + AtomicBoolean mainThreadLegacyOnMainThread = new AtomicBoolean(false); // Should be true + + AtomicReference backgroundMetadata = new AtomicReference<>(); + AtomicReference mainThreadMetadata = new AtomicReference<>(); + + // Wait for SDK_READY first + eventManager.register(SplitEvent.SDK_READY, new SplitEventTask() { + @Override + public void onPostExecutionView(SplitClient client) { + readyLatch.countDown(); + } + }); + + // Register a task that implements ALL FOUR methods + eventManager.register(SplitEvent.SDK_UPDATE, new SplitEventTask() { + @Override + public void onPostExecution(SplitClient client, EventMetadata metadata) { + backgroundMetadataCalled.set(true); + backgroundMetadataOnMainThread.set(Looper.myLooper() == Looper.getMainLooper()); + backgroundMetadata.set(metadata); + allCalledLatch.countDown(); + } + + @Override + public void onPostExecution(SplitClient client) { + backgroundLegacyCalled.set(true); + backgroundLegacyOnMainThread.set(Looper.myLooper() == Looper.getMainLooper()); + allCalledLatch.countDown(); + } + + @Override + public void onPostExecutionView(SplitClient client, EventMetadata metadata) { + mainThreadMetadataCalled.set(true); + mainThreadMetadataOnMainThread.set(Looper.myLooper() == Looper.getMainLooper()); + mainThreadMetadata.set(metadata); + allCalledLatch.countDown(); + } + + @Override + public void onPostExecutionView(SplitClient client) { + mainThreadLegacyCalled.set(true); + mainThreadLegacyOnMainThread.set(Looper.myLooper() == Looper.getMainLooper()); + allCalledLatch.countDown(); + } + }); + + // Make SDK_READY fire + eventManager.notifyInternalEvent(SplitInternalEvent.TARGETING_RULES_SYNC_COMPLETE); + eventManager.notifyInternalEvent(SplitInternalEvent.MEMBERSHIPS_SYNC_COMPLETE); + Assert.assertTrue("SDK_READY should fire", readyLatch.await(5, TimeUnit.SECONDS)); + + // Trigger SDK_UPDATE with metadata + EventMetadata metadata = EventMetadataHelpers.createUpdatedFlagsMetadata( + Arrays.asList("flag1", "flag2")); + eventManager.notifyInternalEvent(SplitInternalEvent.SPLITS_UPDATED, metadata); + + Assert.assertTrue("All four callbacks should be called", allCalledLatch.await(5, TimeUnit.SECONDS)); + + Assert.assertTrue("Background metadata method should be called", backgroundMetadataCalled.get()); + Assert.assertTrue("Background legacy method should be called", backgroundLegacyCalled.get()); + Assert.assertTrue("Main thread metadata method should be called", mainThreadMetadataCalled.get()); + Assert.assertTrue("Main thread legacy method should be called", mainThreadLegacyCalled.get()); + + Assert.assertFalse("Background metadata method should NOT run on main thread", backgroundMetadataOnMainThread.get()); + Assert.assertFalse("Background legacy method should NOT run on main thread", backgroundLegacyOnMainThread.get()); + Assert.assertTrue("Main thread metadata method SHOULD run on main thread", mainThreadMetadataOnMainThread.get()); + Assert.assertTrue("Main thread legacy method SHOULD run on main thread", mainThreadLegacyOnMainThread.get()); + + Assert.assertNotNull("Background metadata should not be null", backgroundMetadata.get()); + Assert.assertTrue("Background metadata should contain updatedFlags", backgroundMetadata.get().containsKey("updatedFlags")); + Assert.assertNotNull("Main thread metadata should not be null", mainThreadMetadata.get()); + Assert.assertTrue("Main thread metadata should contain updatedFlags", mainThreadMetadata.get().containsKey("updatedFlags")); + } } diff --git a/main/src/test/java/io/split/android/client/events/EventsManagerTest.java b/main/src/test/java/io/split/android/client/events/EventsManagerTest.java index d4a1e1977..5b4643f3a 100644 --- a/main/src/test/java/io/split/android/client/events/EventsManagerTest.java +++ b/main/src/test/java/io/split/android/client/events/EventsManagerTest.java @@ -1,6 +1,8 @@ package io.split.android.client.events; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.when; @@ -11,13 +13,17 @@ import org.mockito.MockitoAnnotations; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; import io.split.android.client.SplitClient; import io.split.android.client.SplitClientConfig; +import io.split.android.client.api.EventMetadata; import io.split.android.client.events.executors.SplitEventExecutorResources; +import io.split.android.client.events.metadata.EventMetadataHelpers; import io.split.android.fake.SplitTaskExecutorStub; public class EventsManagerTest { @@ -266,4 +272,176 @@ private static void execute(boolean shouldStop, long intervalExecutionTime, long } } } + + @Test + public void sdkUpdateWithMetadataCallsMetadataMethod() throws InterruptedException { + SplitEventsManager eventManager = new SplitEventsManager(new SplitTaskExecutorStub(), 0); + CountDownLatch readyLatch = new CountDownLatch(1); + CountDownLatch updateLatch = new CountDownLatch(1); + AtomicReference receivedMetadata = new AtomicReference<>(); + + waitForSdkReady(eventManager, readyLatch); + + eventManager.register(SplitEvent.SDK_UPDATE, new SplitEventTask() { + @Override + public void onPostExecution(SplitClient client, EventMetadata metadata) { + receivedMetadata.set(metadata); + updateLatch.countDown(); + } + }); + + EventMetadata metadata = createTestMetadata(); + triggerSdkUpdateWithMetadata(eventManager, metadata); + + boolean updateAwait = updateLatch.await(3, TimeUnit.SECONDS); + assertTrue("SDK_UPDATE callback should be called", updateAwait); + assertNotNull("Metadata should not be null", receivedMetadata.get()); + assertTrue("Metadata should contain updatedFlags", receivedMetadata.get().containsKey("updatedFlags")); + } + + @Test + public void sdkUpdateWithMetadataCallsMetadataMethodOnMainThread() throws InterruptedException { + SplitEventsManager eventManager = new SplitEventsManager(new SplitTaskExecutorStub(), 0); + CountDownLatch readyLatch = new CountDownLatch(1); + CountDownLatch updateLatch = new CountDownLatch(1); + AtomicReference receivedMetadata = new AtomicReference<>(); + + waitForSdkReady(eventManager, readyLatch); + + eventManager.register(SplitEvent.SDK_UPDATE, new SplitEventTask() { + @Override + public void onPostExecutionView(SplitClient client, EventMetadata metadata) { + receivedMetadata.set(metadata); + updateLatch.countDown(); + } + }); + + EventMetadata metadata = createTestMetadata(); + triggerSdkUpdateWithMetadata(eventManager, metadata); + + boolean updateAwait = updateLatch.await(3, TimeUnit.SECONDS); + assertTrue("SDK_UPDATE callback should be called on main thread", updateAwait); + assertNotNull("Metadata should not be null", receivedMetadata.get()); + assertTrue("Metadata should contain updatedFlags", receivedMetadata.get().containsKey("updatedFlags")); + } + + @Test + public void sdkUpdateCallsLegacyMethodWhenOnlyLegacyImplemented() throws InterruptedException { + SplitEventsManager eventManager = new SplitEventsManager(new SplitTaskExecutorStub(), 0); + CountDownLatch readyLatch = new CountDownLatch(1); + CountDownLatch updateLatch = new CountDownLatch(1); + final boolean[] nonMetadataMethodCalled = {false}; + + waitForSdkReady(eventManager, readyLatch); + + eventManager.register(SplitEvent.SDK_UPDATE, new SplitEventTask() { + @Override + public void onPostExecution(SplitClient client) { + nonMetadataMethodCalled[0] = true; + updateLatch.countDown(); + } + }); + + EventMetadata metadata = createTestMetadata(); + triggerSdkUpdateWithMetadata(eventManager, metadata); + + boolean updateAwait = updateLatch.await(3, TimeUnit.SECONDS); + assertTrue("SDK_UPDATE callback should be called", updateAwait); + assertTrue("Legacy method should be called", nonMetadataMethodCalled[0]); + } + + @Test + public void sdkUpdateCallsBothMethodsWhenBothImplemented() throws InterruptedException { + SplitEventsManager eventManager = new SplitEventsManager(new SplitTaskExecutorStub(), 0); + CountDownLatch readyLatch = new CountDownLatch(1); + CountDownLatch bothCalledLatch = new CountDownLatch(2); + final boolean[] metadataMethodCalled = {false}; + final boolean[] legacyMethodCalled = {false}; + AtomicReference receivedMetadata = new AtomicReference<>(); + + waitForSdkReady(eventManager, readyLatch); + + eventManager.register(SplitEvent.SDK_UPDATE, new SplitEventTask() { + @Override + public void onPostExecution(SplitClient client, EventMetadata metadata) { + metadataMethodCalled[0] = true; + receivedMetadata.set(metadata); + bothCalledLatch.countDown(); + } + + @Override + public void onPostExecution(SplitClient client) { + legacyMethodCalled[0] = true; + bothCalledLatch.countDown(); + } + }); + + EventMetadata metadata = createTestMetadata(); + triggerSdkUpdateWithMetadata(eventManager, metadata); + + boolean bothCalled = bothCalledLatch.await(3, TimeUnit.SECONDS); + assertTrue("Both callbacks should be called", bothCalled); + assertTrue("Metadata method should be called", metadataMethodCalled[0]); + assertTrue("Legacy method should also be called", legacyMethodCalled[0]); + assertNotNull("Metadata should be passed to metadata method", receivedMetadata.get()); + assertTrue("Metadata should contain updatedFlags", receivedMetadata.get().containsKey("updatedFlags")); + } + + @Test + public void sdkReadyFromCacheCallsBothMethodsWhenBothImplemented() throws InterruptedException { + SplitEventsManager eventManager = new SplitEventsManager(new SplitTaskExecutorStub(), 0); + + CountDownLatch bothCalledLatch = new CountDownLatch(2); // Expect 2 calls + final boolean[] metadataMethodCalled = {false}; + final boolean[] legacyMethodCalled = {false}; + + // Register a task that implements both versions + eventManager.register(SplitEvent.SDK_READY_FROM_CACHE, new SplitEventTask() { + @Override + public void onPostExecution(SplitClient client, EventMetadata metadata) { + metadataMethodCalled[0] = true; + bothCalledLatch.countDown(); + } + + @Override + public void onPostExecution(SplitClient client) { + legacyMethodCalled[0] = true; + bothCalledLatch.countDown(); + } + }); + + // Trigger SDK_READY_FROM_CACHE + eventManager.notifyInternalEvent(SplitInternalEvent.SPLITS_LOADED_FROM_STORAGE); + eventManager.notifyInternalEvent(SplitInternalEvent.MY_SEGMENTS_LOADED_FROM_STORAGE); + eventManager.notifyInternalEvent(SplitInternalEvent.ATTRIBUTES_LOADED_FROM_STORAGE); + eventManager.notifyInternalEvent(SplitInternalEvent.ENCRYPTION_MIGRATION_DONE); + + boolean bothCalled = bothCalledLatch.await(3, TimeUnit.SECONDS); + assertTrue("Both callbacks should be called", bothCalled); + assertTrue("Metadata method should be called", metadataMethodCalled[0]); + assertTrue("Legacy method should also be called", legacyMethodCalled[0]); + } + + private void waitForSdkReady(SplitEventsManager eventManager, CountDownLatch readyLatch) throws InterruptedException { + eventManager.register(SplitEvent.SDK_READY, new SplitEventTask() { + @Override + public void onPostExecutionView(SplitClient client) { + readyLatch.countDown(); + } + }); + + eventManager.notifyInternalEvent(SplitInternalEvent.TARGETING_RULES_SYNC_COMPLETE); + eventManager.notifyInternalEvent(SplitInternalEvent.MEMBERSHIPS_SYNC_COMPLETE); + boolean readyAwait = readyLatch.await(3, TimeUnit.SECONDS); + assertTrue("SDK_READY should be triggered", readyAwait); + } + + private static EventMetadata createTestMetadata() { + return EventMetadataHelpers.createUpdatedFlagsMetadata( + Arrays.asList("flag1", "flag2")); + } + + private static void triggerSdkUpdateWithMetadata(SplitEventsManager eventManager, EventMetadata metadata) { + eventManager.notifyInternalEvent(SplitInternalEvent.SPLITS_UPDATED, metadata); + } } diff --git a/main/src/test/java/io/split/android/client/events/SplitEventTaskMetadataTest.java b/main/src/test/java/io/split/android/client/events/SplitEventTaskMetadataTest.java new file mode 100644 index 000000000..03b125ae2 --- /dev/null +++ b/main/src/test/java/io/split/android/client/events/SplitEventTaskMetadataTest.java @@ -0,0 +1,123 @@ +package io.split.android.client.events; + +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import io.split.android.client.SplitClient; +import io.split.android.client.api.EventMetadata; +import io.split.android.client.events.metadata.EventMetadataHelpers; + +public class SplitEventTaskMetadataTest { + + @Mock + private SplitClient mClient; + + @Mock + private EventMetadata mMetadata; + + @Before + public void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + public void onPostExecutionWithMetadataThrowsExceptionWhenNotImplemented() { + SplitEventTask task = new SplitEventTask(); + + assertThrows(SplitEventTaskMethodNotImplementedException.class, () -> { + task.onPostExecution(mClient, mMetadata); + }); + } + + @Test + public void onPostExecutionViewWithMetadataThrowsExceptionWhenNotImplemented() { + SplitEventTask task = new SplitEventTask(); + + assertThrows(SplitEventTaskMethodNotImplementedException.class, () -> { + task.onPostExecutionView(mClient, mMetadata); + }); + } + + @Test + public void onPostExecutionWithMetadataCanBeOverridden() { + EventMetadata metadata = EventMetadataHelpers.createUpdatedFlagsMetadata( + java.util.Arrays.asList("flag1", "flag2")); + + SplitEventTask task = new SplitEventTask() { + @Override + public void onPostExecution(SplitClient client, EventMetadata metadata) { + // Overridden implementation + } + }; + + // Should not throw exception + task.onPostExecution(mClient, metadata); + } + + @Test + public void onPostExecutionViewWithMetadataCanBeOverridden() { + EventMetadata metadata = EventMetadataHelpers.createCacheReadyMetadata(1234567890L, false); + + SplitEventTask task = new SplitEventTask() { + @Override + public void onPostExecutionView(SplitClient client, EventMetadata metadata) { + // Overridden implementation + } + }; + + // Should not throw exception + task.onPostExecutionView(mClient, metadata); + } + + @Test + public void onPostExecutionWithMetadataReceivesCorrectParameters() { + EventMetadata expectedMetadata = EventMetadataHelpers.createUpdatedFlagsMetadata( + java.util.Arrays.asList("flag1", "flag2")); + + final boolean[] metadataReceived = {false}; + final boolean[] hasUpdatedFlags = {false}; + + SplitEventTask task = new SplitEventTask() { + @Override + public void onPostExecution(SplitClient client, EventMetadata metadata) { + metadataReceived[0] = metadata != null; + hasUpdatedFlags[0] = metadata != null && metadata.containsKey("updatedFlags"); + } + }; + + task.onPostExecution(mClient, expectedMetadata); + + assertTrue("Metadata should be received", metadataReceived[0]); + assertTrue("Metadata should contain updatedFlags", hasUpdatedFlags[0]); + } + + @Test + public void onPostExecutionViewWithMetadataReceivesCorrectParameters() { + EventMetadata expectedMetadata = EventMetadataHelpers.createCacheReadyMetadata(1234567890L, false); + + final boolean[] metadataReceived = {false}; + final boolean[] hasTimestamp = {false}; + final boolean[] hasFreshInstall = {false}; + + SplitEventTask task = new SplitEventTask() { + @Override + public void onPostExecutionView(SplitClient client, EventMetadata metadata) { + metadataReceived[0] = metadata != null; + hasTimestamp[0] = metadata != null && metadata.containsKey("lastUpdateTimestamp"); + hasFreshInstall[0] = metadata != null && metadata.containsKey("freshInstall"); + } + }; + + task.onPostExecutionView(mClient, expectedMetadata); + + assertTrue("Metadata should be received", metadataReceived[0]); + assertTrue("Metadata should contain lastUpdateTimestamp", hasTimestamp[0]); + assertTrue("Metadata should contain freshInstall", hasFreshInstall[0]); + } +} +