From 8c43a585e332901286f3ed811d306caa0ea7f826 Mon Sep 17 00:00:00 2001 From: Josef Raska <6277721+jraska@users.noreply.github.com> Date: Sun, 27 Sep 2020 23:38:40 +0200 Subject: [PATCH 1/9] Add Firebase Push Integration Tests --- app/build.gradle | 2 + .../github/client/push/PushIntegrationTest.kt | 66 +++++++++++++++++++ .../github/client/push/PushServerClient.kt | 39 +++++++++++ .../github/client/push/PushServerDto.kt | 13 ++++ .../jraska/github/client/push/PushHandler.kt | 8 ++- .../client/firebase/FirebaseTestLabPlugin.kt | 5 +- 6 files changed, 129 insertions(+), 4 deletions(-) create mode 100644 app/src/androidTest/java/com/jraska/github/client/push/PushIntegrationTest.kt create mode 100644 app/src/androidTest/java/com/jraska/github/client/push/PushServerClient.kt create mode 100644 app/src/androidTest/java/com/jraska/github/client/push/PushServerDto.kt diff --git a/app/build.gradle b/app/build.gradle index 86345680..c81401a2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -37,6 +37,8 @@ android { targetCompatibility JavaVersion.VERSION_1_8 } + kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8.toString() + signingConfigs { debug { storeFile file("debug.keystore") diff --git a/app/src/androidTest/java/com/jraska/github/client/push/PushIntegrationTest.kt b/app/src/androidTest/java/com/jraska/github/client/push/PushIntegrationTest.kt new file mode 100644 index 00000000..f3b048f1 --- /dev/null +++ b/app/src/androidTest/java/com/jraska/github/client/push/PushIntegrationTest.kt @@ -0,0 +1,66 @@ +package com.jraska.github.client.push + +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.platform.app.InstrumentationRegistry +import com.google.firebase.iid.FirebaseInstanceId +import com.jraska.github.client.DeepLinkLaunchTest +import org.junit.Assume +import org.junit.Before +import org.junit.Test + +class PushIntegrationTest { + + lateinit var pushClient: PushServerClient + lateinit var thisDeviceToken: String + + @Before + fun setUp() { + pushClient = PushServerClient.create(apiKey()) + thisDeviceToken = FirebaseInstanceId.getInstance().token!! + } + + @Test + fun testPushIntegration_fromSettingsToAbout() { + DeepLinkLaunchTest.launchDeepLink("https://github.com/settings") + + sendDeepLinKMessage("https://github.com/about") + + awaitPush() + onView(withText("by Josef Raska")).check(matches(isDisplayed())) + } + + @Test + fun testPushIntegration_fromAboutToSettings() { + DeepLinkLaunchTest.launchDeepLink("https://github.com/about") + + sendDeepLinKMessage("https://github.com/settings") + + awaitPush() + onView(withText("Purchase")).check(matches(isDisplayed())) + } + + private fun sendDeepLinKMessage(deepLink: String) { + val messageToThisDevice = PushServerDto().apply { + ids.add(thisDeviceToken) + data["action"] = "launch_deep_link" + data["deepLink"] = deepLink + } + + pushClient.sendPush(messageToThisDevice).blockingAwait() + } + + private fun apiKey(): String { + val apiKey = InstrumentationRegistry.getArguments()["FCM_API_KEY"] + Assume.assumeTrue("FCM key not found in argument 'FCM_API_KEY', ignoring the test.", apiKey is String) + + return apiKey as String + } + + private fun awaitPush() { + // TODO: 27/09/2020 Idling resource on Push + Thread.sleep(3_000) + } +} diff --git a/app/src/androidTest/java/com/jraska/github/client/push/PushServerClient.kt b/app/src/androidTest/java/com/jraska/github/client/push/PushServerClient.kt new file mode 100644 index 00000000..d8d68345 --- /dev/null +++ b/app/src/androidTest/java/com/jraska/github/client/push/PushServerClient.kt @@ -0,0 +1,39 @@ +package com.jraska.github.client.push + +import io.reactivex.Completable +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import retrofit2.Retrofit +import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory +import retrofit2.converter.gson.GsonConverterFactory +import retrofit2.http.Body +import retrofit2.http.POST +import timber.log.Timber + +interface PushServerClient { + @POST("/fcm/send") + fun sendPush(@Body message: PushServerDto): Completable + + companion object { + fun create(authorizationToken: String): PushServerClient { + return Retrofit.Builder().validateEagerly(true) + .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) + .addConverterFactory(GsonConverterFactory.create()) + .baseUrl("https://fcm.googleapis.com") + .client( + OkHttpClient.Builder() + .addInterceptor { chain -> + chain.proceed( + chain.request().newBuilder() + .addHeader("Authorization", "key=$authorizationToken") + .build() + ) + } + .addInterceptor(HttpLoggingInterceptor { Timber.d(it) }.setLevel(HttpLoggingInterceptor.Level.BASIC)) + .build() + ) + .build() + .create(PushServerClient::class.java) + } + } +} diff --git a/app/src/androidTest/java/com/jraska/github/client/push/PushServerDto.kt b/app/src/androidTest/java/com/jraska/github/client/push/PushServerDto.kt new file mode 100644 index 00000000..71325aa1 --- /dev/null +++ b/app/src/androidTest/java/com/jraska/github/client/push/PushServerDto.kt @@ -0,0 +1,13 @@ +package com.jraska.github.client.push + +import com.google.gson.annotations.SerializedName + +class PushServerDto { + @SerializedName("registration_ids") + val ids = mutableListOf() + + @SerializedName("data") + val data = mutableMapOf() +} + + diff --git a/feature/push/src/main/java/com/jraska/github/client/push/PushHandler.kt b/feature/push/src/main/java/com/jraska/github/client/push/PushHandler.kt index d9fb3ad3..218c2991 100644 --- a/feature/push/src/main/java/com/jraska/github/client/push/PushHandler.kt +++ b/feature/push/src/main/java/com/jraska/github/client/push/PushHandler.kt @@ -1,20 +1,22 @@ package com.jraska.github.client.push -import com.jraska.github.client.analytics.EventAnalytics import com.jraska.github.client.common.BooleanResult import timber.log.Timber import javax.inject.Inject import javax.inject.Provider class PushHandler @Inject internal constructor( - private val eventAnalytics: EventAnalytics, private val pushCommands: Map> ) { internal fun handlePush(action: PushAction): BooleanResult { Timber.v("Push received action: %s", action.name) - return handleInternal(action) + val result = handleInternal(action) + + Timber.d("Push result: %s, Action: %s", result, action) + + return result } private fun handleInternal(action: PushAction): BooleanResult { diff --git a/firebasePlugin/src/main/java/com/jraska/github/client/firebase/FirebaseTestLabPlugin.kt b/firebasePlugin/src/main/java/com/jraska/github/client/firebase/FirebaseTestLabPlugin.kt index 905d821d..59cacb3a 100644 --- a/firebasePlugin/src/main/java/com/jraska/github/client/firebase/FirebaseTestLabPlugin.kt +++ b/firebasePlugin/src/main/java/com/jraska/github/client/firebase/FirebaseTestLabPlugin.kt @@ -32,6 +32,8 @@ class FirebaseTestLabPlugin : Plugin { val device = "model=$deviceName,version=$androidVersion,locale=en,orientation=portrait" val resultDir = DateTimeFormatter.ISO_DATE_TIME.format(LocalDateTime.now()) + val fcmKey = System.getenv("FCM_API_KEY") + resultsFileToPull = "gs://test-lab-twsawhz0hy5am-h35y3vymzadax/$resultDir/$deviceName-$androidVersion-en-portrait/test_result_1.xml" it.commandLine = @@ -41,7 +43,8 @@ class FirebaseTestLabPlugin : Plugin { "--test $testApk " + "--device $device " + "--results-dir $resultDir " + - "--no-performance-metrics") + "--no-performance-metrics " + + "--environment-variables FCM_API_KEY=$fcmKey") .split(' ') it.dependsOn(project.tasks.named("assembleDebugAndroidTest")) From 68c8f2c42358b448fd7b2479386c0565a5446f69 Mon Sep 17 00:00:00 2001 From: Josef Raska <6277721+jraska@users.noreply.github.com> Date: Mon, 28 Sep 2020 00:26:29 +0200 Subject: [PATCH 2/9] Rename package for push to run later --- .../jraska/github/client/{push => xpush}/PushIntegrationTest.kt | 2 +- .../jraska/github/client/{push => xpush}/PushServerClient.kt | 2 +- .../com/jraska/github/client/{push => xpush}/PushServerDto.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename app/src/androidTest/java/com/jraska/github/client/{push => xpush}/PushIntegrationTest.kt (97%) rename app/src/androidTest/java/com/jraska/github/client/{push => xpush}/PushServerClient.kt (96%) rename app/src/androidTest/java/com/jraska/github/client/{push => xpush}/PushServerDto.kt (85%) diff --git a/app/src/androidTest/java/com/jraska/github/client/push/PushIntegrationTest.kt b/app/src/androidTest/java/com/jraska/github/client/xpush/PushIntegrationTest.kt similarity index 97% rename from app/src/androidTest/java/com/jraska/github/client/push/PushIntegrationTest.kt rename to app/src/androidTest/java/com/jraska/github/client/xpush/PushIntegrationTest.kt index f3b048f1..9da155d2 100644 --- a/app/src/androidTest/java/com/jraska/github/client/push/PushIntegrationTest.kt +++ b/app/src/androidTest/java/com/jraska/github/client/xpush/PushIntegrationTest.kt @@ -1,4 +1,4 @@ -package com.jraska.github.client.push +package com.jraska.github.client.xpush import androidx.test.espresso.Espresso.onView import androidx.test.espresso.assertion.ViewAssertions.matches diff --git a/app/src/androidTest/java/com/jraska/github/client/push/PushServerClient.kt b/app/src/androidTest/java/com/jraska/github/client/xpush/PushServerClient.kt similarity index 96% rename from app/src/androidTest/java/com/jraska/github/client/push/PushServerClient.kt rename to app/src/androidTest/java/com/jraska/github/client/xpush/PushServerClient.kt index d8d68345..68347ee1 100644 --- a/app/src/androidTest/java/com/jraska/github/client/push/PushServerClient.kt +++ b/app/src/androidTest/java/com/jraska/github/client/xpush/PushServerClient.kt @@ -1,4 +1,4 @@ -package com.jraska.github.client.push +package com.jraska.github.client.xpush import io.reactivex.Completable import okhttp3.OkHttpClient diff --git a/app/src/androidTest/java/com/jraska/github/client/push/PushServerDto.kt b/app/src/androidTest/java/com/jraska/github/client/xpush/PushServerDto.kt similarity index 85% rename from app/src/androidTest/java/com/jraska/github/client/push/PushServerDto.kt rename to app/src/androidTest/java/com/jraska/github/client/xpush/PushServerDto.kt index 71325aa1..e9458ed4 100644 --- a/app/src/androidTest/java/com/jraska/github/client/push/PushServerDto.kt +++ b/app/src/androidTest/java/com/jraska/github/client/xpush/PushServerDto.kt @@ -1,4 +1,4 @@ -package com.jraska.github.client.push +package com.jraska.github.client.xpush import com.google.gson.annotations.SerializedName From 198545fbbe8bbdb2894c7a1acc6e9ca0b0e455ec Mon Sep 17 00:00:00 2001 From: Josef Raska <6277721+jraska@users.noreply.github.com> Date: Mon, 28 Sep 2020 00:26:47 +0200 Subject: [PATCH 3/9] Try to wait longer --- .../java/com/jraska/github/client/xpush/PushIntegrationTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/androidTest/java/com/jraska/github/client/xpush/PushIntegrationTest.kt b/app/src/androidTest/java/com/jraska/github/client/xpush/PushIntegrationTest.kt index 9da155d2..ba09e778 100644 --- a/app/src/androidTest/java/com/jraska/github/client/xpush/PushIntegrationTest.kt +++ b/app/src/androidTest/java/com/jraska/github/client/xpush/PushIntegrationTest.kt @@ -61,6 +61,6 @@ class PushIntegrationTest { private fun awaitPush() { // TODO: 27/09/2020 Idling resource on Push - Thread.sleep(3_000) + Thread.sleep(10_000) } } From 53efaf572c761226f8dc57678bd9ca8931ce04e6 Mon Sep 17 00:00:00 2001 From: Josef Raska <6277721+jraska@users.noreply.github.com> Date: Mon, 28 Sep 2020 23:20:45 +0200 Subject: [PATCH 4/9] Add decoration for service modesl and use idling resource for push. --- .../client/DecoratedServiceModelFactory.kt | 15 ++++++ .../com/jraska/github/client/TestUITestApp.kt | 8 ++++ .../client/xpush/PushIntegrationTest.kt | 46 ++++++++++++++++++- .../github/client/push/PushHandleModel.kt | 20 ++------ .../github/client/push/PushHandleModelImpl.kt | 24 ++++++++++ .../jraska/github/client/push/PushModule.kt | 3 ++ 6 files changed, 97 insertions(+), 19 deletions(-) create mode 100644 app/src/androidTest/java/com/jraska/github/client/DecoratedServiceModelFactory.kt create mode 100644 feature/push/src/main/java/com/jraska/github/client/push/PushHandleModelImpl.kt diff --git a/app/src/androidTest/java/com/jraska/github/client/DecoratedServiceModelFactory.kt b/app/src/androidTest/java/com/jraska/github/client/DecoratedServiceModelFactory.kt new file mode 100644 index 00000000..1d0d7ca4 --- /dev/null +++ b/app/src/androidTest/java/com/jraska/github/client/DecoratedServiceModelFactory.kt @@ -0,0 +1,15 @@ +package com.jraska.github.client + +import com.jraska.github.client.core.android.ServiceModel + +class DecoratedServiceModelFactory(val productionFactory: ServiceModel.Factory) : ServiceModel.Factory { + var decorator: Decorator? = null + + override fun create(modelClass: Class): T { + return decorator?.create(modelClass, productionFactory) ?: productionFactory.create(modelClass) + } + + interface Decorator { + fun create(modelClass: Class, productionFactory: ServiceModel.Factory): T + } +} diff --git a/app/src/androidTest/java/com/jraska/github/client/TestUITestApp.kt b/app/src/androidTest/java/com/jraska/github/client/TestUITestApp.kt index 68350ddd..e8dac332 100644 --- a/app/src/androidTest/java/com/jraska/github/client/TestUITestApp.kt +++ b/app/src/androidTest/java/com/jraska/github/client/TestUITestApp.kt @@ -1,10 +1,18 @@ package com.jraska.github.client import androidx.test.platform.app.InstrumentationRegistry +import com.jraska.github.client.core.android.ServiceModel import com.jraska.github.client.http.ReplayHttpComponent class TestUITestApp : GitHubClientApp() { val coreComponent = FakeCoreComponent() + val decoratedServiceFactory by lazy { + DecoratedServiceModelFactory(super.serviceModelFactory()) + } + + override fun serviceModelFactory(): ServiceModel.Factory { + return decoratedServiceFactory + } override fun retrofit(): HasRetrofit { return ReplayHttpComponent.create() diff --git a/app/src/androidTest/java/com/jraska/github/client/xpush/PushIntegrationTest.kt b/app/src/androidTest/java/com/jraska/github/client/xpush/PushIntegrationTest.kt index ba09e778..0944464a 100644 --- a/app/src/androidTest/java/com/jraska/github/client/xpush/PushIntegrationTest.kt +++ b/app/src/androidTest/java/com/jraska/github/client/xpush/PushIntegrationTest.kt @@ -1,12 +1,21 @@ package com.jraska.github.client.xpush import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.IdlingRegistry +import androidx.test.espresso.IdlingResource import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.idling.CountingIdlingResource import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.platform.app.InstrumentationRegistry import com.google.firebase.iid.FirebaseInstanceId +import com.google.firebase.messaging.RemoteMessage +import com.jraska.github.client.DecoratedServiceModelFactory import com.jraska.github.client.DeepLinkLaunchTest +import com.jraska.github.client.TestUITestApp +import com.jraska.github.client.core.android.ServiceModel +import com.jraska.github.client.push.PushHandleModel +import org.junit.After import org.junit.Assume import org.junit.Before import org.junit.Test @@ -16,10 +25,25 @@ class PushIntegrationTest { lateinit var pushClient: PushServerClient lateinit var thisDeviceToken: String + @Suppress("UNCHECKED_CAST") // We want to fail on unchecked casting @Before fun setUp() { pushClient = PushServerClient.create(apiKey()) thisDeviceToken = FirebaseInstanceId.getInstance().token!! + + TestUITestApp.get().decoratedServiceFactory.decorator = object : DecoratedServiceModelFactory.Decorator { + override fun create(modelClass: Class, productionFactory: ServiceModel.Factory): T { + return TestPushHandleModel(productionFactory.create(modelClass) as PushHandleModel) as T + } + } + + IdlingRegistry.getInstance().register(PushAwaitIdlingResource.idlingResource()) + } + + @After + fun tearDown() { + IdlingRegistry.getInstance().unregister(PushAwaitIdlingResource.idlingResource()) + TestUITestApp.get().decoratedServiceFactory.decorator = null } @Test @@ -60,7 +84,25 @@ class PushIntegrationTest { } private fun awaitPush() { - // TODO: 27/09/2020 Idling resource on Push - Thread.sleep(10_000) + PushAwaitIdlingResource.waitForPush() + } + + object PushAwaitIdlingResource { + private val countingIdlingResource = CountingIdlingResource("Push Await") + + fun idlingResource(): IdlingResource = countingIdlingResource + + fun waitForPush() = countingIdlingResource.increment() + + fun onPush() = countingIdlingResource.decrement() + } + + class TestPushHandleModel( + val productionModel: PushHandleModel, + ) : PushHandleModel by productionModel { + override fun onMessageReceived(message: RemoteMessage) { + productionModel.onMessageReceived(message) + PushAwaitIdlingResource.onPush() + } } } diff --git a/feature/push/src/main/java/com/jraska/github/client/push/PushHandleModel.kt b/feature/push/src/main/java/com/jraska/github/client/push/PushHandleModel.kt index 9e72d6ee..60a37e10 100644 --- a/feature/push/src/main/java/com/jraska/github/client/push/PushHandleModel.kt +++ b/feature/push/src/main/java/com/jraska/github/client/push/PushHandleModel.kt @@ -2,23 +2,9 @@ package com.jraska.github.client.push import com.google.firebase.messaging.RemoteMessage import com.jraska.github.client.core.android.ServiceModel -import javax.inject.Inject -internal class PushHandleModel @Inject constructor( - private val pushHandler: PushHandler, - private val analytics: PushAnalytics, - private val tokenSynchronizer: PushTokenSynchronizer -) : ServiceModel { - fun onMessageReceived(remoteMessage: RemoteMessage) { - val action = RemoteMessageToActionConverter.convert(remoteMessage) +interface PushHandleModel : ServiceModel { + fun onMessageReceived(message: RemoteMessage) - val pushResult = pushHandler.handlePush(action) - - analytics.onPushHandled(action, pushResult) - } - - fun onNewToken(token: String) { - analytics.onTokenRefresh() - tokenSynchronizer.onTokenRefresh(token) - } + fun onNewToken(token: String) } diff --git a/feature/push/src/main/java/com/jraska/github/client/push/PushHandleModelImpl.kt b/feature/push/src/main/java/com/jraska/github/client/push/PushHandleModelImpl.kt new file mode 100644 index 00000000..dbab7641 --- /dev/null +++ b/feature/push/src/main/java/com/jraska/github/client/push/PushHandleModelImpl.kt @@ -0,0 +1,24 @@ +package com.jraska.github.client.push + +import com.google.firebase.messaging.RemoteMessage +import com.jraska.github.client.core.android.ServiceModel +import javax.inject.Inject + +internal class PushHandleModelImpl @Inject constructor( + private val pushHandler: PushHandler, + private val analytics: PushAnalytics, + private val tokenSynchronizer: PushTokenSynchronizer +) : PushHandleModel, ServiceModel { + override fun onMessageReceived(message: RemoteMessage) { + val action = RemoteMessageToActionConverter.convert(message) + + val pushResult = pushHandler.handlePush(action) + + analytics.onPushHandled(action, pushResult) + } + + override fun onNewToken(token: String) { + analytics.onTokenRefresh() + tokenSynchronizer.onTokenRefresh(token) + } +} diff --git a/feature/push/src/main/java/com/jraska/github/client/push/PushModule.kt b/feature/push/src/main/java/com/jraska/github/client/push/PushModule.kt index 78bc0f31..8f8a6de2 100644 --- a/feature/push/src/main/java/com/jraska/github/client/push/PushModule.kt +++ b/feature/push/src/main/java/com/jraska/github/client/push/PushModule.kt @@ -37,6 +37,9 @@ object PushModule { @ClassKey(PushHandleModel::class) internal abstract fun bindServiceModel(pushHandleModel: PushHandleModel): ServiceModel + @Binds + internal abstract fun bindPushModel(pushHandleModel: PushHandleModelImpl): PushHandleModel + @Binds @IntoMap @StringKey("refresh_config") From 12b27006a26771f46d5b80fcd9fd69af968b8218 Mon Sep 17 00:00:00 2001 From: Josef Raska <6277721+jraska@users.noreply.github.com> Date: Mon, 28 Sep 2020 23:27:32 +0200 Subject: [PATCH 5/9] Move the Push await to rule --- .../github/client/xpush/PushAwaitRule.kt | 52 +++++++++++++++++++ .../client/xpush/PushIntegrationTest.kt | 48 ++--------------- 2 files changed, 57 insertions(+), 43 deletions(-) create mode 100644 app/src/androidTest/java/com/jraska/github/client/xpush/PushAwaitRule.kt diff --git a/app/src/androidTest/java/com/jraska/github/client/xpush/PushAwaitRule.kt b/app/src/androidTest/java/com/jraska/github/client/xpush/PushAwaitRule.kt new file mode 100644 index 00000000..33d3b9f9 --- /dev/null +++ b/app/src/androidTest/java/com/jraska/github/client/xpush/PushAwaitRule.kt @@ -0,0 +1,52 @@ +package com.jraska.github.client.xpush + +import androidx.test.espresso.IdlingRegistry +import androidx.test.espresso.IdlingResource +import androidx.test.espresso.idling.CountingIdlingResource +import com.google.firebase.messaging.RemoteMessage +import com.jraska.github.client.DecoratedServiceModelFactory +import com.jraska.github.client.TestUITestApp +import com.jraska.github.client.core.android.ServiceModel +import com.jraska.github.client.push.PushHandleModel +import org.junit.rules.ExternalResource + +class PushAwaitRule : ExternalResource() { + override fun before() { + TestUITestApp.get().decoratedServiceFactory.decorator = object : DecoratedServiceModelFactory.Decorator { + override fun create(modelClass: Class, productionFactory: ServiceModel.Factory): T { + @Suppress("UNCHECKED_CAST") // We want to fail if someoen creates other models during the test + return TestPushHandleModel(productionFactory.create(modelClass) as PushHandleModel) as T + } + } + + IdlingRegistry.getInstance().register(PushAwaitIdlingResource.idlingResource()) + } + + override fun after() { + IdlingRegistry.getInstance().unregister(PushAwaitIdlingResource.idlingResource()) + TestUITestApp.get().decoratedServiceFactory.decorator = null + } + + fun waitForPush() { + PushAwaitIdlingResource.waitForPush() + } + + private object PushAwaitIdlingResource { + private val countingIdlingResource = CountingIdlingResource("Push Await") + + fun idlingResource(): IdlingResource = countingIdlingResource + + fun waitForPush() = countingIdlingResource.increment() + + fun onPush() = countingIdlingResource.decrement() + } + + private class TestPushHandleModel( + val productionModel: PushHandleModel, + ) : PushHandleModel by productionModel { + override fun onMessageReceived(message: RemoteMessage) { + productionModel.onMessageReceived(message) + PushAwaitIdlingResource.onPush() + } + } +} diff --git a/app/src/androidTest/java/com/jraska/github/client/xpush/PushIntegrationTest.kt b/app/src/androidTest/java/com/jraska/github/client/xpush/PushIntegrationTest.kt index 0944464a..7ab7f7ea 100644 --- a/app/src/androidTest/java/com/jraska/github/client/xpush/PushIntegrationTest.kt +++ b/app/src/androidTest/java/com/jraska/github/client/xpush/PushIntegrationTest.kt @@ -1,23 +1,15 @@ package com.jraska.github.client.xpush import androidx.test.espresso.Espresso.onView -import androidx.test.espresso.IdlingRegistry -import androidx.test.espresso.IdlingResource import androidx.test.espresso.assertion.ViewAssertions.matches -import androidx.test.espresso.idling.CountingIdlingResource import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.platform.app.InstrumentationRegistry import com.google.firebase.iid.FirebaseInstanceId -import com.google.firebase.messaging.RemoteMessage -import com.jraska.github.client.DecoratedServiceModelFactory import com.jraska.github.client.DeepLinkLaunchTest -import com.jraska.github.client.TestUITestApp -import com.jraska.github.client.core.android.ServiceModel -import com.jraska.github.client.push.PushHandleModel -import org.junit.After import org.junit.Assume import org.junit.Before +import org.junit.Rule import org.junit.Test class PushIntegrationTest { @@ -25,25 +17,13 @@ class PushIntegrationTest { lateinit var pushClient: PushServerClient lateinit var thisDeviceToken: String - @Suppress("UNCHECKED_CAST") // We want to fail on unchecked casting + @get:Rule + val pushRule = PushAwaitRule() + @Before fun setUp() { pushClient = PushServerClient.create(apiKey()) thisDeviceToken = FirebaseInstanceId.getInstance().token!! - - TestUITestApp.get().decoratedServiceFactory.decorator = object : DecoratedServiceModelFactory.Decorator { - override fun create(modelClass: Class, productionFactory: ServiceModel.Factory): T { - return TestPushHandleModel(productionFactory.create(modelClass) as PushHandleModel) as T - } - } - - IdlingRegistry.getInstance().register(PushAwaitIdlingResource.idlingResource()) - } - - @After - fun tearDown() { - IdlingRegistry.getInstance().unregister(PushAwaitIdlingResource.idlingResource()) - TestUITestApp.get().decoratedServiceFactory.decorator = null } @Test @@ -84,25 +64,7 @@ class PushIntegrationTest { } private fun awaitPush() { - PushAwaitIdlingResource.waitForPush() + pushRule.waitForPush() } - object PushAwaitIdlingResource { - private val countingIdlingResource = CountingIdlingResource("Push Await") - - fun idlingResource(): IdlingResource = countingIdlingResource - - fun waitForPush() = countingIdlingResource.increment() - - fun onPush() = countingIdlingResource.decrement() - } - - class TestPushHandleModel( - val productionModel: PushHandleModel, - ) : PushHandleModel by productionModel { - override fun onMessageReceived(message: RemoteMessage) { - productionModel.onMessageReceived(message) - PushAwaitIdlingResource.onPush() - } - } } From 33bfa046de091fc277e5f22bbdc7bf7f9a92e6d3 Mon Sep 17 00:00:00 2001 From: Josef Raska <6277721+jraska@users.noreply.github.com> Date: Mon, 28 Sep 2020 23:43:04 +0200 Subject: [PATCH 6/9] Make sure the test model is always created --- .../github/client/DecoratedServiceModelFactory.kt | 12 +++++++----- .../com/jraska/github/client/xpush/PushAwaitRule.kt | 13 +------------ 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/app/src/androidTest/java/com/jraska/github/client/DecoratedServiceModelFactory.kt b/app/src/androidTest/java/com/jraska/github/client/DecoratedServiceModelFactory.kt index 1d0d7ca4..e57343d8 100644 --- a/app/src/androidTest/java/com/jraska/github/client/DecoratedServiceModelFactory.kt +++ b/app/src/androidTest/java/com/jraska/github/client/DecoratedServiceModelFactory.kt @@ -1,15 +1,17 @@ package com.jraska.github.client import com.jraska.github.client.core.android.ServiceModel +import com.jraska.github.client.push.PushHandleModel +import com.jraska.github.client.xpush.PushAwaitRule class DecoratedServiceModelFactory(val productionFactory: ServiceModel.Factory) : ServiceModel.Factory { - var decorator: Decorator? = null + @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T { - return decorator?.create(modelClass, productionFactory) ?: productionFactory.create(modelClass) - } + if (modelClass == PushHandleModel::class.java) { + return PushAwaitRule.TestPushHandleModel(productionFactory.create(modelClass)) as T + } - interface Decorator { - fun create(modelClass: Class, productionFactory: ServiceModel.Factory): T + return productionFactory.create(modelClass) } } diff --git a/app/src/androidTest/java/com/jraska/github/client/xpush/PushAwaitRule.kt b/app/src/androidTest/java/com/jraska/github/client/xpush/PushAwaitRule.kt index 33d3b9f9..2136dfb4 100644 --- a/app/src/androidTest/java/com/jraska/github/client/xpush/PushAwaitRule.kt +++ b/app/src/androidTest/java/com/jraska/github/client/xpush/PushAwaitRule.kt @@ -4,27 +4,16 @@ import androidx.test.espresso.IdlingRegistry import androidx.test.espresso.IdlingResource import androidx.test.espresso.idling.CountingIdlingResource import com.google.firebase.messaging.RemoteMessage -import com.jraska.github.client.DecoratedServiceModelFactory -import com.jraska.github.client.TestUITestApp -import com.jraska.github.client.core.android.ServiceModel import com.jraska.github.client.push.PushHandleModel import org.junit.rules.ExternalResource class PushAwaitRule : ExternalResource() { override fun before() { - TestUITestApp.get().decoratedServiceFactory.decorator = object : DecoratedServiceModelFactory.Decorator { - override fun create(modelClass: Class, productionFactory: ServiceModel.Factory): T { - @Suppress("UNCHECKED_CAST") // We want to fail if someoen creates other models during the test - return TestPushHandleModel(productionFactory.create(modelClass) as PushHandleModel) as T - } - } - IdlingRegistry.getInstance().register(PushAwaitIdlingResource.idlingResource()) } override fun after() { IdlingRegistry.getInstance().unregister(PushAwaitIdlingResource.idlingResource()) - TestUITestApp.get().decoratedServiceFactory.decorator = null } fun waitForPush() { @@ -41,7 +30,7 @@ class PushAwaitRule : ExternalResource() { fun onPush() = countingIdlingResource.decrement() } - private class TestPushHandleModel( + class TestPushHandleModel( val productionModel: PushHandleModel, ) : PushHandleModel by productionModel { override fun onMessageReceived(message: RemoteMessage) { From a27ca1f0458a4cd2de8ca6b8f251fc325fc9b905 Mon Sep 17 00:00:00 2001 From: Josef Raska <6277721+jraska@users.noreply.github.com> Date: Mon, 28 Sep 2020 23:50:32 +0200 Subject: [PATCH 7/9] Minor tweaks --- .../com/jraska/github/client/DecoratedServiceModelFactory.kt | 5 +++-- .../java/com/jraska/github/client/xpush/PushServerDto.kt | 2 -- .../main/java/com/jraska/github/client/push/PushHandler.kt | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/app/src/androidTest/java/com/jraska/github/client/DecoratedServiceModelFactory.kt b/app/src/androidTest/java/com/jraska/github/client/DecoratedServiceModelFactory.kt index e57343d8..a9ea1993 100644 --- a/app/src/androidTest/java/com/jraska/github/client/DecoratedServiceModelFactory.kt +++ b/app/src/androidTest/java/com/jraska/github/client/DecoratedServiceModelFactory.kt @@ -4,14 +4,15 @@ import com.jraska.github.client.core.android.ServiceModel import com.jraska.github.client.push.PushHandleModel import com.jraska.github.client.xpush.PushAwaitRule -class DecoratedServiceModelFactory(val productionFactory: ServiceModel.Factory) : ServiceModel.Factory { +class DecoratedServiceModelFactory( + private val productionFactory: ServiceModel.Factory +) : ServiceModel.Factory { @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T { if (modelClass == PushHandleModel::class.java) { return PushAwaitRule.TestPushHandleModel(productionFactory.create(modelClass)) as T } - return productionFactory.create(modelClass) } } diff --git a/app/src/androidTest/java/com/jraska/github/client/xpush/PushServerDto.kt b/app/src/androidTest/java/com/jraska/github/client/xpush/PushServerDto.kt index e9458ed4..351bc086 100644 --- a/app/src/androidTest/java/com/jraska/github/client/xpush/PushServerDto.kt +++ b/app/src/androidTest/java/com/jraska/github/client/xpush/PushServerDto.kt @@ -9,5 +9,3 @@ class PushServerDto { @SerializedName("data") val data = mutableMapOf() } - - diff --git a/feature/push/src/main/java/com/jraska/github/client/push/PushHandler.kt b/feature/push/src/main/java/com/jraska/github/client/push/PushHandler.kt index 218c2991..ee654b4b 100644 --- a/feature/push/src/main/java/com/jraska/github/client/push/PushHandler.kt +++ b/feature/push/src/main/java/com/jraska/github/client/push/PushHandler.kt @@ -10,7 +10,7 @@ class PushHandler @Inject internal constructor( ) { internal fun handlePush(action: PushAction): BooleanResult { - Timber.v("Push received action: %s", action.name) + Timber.d("Push received action: %s", action.name) val result = handleInternal(action) From 6469b9d652a17003b00120f6a905e83ac3a25c72 Mon Sep 17 00:00:00 2001 From: Josef Raska <6277721+jraska@users.noreply.github.com> Date: Tue, 29 Sep 2020 00:17:22 +0200 Subject: [PATCH 8/9] Run the decrement on UI thread --- .../java/com/jraska/github/client/xpush/PushAwaitRule.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/src/androidTest/java/com/jraska/github/client/xpush/PushAwaitRule.kt b/app/src/androidTest/java/com/jraska/github/client/xpush/PushAwaitRule.kt index 2136dfb4..bb026634 100644 --- a/app/src/androidTest/java/com/jraska/github/client/xpush/PushAwaitRule.kt +++ b/app/src/androidTest/java/com/jraska/github/client/xpush/PushAwaitRule.kt @@ -3,6 +3,7 @@ package com.jraska.github.client.xpush import androidx.test.espresso.IdlingRegistry import androidx.test.espresso.IdlingResource import androidx.test.espresso.idling.CountingIdlingResource +import androidx.test.internal.runner.junit4.statement.UiThreadStatement import com.google.firebase.messaging.RemoteMessage import com.jraska.github.client.push.PushHandleModel import org.junit.rules.ExternalResource @@ -27,7 +28,12 @@ class PushAwaitRule : ExternalResource() { fun waitForPush() = countingIdlingResource.increment() - fun onPush() = countingIdlingResource.decrement() + fun onPush() { + // Doing this to make sure anything scheduled on UI thread will run before this + UiThreadStatement.runOnUiThread { + countingIdlingResource.decrement() + } + } } class TestPushHandleModel( From bc55bccfe534ab9d9040055327da65cac5435daf Mon Sep 17 00:00:00 2001 From: Josef Raska <6277721+jraska@users.noreply.github.com> Date: Tue, 29 Sep 2020 00:38:27 +0200 Subject: [PATCH 9/9] Remvoe usage of the deprecated `getToken` method --- .../github/client/xpush/PushIntegrationTest.kt | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/app/src/androidTest/java/com/jraska/github/client/xpush/PushIntegrationTest.kt b/app/src/androidTest/java/com/jraska/github/client/xpush/PushIntegrationTest.kt index 7ab7f7ea..b93cf857 100644 --- a/app/src/androidTest/java/com/jraska/github/client/xpush/PushIntegrationTest.kt +++ b/app/src/androidTest/java/com/jraska/github/client/xpush/PushIntegrationTest.kt @@ -5,6 +5,7 @@ import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.platform.app.InstrumentationRegistry +import com.google.android.gms.tasks.Tasks import com.google.firebase.iid.FirebaseInstanceId import com.jraska.github.client.DeepLinkLaunchTest import org.junit.Assume @@ -23,14 +24,16 @@ class PushIntegrationTest { @Before fun setUp() { pushClient = PushServerClient.create(apiKey()) - thisDeviceToken = FirebaseInstanceId.getInstance().token!! + + val instanceIdTask = FirebaseInstanceId.getInstance().instanceId + thisDeviceToken = Tasks.await(instanceIdTask).token } @Test fun testPushIntegration_fromSettingsToAbout() { DeepLinkLaunchTest.launchDeepLink("https://github.com/settings") - sendDeepLinKMessage("https://github.com/about") + sendDeepLinkPush("https://github.com/about") awaitPush() onView(withText("by Josef Raska")).check(matches(isDisplayed())) @@ -40,13 +43,14 @@ class PushIntegrationTest { fun testPushIntegration_fromAboutToSettings() { DeepLinkLaunchTest.launchDeepLink("https://github.com/about") - sendDeepLinKMessage("https://github.com/settings") + sendDeepLinkPush("https://github.com/settings") awaitPush() onView(withText("Purchase")).check(matches(isDisplayed())) } - private fun sendDeepLinKMessage(deepLink: String) { + // See LaunchDeepLinkCommand to see how this is handled. + private fun sendDeepLinkPush(deepLink: String) { val messageToThisDevice = PushServerDto().apply { ids.add(thisDeviceToken) data["action"] = "launch_deep_link" @@ -66,5 +70,4 @@ class PushIntegrationTest { private fun awaitPush() { pushRule.waitForPush() } - }