From 40608d8ac110dffa58f59110cc863135eeaa5d04 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 22 May 2024 19:18:20 +0200 Subject: [PATCH] Add test on VectorUnifiedPushMessagingReceiver --- .../unifiedpush/build.gradle.kts | 2 + .../unifiedpush/UnifiedPushGatewayResolver.kt | 4 +- .../unifiedpush/UnifiedPushParser.kt | 1 - .../VectorUnifiedPushMessagingReceiver.kt | 4 +- .../FakeUnifiedPushGatewayResolver.kt | 25 +++ .../FakeUnifiedPushNewGatewayHandler.kt | 25 +++ .../unifiedpush/UnifiedPushParserTest.kt | 3 +- .../VectorUnifiedPushMessagingReceiverTest.kt | 196 ++++++++++++++++++ 8 files changed, 252 insertions(+), 8 deletions(-) create mode 100644 libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/FakeUnifiedPushGatewayResolver.kt create mode 100644 libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/FakeUnifiedPushNewGatewayHandler.kt create mode 100644 libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/VectorUnifiedPushMessagingReceiverTest.kt diff --git a/libraries/pushproviders/unifiedpush/build.gradle.kts b/libraries/pushproviders/unifiedpush/build.gradle.kts index 42b950d2ca..d1c8cf5ee1 100644 --- a/libraries/pushproviders/unifiedpush/build.gradle.kts +++ b/libraries/pushproviders/unifiedpush/build.gradle.kts @@ -55,9 +55,11 @@ dependencies { testImplementation(libs.coroutines.test) testImplementation(libs.test.junit) + testImplementation(libs.test.robolectric) testImplementation(libs.test.truth) testImplementation(libs.test.turbine) testImplementation(projects.libraries.matrix.test) + testImplementation(projects.libraries.push.test) testImplementation(projects.libraries.pushstore.test) testImplementation(projects.tests.testutils) testImplementation(projects.services.toolbox.test) diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushGatewayResolver.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushGatewayResolver.kt index 707eb3fed4..c39c7ec066 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushGatewayResolver.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushGatewayResolver.kt @@ -32,8 +32,8 @@ interface UnifiedPushGatewayResolver { class DefaultUnifiedPushGatewayResolver @Inject constructor( private val unifiedPushApiFactory: UnifiedPushApiFactory, private val coroutineDispatchers: CoroutineDispatchers, -) { - suspend fun getGateway(endpoint: String): String { +) : UnifiedPushGatewayResolver { + override suspend fun getGateway(endpoint: String): String { val gateway = UnifiedPushConfig.DEFAULT_PUSH_GATEWAY_HTTP_URL try { val url = URL(endpoint) diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushParser.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushParser.kt index f68cd8542b..46c7c0f9bb 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushParser.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushParser.kt @@ -18,7 +18,6 @@ package io.element.android.libraries.pushproviders.unifiedpush import io.element.android.libraries.core.data.tryOrNull import io.element.android.libraries.pushproviders.api.PushData -import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json import javax.inject.Inject diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/VectorUnifiedPushMessagingReceiver.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/VectorUnifiedPushMessagingReceiver.kt index 8aa41bc250..41e82eab1c 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/VectorUnifiedPushMessagingReceiver.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/VectorUnifiedPushMessagingReceiver.kt @@ -24,7 +24,6 @@ import io.element.android.libraries.pushproviders.api.PushHandler import io.element.android.libraries.pushproviders.unifiedpush.registration.EndpointRegistrationHandler import io.element.android.libraries.pushproviders.unifiedpush.registration.RegistrationResult import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch import org.unifiedpush.android.connector.MessagingReceiver import timber.log.Timber @@ -40,8 +39,7 @@ class VectorUnifiedPushMessagingReceiver : MessagingReceiver() { @Inject lateinit var unifiedPushGatewayResolver: UnifiedPushGatewayResolver @Inject lateinit var newGatewayHandler: UnifiedPushNewGatewayHandler @Inject lateinit var endpointRegistrationHandler: EndpointRegistrationHandler - - private val coroutineScope = CoroutineScope(SupervisorJob()) + @Inject lateinit var coroutineScope: CoroutineScope override fun onReceive(context: Context, intent: Intent) { context.applicationContext.bindings().inject(this) diff --git a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/FakeUnifiedPushGatewayResolver.kt b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/FakeUnifiedPushGatewayResolver.kt new file mode 100644 index 0000000000..37b70fb438 --- /dev/null +++ b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/FakeUnifiedPushGatewayResolver.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.pushproviders.unifiedpush + +class FakeUnifiedPushGatewayResolver( + private val getGatewayResult: (String) -> String = { TODO() }, +) : UnifiedPushGatewayResolver { + override suspend fun getGateway(endpoint: String): String { + return getGatewayResult(endpoint) + } +} diff --git a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/FakeUnifiedPushNewGatewayHandler.kt b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/FakeUnifiedPushNewGatewayHandler.kt new file mode 100644 index 0000000000..4e84e5e198 --- /dev/null +++ b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/FakeUnifiedPushNewGatewayHandler.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.pushproviders.unifiedpush + +class FakeUnifiedPushNewGatewayHandler( + private val handleResult: suspend (String, String, String) -> Result = { _, _, _ -> TODO() }, +) : UnifiedPushNewGatewayHandler { + override suspend fun handle(endpoint: String, pushGateway: String, clientSecret: String): Result { + return handleResult(endpoint, pushGateway, clientSecret) + } +} diff --git a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushParserTest.kt b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushParserTest.kt index bbccc92581..da710037c4 100644 --- a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushParserTest.kt +++ b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushParserTest.kt @@ -82,9 +82,8 @@ class UnifiedPushParserTest { } companion object { - private val UNIFIED_PUSH_DATA = + val UNIFIED_PUSH_DATA = "{\"notification\":{\"event_id\":\"$AN_EVENT_ID\",\"room_id\":\"$A_ROOM_ID\",\"counts\":{\"unread\":1},\"prio\":\"high\"}}" - // TODO Check client secret format? } } diff --git a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/VectorUnifiedPushMessagingReceiverTest.kt b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/VectorUnifiedPushMessagingReceiverTest.kt new file mode 100644 index 0000000000..6c14d1efee --- /dev/null +++ b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/VectorUnifiedPushMessagingReceiverTest.kt @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package io.element.android.libraries.pushproviders.unifiedpush + +import androidx.test.platform.app.InstrumentationRegistry +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.matrix.test.AN_EVENT_ID +import io.element.android.libraries.matrix.test.AN_EXCEPTION +import io.element.android.libraries.matrix.test.A_ROOM_ID +import io.element.android.libraries.matrix.test.A_SECRET +import io.element.android.libraries.push.test.test.FakePushHandler +import io.element.android.libraries.pushproviders.api.PushData +import io.element.android.libraries.pushproviders.api.PushHandler +import io.element.android.libraries.pushproviders.unifiedpush.registration.EndpointRegistrationHandler +import io.element.android.libraries.pushproviders.unifiedpush.registration.RegistrationResult +import io.element.android.tests.testutils.lambda.lambdaRecorder +import io.element.android.tests.testutils.lambda.value +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner + +@RunWith(RobolectricTestRunner::class) +class VectorUnifiedPushMessagingReceiverTest { + @Test + fun `onUnregistered does nothing`() = runTest { + val context = InstrumentationRegistry.getInstrumentation().context + val vectorUnifiedPushMessagingReceiver = createVectorUnifiedPushMessagingReceiver() + vectorUnifiedPushMessagingReceiver.onUnregistered(context, A_SECRET) + } + + @Test + fun `onRegistrationFailed does nothing`() = runTest { + val context = InstrumentationRegistry.getInstrumentation().context + val vectorUnifiedPushMessagingReceiver = createVectorUnifiedPushMessagingReceiver() + vectorUnifiedPushMessagingReceiver.onRegistrationFailed(context, A_SECRET) + } + + @Test + fun `onMessage valid invoke the push handler`() = runTest { + val context = InstrumentationRegistry.getInstrumentation().context + val pushHandlerResult = lambdaRecorder {} + val vectorUnifiedPushMessagingReceiver = createVectorUnifiedPushMessagingReceiver( + pushHandler = FakePushHandler( + handleResult = pushHandlerResult + ), + ) + vectorUnifiedPushMessagingReceiver.onMessage(context, UnifiedPushParserTest.UNIFIED_PUSH_DATA.toByteArray(), A_SECRET) + advanceUntilIdle() + pushHandlerResult.assertions() + .isCalledOnce() + .with( + value( + PushData( + eventId = AN_EVENT_ID, + roomId = A_ROOM_ID, + unread = 1, + clientSecret = A_SECRET + ) + ) + ) + } + + @Test + fun `onMessage invalid does not invoke the push handler`() = runTest { + val context = InstrumentationRegistry.getInstrumentation().context + val pushHandlerResult = lambdaRecorder {} + val vectorUnifiedPushMessagingReceiver = createVectorUnifiedPushMessagingReceiver( + pushHandler = FakePushHandler( + handleResult = pushHandlerResult + ), + ) + vectorUnifiedPushMessagingReceiver.onMessage(context, "".toByteArray(), A_SECRET) + advanceUntilIdle() + pushHandlerResult.assertions() + .isNeverCalled() + } + + @Test + fun `onNewEndpoint run the expected tasks`() = runTest { + val context = InstrumentationRegistry.getInstrumentation().context + val storePushGatewayResult = lambdaRecorder { _, _ -> } + val storeUpEndpointResult = lambdaRecorder { _, _ -> } + val unifiedPushStore = FakeUnifiedPushStore( + storePushGatewayResult = storePushGatewayResult, + storeUpEndpointResult = storeUpEndpointResult, + ) + val endpointRegistrationHandler = EndpointRegistrationHandler() + val handleResult = lambdaRecorder> { _, _, _ -> Result.success(Unit) } + val unifiedPushNewGatewayHandler = FakeUnifiedPushNewGatewayHandler( + handleResult = handleResult + ) + val vectorUnifiedPushMessagingReceiver = createVectorUnifiedPushMessagingReceiver( + unifiedPushStore = unifiedPushStore, + unifiedPushGatewayResolver = FakeUnifiedPushGatewayResolver( + getGatewayResult = { "aGateway" } + ), + endpointRegistrationHandler = endpointRegistrationHandler, + unifiedPushNewGatewayHandler = unifiedPushNewGatewayHandler, + ) + endpointRegistrationHandler.state.test { + vectorUnifiedPushMessagingReceiver.onNewEndpoint(context, "anEndpoint", A_SECRET) + advanceUntilIdle() + assertThat(awaitItem()).isEqualTo( + RegistrationResult( + clientSecret = A_SECRET, + result = Result.success(Unit) + ) + ) + } + storePushGatewayResult.assertions() + .isCalledOnce() + .with(value("aGateway"), value(A_SECRET)) + storeUpEndpointResult.assertions() + .isCalledOnce() + .with(value("anEndpoint"), value(A_SECRET)) + } + + @Test + fun `onNewEndpoint, if registration fails, the endpoint should not be stored`() = runTest { + val context = InstrumentationRegistry.getInstrumentation().context + val storePushGatewayResult = lambdaRecorder { _, _ -> } + val storeUpEndpointResult = lambdaRecorder { _, _ -> } + val unifiedPushStore = FakeUnifiedPushStore( + storePushGatewayResult = storePushGatewayResult, + storeUpEndpointResult = storeUpEndpointResult, + ) + val endpointRegistrationHandler = EndpointRegistrationHandler() + val handleResult = lambdaRecorder> { _, _, _ -> Result.failure(AN_EXCEPTION) } + val unifiedPushNewGatewayHandler = FakeUnifiedPushNewGatewayHandler( + handleResult = handleResult + ) + val vectorUnifiedPushMessagingReceiver = createVectorUnifiedPushMessagingReceiver( + unifiedPushStore = unifiedPushStore, + unifiedPushGatewayResolver = FakeUnifiedPushGatewayResolver( + getGatewayResult = { "aGateway" } + ), + endpointRegistrationHandler = endpointRegistrationHandler, + unifiedPushNewGatewayHandler = unifiedPushNewGatewayHandler, + ) + endpointRegistrationHandler.state.test { + vectorUnifiedPushMessagingReceiver.onNewEndpoint(context, "anEndpoint", A_SECRET) + advanceUntilIdle() + assertThat(awaitItem()).isEqualTo( + RegistrationResult( + clientSecret = A_SECRET, + result = Result.failure(AN_EXCEPTION) + ) + ) + } + storePushGatewayResult.assertions() + .isCalledOnce() + .with(value("aGateway"), value(A_SECRET)) + storeUpEndpointResult.assertions() + .isNeverCalled() + } + + private fun TestScope.createVectorUnifiedPushMessagingReceiver( + pushHandler: PushHandler = FakePushHandler(), + unifiedPushStore: UnifiedPushStore = FakeUnifiedPushStore(), + unifiedPushGatewayResolver: UnifiedPushGatewayResolver = FakeUnifiedPushGatewayResolver(), + unifiedPushNewGatewayHandler: UnifiedPushNewGatewayHandler = FakeUnifiedPushNewGatewayHandler(), + endpointRegistrationHandler: EndpointRegistrationHandler = EndpointRegistrationHandler(), + ): VectorUnifiedPushMessagingReceiver { + return VectorUnifiedPushMessagingReceiver().apply { + this.pushParser = UnifiedPushParser() + this.pushHandler = pushHandler + this.guardServiceStarter = NoopGuardServiceStarter() + this.unifiedPushStore = unifiedPushStore + this.unifiedPushGatewayResolver = unifiedPushGatewayResolver + this.newGatewayHandler = unifiedPushNewGatewayHandler + this.endpointRegistrationHandler = endpointRegistrationHandler + this.coroutineScope = this@createVectorUnifiedPushMessagingReceiver + } + } +}