Skip to content

Commit 21d4afd

Browse files
[SDK-115] Add test user email override on MainActivity
Lets a developer pick which user the SDK signs in as without editing local.properties or rebuilding. Field + Save / Clear buttons on MainActivity persist to SharedPreferences; both manual app flows and instrumented tests honour it (override → CI dated email → BuildConfig). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 8132118 commit 21d4afd

5 files changed

Lines changed: 207 additions & 13 deletions

File tree

integration-tests/src/androidTest/java/com/iterable/integration/tests/BaseIntegrationTest.kt

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import com.iterable.iterableapi.IterableApi
99
import com.iterable.iterableapi.IterableConfig
1010
import com.iterable.integration.tests.utils.IntegrationTestUtils
1111
import com.iterable.integration.tests.utils.TestUserEmailGenerator
12+
import com.iterable.integration.tests.utils.TestUserEmailOverride
1213
import com.iterable.integration.tests.TestConstants
1314
import org.awaitility.Awaitility
1415
import org.awaitility.core.ConditionTimeoutException
@@ -36,14 +37,16 @@ abstract class BaseIntegrationTest {
3637
arg?.lowercase().let { it == "true" || it == "1" }
3738
}
3839

39-
// Dated user email in CI, hardcoded BCIT user locally. Mirrors the iOS BCIT
40-
// shape (YYYY-MM-DD-integration-test-user@test.com) so a fresh user is created
41-
// daily and audience-eligibility transitions fire reliably.
40+
// Resolution order: local override (set via MainActivity, persisted in
41+
// SharedPreferences) → dated email when in CI → BuildConfig fallback.
4242
@JvmStatic
43-
val testUserEmail: String by lazy {
44-
if (isRunningInCI) TestUserEmailGenerator.generate()
45-
else TestConstants.TEST_USER_EMAIL
46-
}
43+
val testUserEmail: String
44+
get() {
45+
val ctx = ApplicationProvider.getApplicationContext<Context>()
46+
TestUserEmailOverride.get(ctx)?.let { return it }
47+
return if (isRunningInCI) TestUserEmailGenerator.generate()
48+
else TestConstants.TEST_USER_EMAIL
49+
}
4750
}
4851

4952
protected lateinit var context: Context

integration-tests/src/main/java/com/iterable/integration/tests/MainActivity.kt

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import com.iterable.iterableapi.IterableConfig
1010
import com.iterable.iterableapi.IterableUrlHandler
1111
import com.iterable.integration.tests.activities.*
1212
import com.iterable.integration.tests.utils.IntegrationTestUtils
13+
import com.iterable.integration.tests.utils.TestUserEmailOverride
1314
import com.iterable.integration.tests.TestConstants
1415

1516
class MainActivity : AppCompatActivity() {
@@ -77,11 +78,13 @@ class MainActivity : AppCompatActivity() {
7778
.build()
7879

7980
IterableApi.initialize(this, BuildConfig.ITERABLE_API_KEY, config)
80-
81-
// Set the user email for integration testing
82-
val userEmail = TestConstants.TEST_USER_EMAIL
81+
82+
// Override (set via the field on this screen) takes precedence over the
83+
// BuildConfig email so a developer can validate against any user without
84+
// editing local.properties or rebuilding.
85+
val userEmail = TestUserEmailOverride.get(this) ?: TestConstants.TEST_USER_EMAIL
8386
IterableApi.getInstance().setEmail(userEmail)
84-
87+
8588
Log.d(TAG, "Iterable SDK initialized successfully with email: $userEmail")
8689
} catch (e: Exception) {
8790
Log.e(TAG, "Failed to initialize Iterable SDK", e)
@@ -96,7 +99,9 @@ class MainActivity : AppCompatActivity() {
9699
else -> "API Key: ****${apiKey.takeLast(4)} (length=${apiKey.length})"
97100
}
98101
findViewById<android.widget.TextView>(R.id.tvApiKey).text = keyDisplay
99-
102+
103+
setupTestEmailOverride()
104+
100105
findViewById<android.widget.Button>(R.id.btnPushNotifications).setOnClickListener {
101106
startActivity(Intent(this@MainActivity, PushNotificationTestActivity::class.java))
102107
}
@@ -131,4 +136,39 @@ class MainActivity : AppCompatActivity() {
131136
startActivity(intent)
132137
}
133138
}
134-
}
139+
140+
private fun setupTestEmailOverride() {
141+
val currentLabel = findViewById<android.widget.TextView>(R.id.tvCurrentTestEmail)
142+
val editField = findViewById<com.google.android.material.textfield.TextInputEditText>(R.id.etTestEmailOverride)
143+
val saveButton = findViewById<android.widget.Button>(R.id.btnSaveTestEmail)
144+
val clearButton = findViewById<android.widget.Button>(R.id.btnClearTestEmail)
145+
146+
fun refresh() {
147+
val override = TestUserEmailOverride.get(this)
148+
val effective = override ?: TestConstants.TEST_USER_EMAIL
149+
val source = if (override != null) "override" else "BuildConfig"
150+
currentLabel.text = "Current: $effective ($source)"
151+
editField.setText(override ?: "")
152+
}
153+
refresh()
154+
155+
saveButton.setOnClickListener {
156+
val email = editField.text?.toString()?.trim().orEmpty()
157+
if (email.isEmpty()) {
158+
android.widget.Toast.makeText(this, "Enter an email first", android.widget.Toast.LENGTH_SHORT).show()
159+
return@setOnClickListener
160+
}
161+
TestUserEmailOverride.set(this, email)
162+
IterableApi.getInstance().setEmail(email)
163+
refresh()
164+
android.widget.Toast.makeText(this, "Test email override saved", android.widget.Toast.LENGTH_SHORT).show()
165+
}
166+
167+
clearButton.setOnClickListener {
168+
TestUserEmailOverride.clear(this)
169+
IterableApi.getInstance().setEmail(TestConstants.TEST_USER_EMAIL)
170+
refresh()
171+
android.widget.Toast.makeText(this, "Test email override cleared", android.widget.Toast.LENGTH_SHORT).show()
172+
}
173+
}
174+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.iterable.integration.tests.utils
2+
3+
import android.content.Context
4+
import android.content.SharedPreferences
5+
6+
/**
7+
* Persisted local override of the test user email. When set, takes precedence over
8+
* the BuildConfig default and the CI dated email — so a developer can validate
9+
* tests against any user (e.g. their own personal Iterable account) without
10+
* editing local.properties or rebuilding.
11+
*
12+
* Stored in app-private SharedPreferences. Never read in CI by the workflow path,
13+
* but BaseIntegrationTest will still honour it if a developer left an override
14+
* set on a local emulator.
15+
*/
16+
object TestUserEmailOverride {
17+
18+
private const val PREFS_NAME = "iterable_integration_tests"
19+
private const val KEY_EMAIL = "test_user_email_override"
20+
21+
fun get(context: Context): String? =
22+
prefs(context).getString(KEY_EMAIL, null)?.takeIf { it.isNotBlank() }
23+
24+
fun set(context: Context, email: String) {
25+
prefs(context).edit().putString(KEY_EMAIL, email).apply()
26+
}
27+
28+
fun clear(context: Context) {
29+
prefs(context).edit().remove(KEY_EMAIL).apply()
30+
}
31+
32+
private fun prefs(context: Context): SharedPreferences =
33+
context.applicationContext.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
34+
}

integration-tests/src/main/res/layout/activity_main.xml

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,79 @@
1818
android:layout_marginBottom="32dp"
1919
android:gravity="center" />
2020

21+
<com.google.android.material.card.MaterialCardView
22+
android:layout_width="match_parent"
23+
android:layout_height="wrap_content"
24+
android:layout_marginBottom="16dp"
25+
app:cardElevation="4dp"
26+
app:cardCornerRadius="8dp">
27+
28+
<LinearLayout
29+
android:layout_width="match_parent"
30+
android:layout_height="wrap_content"
31+
android:orientation="vertical"
32+
android:padding="16dp">
33+
34+
<TextView
35+
android:layout_width="wrap_content"
36+
android:layout_height="wrap_content"
37+
android:text="Test User Email Override"
38+
android:textSize="18sp"
39+
android:textStyle="bold"
40+
android:layout_marginBottom="8dp" />
41+
42+
<TextView
43+
android:id="@+id/tvCurrentTestEmail"
44+
android:layout_width="match_parent"
45+
android:layout_height="wrap_content"
46+
android:textSize="12sp"
47+
android:layout_marginBottom="8dp"
48+
tools:text="Current: bcituser@iterable.com" />
49+
50+
<com.google.android.material.textfield.TextInputLayout
51+
android:layout_width="match_parent"
52+
android:layout_height="wrap_content"
53+
android:hint="Override email"
54+
android:layout_marginBottom="8dp">
55+
56+
<com.google.android.material.textfield.TextInputEditText
57+
android:id="@+id/etTestEmailOverride"
58+
android:layout_width="match_parent"
59+
android:layout_height="wrap_content"
60+
android:inputType="textEmailAddress"
61+
android:imeOptions="actionDone" />
62+
63+
</com.google.android.material.textfield.TextInputLayout>
64+
65+
<LinearLayout
66+
android:layout_width="match_parent"
67+
android:layout_height="wrap_content"
68+
android:orientation="horizontal">
69+
70+
<com.google.android.material.button.MaterialButton
71+
android:id="@+id/btnSaveTestEmail"
72+
android:layout_width="0dp"
73+
android:layout_height="wrap_content"
74+
android:layout_weight="1"
75+
android:layout_marginEnd="4dp"
76+
android:text="Save"
77+
style="@style/Widget.Material3.Button" />
78+
79+
<com.google.android.material.button.MaterialButton
80+
android:id="@+id/btnClearTestEmail"
81+
android:layout_width="0dp"
82+
android:layout_height="wrap_content"
83+
android:layout_weight="1"
84+
android:layout_marginStart="4dp"
85+
android:text="Clear"
86+
style="@style/Widget.Material3.Button.OutlinedButton" />
87+
88+
</LinearLayout>
89+
90+
</LinearLayout>
91+
92+
</com.google.android.material.card.MaterialCardView>
93+
2194
<com.google.android.material.card.MaterialCardView
2295
android:layout_width="match_parent"
2396
android:layout_height="wrap_content"
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package com.iterable.integration.tests.utils
2+
3+
import androidx.test.core.app.ApplicationProvider
4+
import androidx.test.ext.junit.runners.AndroidJUnit4
5+
import org.junit.After
6+
import org.junit.Assert.assertEquals
7+
import org.junit.Assert.assertNull
8+
import org.junit.Test
9+
import org.junit.runner.RunWith
10+
11+
@RunWith(AndroidJUnit4::class)
12+
class TestUserEmailOverrideTest {
13+
14+
private val context get() = ApplicationProvider.getApplicationContext<android.content.Context>()
15+
16+
@After
17+
fun tearDown() {
18+
TestUserEmailOverride.clear(context)
19+
}
20+
21+
@Test
22+
fun `get returns null when no override has been set`() {
23+
assertNull(TestUserEmailOverride.get(context))
24+
}
25+
26+
@Test
27+
fun `set then get returns the persisted email`() {
28+
TestUserEmailOverride.set(context, "user@example.com")
29+
assertEquals("user@example.com", TestUserEmailOverride.get(context))
30+
}
31+
32+
@Test
33+
fun `clear removes a previously persisted email`() {
34+
TestUserEmailOverride.set(context, "user@example.com")
35+
TestUserEmailOverride.clear(context)
36+
assertNull(TestUserEmailOverride.get(context))
37+
}
38+
39+
@Test
40+
fun `blank emails are treated as no override`() {
41+
TestUserEmailOverride.set(context, " ")
42+
assertNull(TestUserEmailOverride.get(context))
43+
}
44+
}

0 commit comments

Comments
 (0)