Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -278,4 +278,53 @@ abstract class BaseIntegrationTest {

return syncHappened
}

/**
* Wait for UpdateEmbedded push notification to be processed and embedded messages to sync.
* Returns true if messages were synced (either via push or manually), false if timeout.
*
* @param initialPlacementIds Initial set of placement IDs before sync
* @param expectedPlacementId Optional placement ID to check for (if provided, waits for it to appear)
* @param pushTimeoutSeconds Timeout in seconds for waiting for push
*/
protected fun waitForEmbeddedSyncViaPush(
initialPlacementIds: Set<Long>,
expectedPlacementId: Long? = null,
pushTimeoutSeconds: Long = 10
): Boolean {
Log.d("BaseIntegrationTest", "Waiting for UpdateEmbedded push to trigger sync (timeout: ${pushTimeoutSeconds}s)...")

// Wait for either:
// 1. Embedded push was processed (indicates UpdateEmbedded push arrived)
// 2. Placement IDs changed (indicates sync happened)
// 3. Expected placement ID appeared (if provided)
val pushProcessed = waitForCondition({
val currentPlacementIds = IterableApi.getInstance().embeddedManager.getPlacementIds().toSet()
val placementIdsChanged = currentPlacementIds != initialPlacementIds
val expectedPlacementFound = expectedPlacementId?.let { currentPlacementIds.contains(it) } ?: false

testUtils.isEmbeddedPushProcessed() || placementIdsChanged || expectedPlacementFound
}, pushTimeoutSeconds)

if (pushProcessed) {
Log.d("BaseIntegrationTest", "UpdateEmbedded push processed or sync detected")
// Give a bit more time for sync to complete if push was just processed
Thread.sleep(2000)
} else {
Log.d("BaseIntegrationTest", "UpdateEmbedded push not received within timeout")
}

// Check if messages were synced
val currentPlacementIds = IterableApi.getInstance().embeddedManager.getPlacementIds().toSet()
val syncHappened = currentPlacementIds != initialPlacementIds ||
(expectedPlacementId != null && currentPlacementIds.contains(expectedPlacementId))

if (syncHappened) {
Log.d("BaseIntegrationTest", "✅ Embedded messages synced via push (placement IDs: $currentPlacementIds)")
} else {
Log.d("BaseIntegrationTest", "⚠️ Embedded messages not synced yet (placement IDs: $currentPlacementIds)")
}

return syncHappened
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -134,46 +134,79 @@ class EmbeddedMessageIntegrationTest : BaseIntegrationTest() {
}
Assert.assertTrue("FragmentContainerView should exist in EmbeddedMessageTestActivity", viewReady)

// Step 3: Update user properties to make user eligible
Log.d(TAG, "📝 Step 3: Updating user properties (isPremium = true)...")
// Step 3: Get initial placement IDs before updating user properties
val initialPlacementIds = IterableApi.getInstance().embeddedManager.getPlacementIds().toSet()
Log.d(TAG, "📊 Initial placement IDs: $initialPlacementIds")

// Reset embedded push tracking to detect UpdateEmbedded push
testUtils.setEmbeddedPushProcessed(false)

// Step 4: Update user properties to make user eligible
Log.d(TAG, "📝 Step 4: Updating user properties (isPremium = true)...")
val dataFields = JSONObject().apply {
put("isPremium", true)
}
IterableApi.getInstance().updateUser(dataFields)
Log.d(TAG, "✅ User properties updated")

// Step 4: Wait 5 seconds for backend to process and make user eligible
Log.d(TAG, "⏳ Step 4: Waiting 5 seconds for backend to process user update...")
// Step 5: Wait for backend to process and make user eligible
Log.d(TAG, "⏳ Step 5: Waiting for backend to process user update...")
Thread.sleep(3000)

// Step 5: Manually sync embedded messages
Log.d(TAG, "🔄 Step 5: Syncing embedded messages...")
IterableApi.getInstance().embeddedManager.syncMessages()
// Step 6: Wait for push-triggered sync (primary path)
Log.d(TAG, "🔄 Step 6: Waiting for UpdateEmbedded push to trigger automatic sync...")
val syncViaPush = waitForEmbeddedSyncViaPush(
initialPlacementIds = initialPlacementIds,
expectedPlacementId = TEST_PLACEMENT_ID,
pushTimeoutSeconds = 10
)

// Wait for sync to complete
Thread.sleep(2000)
var placementIds = IterableApi.getInstance().embeddedManager.getPlacementIds()
var hasExpectedPlacement = placementIds.contains(TEST_PLACEMENT_ID)

// Step 6b: Fallback to manual sync if push didn't work
if (!syncViaPush || !hasExpectedPlacement) {
Log.d(TAG, "⚠️ Push-triggered sync did not complete, falling back to manual sync...")
Log.d(TAG, "🔄 Step 6b: Manually syncing embedded messages...")
Thread.sleep(2000) // Give a bit more time in case push is still arriving

// Check again before manual sync
placementIds = IterableApi.getInstance().embeddedManager.getPlacementIds()
hasExpectedPlacement = placementIds.contains(TEST_PLACEMENT_ID)

if (!hasExpectedPlacement) {
IterableApi.getInstance().embeddedManager.syncMessages()
Thread.sleep(2000) // Wait for sync to complete
placementIds = IterableApi.getInstance().embeddedManager.getPlacementIds()
hasExpectedPlacement = placementIds.contains(TEST_PLACEMENT_ID)
Log.d(TAG, "🔄 Placement IDs after manual sync: $placementIds")
} else {
Log.d(TAG, "✅ Messages synced via push (delayed), placement IDs: $placementIds")
}
} else {
Log.d(TAG, "✅ Messages synced via push-triggered automatic sync, placement IDs: $placementIds")
}

// Step 6: Get placement IDs and verify expected placement ID exists
Log.d(TAG, "🔍 Step 6: Getting placement IDs...")
val placementIds = IterableApi.getInstance().embeddedManager.getPlacementIds()
// Step 7: Verify expected placement ID exists
Log.d(TAG, "🔍 Step 7: Verifying placement ID exists...")
Log.d(TAG, "📋 Found placement IDs: $placementIds")

Assert.assertTrue(
"Placement ID $TEST_PLACEMENT_ID should exist, but found: $placementIds",
placementIds.contains(TEST_PLACEMENT_ID)
hasExpectedPlacement
)
Log.d(TAG, "✅ Placement ID $TEST_PLACEMENT_ID found")

// Step 7: Get messages for the placement ID
Log.d(TAG, "📨 Step 7: Getting messages for placement ID $TEST_PLACEMENT_ID...")
// Step 8: Get messages for the placement ID
Log.d(TAG, "📨 Step 8: Getting messages for placement ID $TEST_PLACEMENT_ID...")
val messages = IterableApi.getInstance().embeddedManager.getMessages(TEST_PLACEMENT_ID)
Assert.assertTrue("Should have at least 1 message for placement $TEST_PLACEMENT_ID", messages!!.isNotEmpty())

val message = messages.first()
Log.d(TAG, "✅ Found message: ${message.metadata.messageId}")

// Step 8: Display message using IterableEmbeddedView
Log.d(TAG, "🎨 Step 8: Displaying message using IterableEmbeddedView...")
// Step 9: Display message using IterableEmbeddedView
Log.d(TAG, "🎨 Step 9: Displaying message using IterableEmbeddedView...")

InstrumentationRegistry.getInstrumentation().runOnMainSync {
val activity = ActivityLifecycleMonitorRegistry.getInstance()
Expand All @@ -194,8 +227,8 @@ class EmbeddedMessageIntegrationTest : BaseIntegrationTest() {
// Wait for fragment to be displayed
Thread.sleep(1000)

// Step 9: Verify display - check fragment exists
Log.d(TAG, "✅ Step 9: Verifying embedded message is displayed...")
// Step 10: Verify display - check fragment exists
Log.d(TAG, "✅ Step 10: Verifying embedded message is displayed...")
var isEmbeddedFragmentDisplayed = false

InstrumentationRegistry.getInstrumentation().runOnMainSync {
Expand All @@ -221,8 +254,8 @@ class EmbeddedMessageIntegrationTest : BaseIntegrationTest() {

Log.d(TAG, "✅ Embedded message is displayed, now interacting with button...")

// Step 10: Interact with button - find and click first button
Log.d(TAG, "🎯 Step 10: Clicking button in the embedded message...")
// Step 11: Interact with button - find and click first button
Log.d(TAG, "🎯 Step 11: Clicking button in the embedded message...")

// Try to find button by resource ID first
val button = uiDevice.findObject(UiSelector().resourceId("com.iterable.iterableapi.ui:id/embedded_message_first_button"))
Expand All @@ -246,16 +279,16 @@ class EmbeddedMessageIntegrationTest : BaseIntegrationTest() {
}
}

// Step 11: Verify URL handler was called
Log.d(TAG, "🎯 Step 11: Verifying URL handler was called after button click...")
// Step 12: Verify URL handler was called
Log.d(TAG, "🎯 Step 12: Verifying URL handler was called after button click...")

val urlHandlerCalled = waitForUrlHandler(timeoutSeconds = 3)
Assert.assertTrue(
"URL handler should have been called after clicking the button",
urlHandlerCalled
)

// Step 12: Verify the correct URL was handled
// Step 13: Verify the correct URL was handled
val handledUrl = getLastHandledUrl()
Log.d(TAG, "🎯 URL handler received: $handledUrl")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,19 @@ class IntegrationFirebaseMessagingService : FirebaseMessagingService() {
val isIterableMessage = IterableFirebaseMessagingService.handleMessageReceived(this, remoteMessage)

if (isIterableMessage) {
// Check if this is an InAppUpdate push notification
// Check the notification type
val notificationType = remoteMessage.data["notificationType"]
val isInAppUpdate = notificationType == "InAppUpdate"
val isUpdateEmbedded = notificationType == "UpdateEmbedded"

if (isInAppUpdate) {
Log.d(TAG, "Received InAppUpdate push notification - SDK automatically synced in-app messages")
// Track that InAppUpdate push was received and processed
IntegrationTestUtils(this).setSilentPushProcessed(true)
} else if (isUpdateEmbedded) {
Log.d(TAG, "Received UpdateEmbedded push notification - SDK automatically synced embedded messages")
// Track that UpdateEmbedded push was received and processed
IntegrationTestUtils(this).setEmbeddedPushProcessed(true)
} else {
// Check if this is a silent push for in-app messages (legacy check)
val isSilent = remoteMessage.data["silent"] == "true"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class IntegrationTestUtils(private val context: Context) {
private val trackPushOpenCalled = AtomicBoolean(false)
private val deepLinkHandlerInvoked = AtomicBoolean(false)
private val silentPushProcessed = AtomicBoolean(false)
private val embeddedPushProcessed = AtomicBoolean(false)

// Error tracking
private var lastErrorMessage: String? = null
Expand All @@ -48,6 +49,7 @@ class IntegrationTestUtils(private val context: Context) {
trackPushOpenCalled.set(false)
deepLinkHandlerInvoked.set(false)
silentPushProcessed.set(false)
embeddedPushProcessed.set(false)
lastErrorMessage = null
}

Expand Down Expand Up @@ -84,6 +86,14 @@ class IntegrationTestUtils(private val context: Context) {
fun isSilentPushProcessed(): Boolean {
return silentPushProcessed.get()
}

fun setEmbeddedPushProcessed(processed: Boolean) {
embeddedPushProcessed.set(processed)
}

fun isEmbeddedPushProcessed(): Boolean {
return embeddedPushProcessed.get()
}

fun setInAppMessageDisplayed(displayed: Boolean) {
inAppMessageDisplayed.set(displayed)
Expand Down
Loading