Merge pull request #3388 from element-hq/feature/bma/pushEndpoint

Unified push endpoint: do not fallback to default endpoint in case of failure and add troubleshoot test.
This commit is contained in:
Benoit Marty
2024-09-04 18:19:34 +02:00
committed by GitHub
22 changed files with 589 additions and 129 deletions

View File

@@ -37,7 +37,7 @@ class NotificationTroubleshootCheckPermissionTest @Inject constructor(
private val permissionStateProvider: PermissionStateProvider,
private val sdkVersionProvider: BuildVersionSdkIntProvider,
private val permissionActions: PermissionActions,
private val stringProvider: StringProvider,
stringProvider: StringProvider,
) : NotificationTroubleshootTest {
override val order: Int = 0

View File

@@ -101,4 +101,32 @@ class NotificationTroubleshootCheckPermissionTestTest {
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Success)
}
}
@Test
fun `test NotificationTroubleshootCheckPermissionTest error and reset`() = runTest {
val permissionStateProvider = FakePermissionStateProvider(
permissionGranted = false
)
val actions = FakePermissionActions(
openSettingsAction = {
permissionStateProvider.setPermissionGranted()
}
)
val sut = NotificationTroubleshootCheckPermissionTest(
permissionStateProvider = permissionStateProvider,
sdkVersionProvider = FakeBuildVersionSdkIntProvider(sdkInt = Build.VERSION_CODES.TIRAMISU),
permissionActions = actions,
stringProvider = FakeStringProvider(),
)
launch {
sut.run(this)
}
sut.state.test {
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true))
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(true))
sut.reset()
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true))
}
}
}

View File

@@ -29,6 +29,7 @@ import io.element.android.libraries.pushproviders.api.CurrentUserPushConfig
import io.element.android.libraries.pushproviders.api.Distributor
import io.element.android.libraries.pushproviders.api.PushProvider
import io.element.android.libraries.pushproviders.test.FakePushProvider
import io.element.android.libraries.pushproviders.test.aCurrentUserPushConfig
import io.element.android.libraries.pushstore.api.UserPushStoreFactory
import io.element.android.libraries.pushstore.test.userpushstore.FakeUserPushStore
import io.element.android.libraries.pushstore.test.userpushstore.FakeUserPushStoreFactory
@@ -57,10 +58,7 @@ class DefaultPushServiceTest {
@Test
fun `test push ok`() = runTest {
val aConfig = CurrentUserPushConfig(
url = "aUrl",
pushKey = "aPushKey",
)
val aConfig = aCurrentUserPushConfig()
val testPushResult = lambdaRecorder<CurrentUserPushConfig, Unit> { }
val aPushProvider = FakePushProvider(
currentUserPushConfig = aConfig

View File

@@ -18,7 +18,7 @@ package io.element.android.libraries.push.impl.test
import io.element.android.appconfig.PushConfig
import io.element.android.libraries.push.impl.pushgateway.PushGatewayNotifyRequest
import io.element.android.libraries.pushproviders.api.CurrentUserPushConfig
import io.element.android.libraries.pushproviders.test.aCurrentUserPushConfig
import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.lambda.value
import kotlinx.coroutines.test.runTest
@@ -33,10 +33,7 @@ class DefaultTestPushTest {
executeResult = executeResult,
)
)
val aConfig = CurrentUserPushConfig(
url = "aUrl",
pushKey = "aPushKey",
)
val aConfig = aCurrentUserPushConfig()
defaultTestPush.execute(aConfig)
executeResult.assertions()
.isCalledOnce()

View File

@@ -58,6 +58,8 @@ class CurrentPushProviderTestTest {
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
val lastItem = awaitItem()
assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(false))
sut.reset()
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true))
}
}
}

View File

@@ -61,6 +61,8 @@ class NotificationTestTest {
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.WaitingForUser)
assertThat(awaitItem().status).isInstanceOf(NotificationTroubleshootTestState.Status.Failure::class.java)
sut.reset()
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true))
}
}

View File

@@ -71,6 +71,8 @@ class PushLoopbackTestTest {
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
val lastItem = awaitItem()
assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(false))
sut.reset()
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true))
}
}

View File

@@ -40,6 +40,8 @@ class PushProvidersTestTest {
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
val lastItem = awaitItem()
assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(false))
sut.reset()
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true))
}
}

View File

@@ -59,6 +59,8 @@ class FirebaseAvailabilityTestTest {
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
val lastItem = awaitItem()
assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(false))
sut.reset()
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(false))
}
}

View File

@@ -77,6 +77,31 @@ class FirebaseTokenTestTest {
}
}
@Test
fun `test FirebaseTokenTest error and reset`() = runTest {
val firebaseStore = InMemoryFirebaseStore(null)
val sut = FirebaseTokenTest(
firebaseStore = firebaseStore,
firebaseTroubleshooter = FakeFirebaseTroubleshooter(
troubleShootResult = {
firebaseStore.storeFcmToken(FAKE_TOKEN)
Result.success(Unit)
}
),
stringProvider = FakeStringProvider(),
)
launch {
sut.run(this)
}
sut.state.test {
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(false))
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(true))
sut.reset()
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(false))
}
}
@Test
fun `test FirebaseTokenTest isRelevant`() {
val sut = FirebaseTokenTest(

View File

@@ -0,0 +1,27 @@
/*
* 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
*
* https://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.test
import io.element.android.libraries.pushproviders.api.CurrentUserPushConfig
fun aCurrentUserPushConfig(
url: String = "aUrl",
pushKey: String = "aPushKey",
) = CurrentUserPushConfig(
url = url,
pushKey = pushKey,
)

View File

@@ -60,6 +60,7 @@ dependencies {
testImplementation(libs.test.turbine)
testImplementation(projects.libraries.matrix.test)
testImplementation(projects.libraries.push.test)
testImplementation(projects.libraries.pushproviders.test)
testImplementation(projects.libraries.pushstore.test)
testImplementation(projects.tests.testutils)
testImplementation(projects.services.toolbox.test)

View File

@@ -0,0 +1,47 @@
/*
* 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
*
* https://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
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.pushproviders.api.CurrentUserPushConfig
import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret
import io.element.android.services.appnavstate.api.AppNavigationStateService
import io.element.android.services.appnavstate.api.currentSessionId
import javax.inject.Inject
interface UnifiedPushCurrentUserPushConfigProvider {
suspend fun provide(): CurrentUserPushConfig?
}
@ContributesBinding(AppScope::class)
class DefaultUnifiedPushCurrentUserPushConfigProvider @Inject constructor(
private val pushClientSecret: PushClientSecret,
private val unifiedPushStore: UnifiedPushStore,
private val appNavigationStateService: AppNavigationStateService,
) : UnifiedPushCurrentUserPushConfigProvider {
override suspend fun provide(): CurrentUserPushConfig? {
val currentSession = appNavigationStateService.appNavigationState.value.navigationState.currentSessionId() ?: return null
val clientSecret = pushClientSecret.getSecretForUser(currentSession)
val url = unifiedPushStore.getPushGateway(clientSecret) ?: return null
val pushKey = unifiedPushStore.getEndpoint(clientSecret) ?: return null
return CurrentUserPushConfig(
url = url,
pushKey = pushKey,
)
}
}

View File

@@ -18,6 +18,7 @@ package io.element.android.libraries.pushproviders.unifiedpush
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.core.data.tryOrNull
import io.element.android.libraries.di.AppScope
import kotlinx.coroutines.withContext
import timber.log.Timber
@@ -33,30 +34,37 @@ class DefaultUnifiedPushGatewayResolver @Inject constructor(
private val unifiedPushApiFactory: UnifiedPushApiFactory,
private val coroutineDispatchers: CoroutineDispatchers,
) : UnifiedPushGatewayResolver {
private val logger = Timber.tag("DefaultUnifiedPushGatewayResolver")
override suspend fun getGateway(endpoint: String): String {
val gateway = UnifiedPushConfig.DEFAULT_PUSH_GATEWAY_HTTP_URL
try {
val url = URL(endpoint)
val url = tryOrNull(
onError = { logger.d(it, "Cannot parse endpoint as an URL") }
) {
URL(endpoint)
}
return if (url == null) {
logger.d("Using default gateway")
UnifiedPushConfig.DEFAULT_PUSH_GATEWAY_HTTP_URL
} else {
val port = if (url.port != -1) ":${url.port}" else ""
val customBase = "${url.protocol}://${url.host}$port"
val customUrl = "$customBase/_matrix/push/v1/notify"
Timber.i("Testing $customUrl")
logger.i("Testing $customUrl")
return withContext(coroutineDispatchers.io) {
val api = unifiedPushApiFactory.create(customBase)
try {
val discoveryResponse = api.discover()
if (discoveryResponse.unifiedpush.gateway == "matrix") {
Timber.d("Using custom gateway")
return@withContext customUrl
logger.d("The endpoint seems to be a valid UnifiedPush gateway")
} else {
logger.e("The endpoint does not seem to be a valid UnifiedPush gateway")
}
} catch (throwable: Throwable) {
Timber.tag("UnifiedPushHelper").e(throwable)
logger.e(throwable, "Error checking for UnifiedPush endpoint")
}
return@withContext gateway
// Always return the custom url.
customUrl
}
} catch (e: Throwable) {
Timber.d(e, "Cannot try custom gateway")
}
return gateway
}
}

View File

@@ -23,8 +23,6 @@ import io.element.android.libraries.pushproviders.api.CurrentUserPushConfig
import io.element.android.libraries.pushproviders.api.Distributor
import io.element.android.libraries.pushproviders.api.PushProvider
import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret
import io.element.android.services.appnavstate.api.AppNavigationStateService
import io.element.android.services.appnavstate.api.currentSessionId
import javax.inject.Inject
@ContributesMultibinding(AppScope::class)
@@ -34,7 +32,7 @@ class UnifiedPushProvider @Inject constructor(
private val unRegisterUnifiedPushUseCase: UnregisterUnifiedPushUseCase,
private val pushClientSecret: PushClientSecret,
private val unifiedPushStore: UnifiedPushStore,
private val appNavigationStateService: AppNavigationStateService,
private val unifiedPushCurrentUserPushConfigProvider: UnifiedPushCurrentUserPushConfigProvider,
) : PushProvider {
override val index = UnifiedPushConfig.INDEX
override val name = UnifiedPushConfig.NAME
@@ -62,13 +60,6 @@ class UnifiedPushProvider @Inject constructor(
}
override suspend fun getCurrentUserPushConfig(): CurrentUserPushConfig? {
val currentSession = appNavigationStateService.appNavigationState.value.navigationState.currentSessionId() ?: return null
val clientSecret = pushClientSecret.getSecretForUser(currentSession)
val url = unifiedPushStore.getPushGateway(clientSecret) ?: return null
val pushKey = unifiedPushStore.getEndpoint(clientSecret) ?: return null
return CurrentUserPushConfig(
url = url,
pushKey = pushKey,
)
return unifiedPushCurrentUserPushConfigProvider.provide()
}
}

View File

@@ -0,0 +1,90 @@
/*
* 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.troubleshoot
import com.squareup.anvil.annotations.ContributesMultibinding
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.pushproviders.unifiedpush.UnifiedPushApiFactory
import io.element.android.libraries.pushproviders.unifiedpush.UnifiedPushConfig
import io.element.android.libraries.pushproviders.unifiedpush.UnifiedPushCurrentUserPushConfigProvider
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTest
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestDelegate
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState
import io.element.android.libraries.troubleshoot.api.test.TestFilterData
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import javax.inject.Inject
@ContributesMultibinding(AppScope::class)
class UnifiedPushMatrixGatewayTest @Inject constructor(
private val unifiedPushApiFactory: UnifiedPushApiFactory,
private val coroutineDispatchers: CoroutineDispatchers,
private val unifiedPushCurrentUserPushConfigProvider: UnifiedPushCurrentUserPushConfigProvider,
) : NotificationTroubleshootTest {
override val order = 450
private val delegate = NotificationTroubleshootTestDelegate(
defaultName = "Test push gateway",
defaultDescription = "Ensure that the push gateway is valid.",
visibleWhenIdle = false,
fakeDelay = NotificationTroubleshootTestDelegate.SHORT_DELAY,
)
override val state: StateFlow<NotificationTroubleshootTestState> = delegate.state
override fun isRelevant(data: TestFilterData): Boolean {
return data.currentPushProviderName == UnifiedPushConfig.NAME
}
override suspend fun run(coroutineScope: CoroutineScope) {
delegate.start()
val config = unifiedPushCurrentUserPushConfigProvider.provide()
if (config == null) {
delegate.updateState(
description = "No current push provider",
status = NotificationTroubleshootTestState.Status.Failure(false)
)
} else {
val gatewayBaseUrl = config.url.removeSuffix("/_matrix/push/v1/notify")
// Checking if the gateway is a Matrix gateway
coroutineScope.launch(coroutineDispatchers.io) {
val api = unifiedPushApiFactory.create(gatewayBaseUrl)
try {
val discoveryResponse = api.discover()
if (discoveryResponse.unifiedpush.gateway == "matrix") {
delegate.updateState(
description = "${config.url} is a Matrix gateway.",
status = NotificationTroubleshootTestState.Status.Success
)
} else {
delegate.updateState(
description = "${config.url} is not a Matrix gateway.",
status = NotificationTroubleshootTestState.Status.Failure(false)
)
}
} catch (throwable: Throwable) {
delegate.updateState(
description = "Fail to check the gateway ${config.url}: ${throwable.localizedMessage}",
status = NotificationTroubleshootTestState.Status.Failure(false)
)
}
}
}
}
override suspend fun reset() = delegate.reset()
}

View File

@@ -0,0 +1,120 @@
/*
* 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
*
* https://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
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.test.A_SECRET
import io.element.android.libraries.matrix.test.A_SESSION_ID
import io.element.android.libraries.pushproviders.api.CurrentUserPushConfig
import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret
import io.element.android.libraries.pushstore.test.userpushstore.clientsecret.FakePushClientSecret
import io.element.android.services.appnavstate.api.AppNavigationState
import io.element.android.services.appnavstate.api.AppNavigationStateService
import io.element.android.services.appnavstate.api.NavigationState
import io.element.android.services.appnavstate.test.FakeAppNavigationStateService
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
import org.junit.Test
class DefaultUnifiedPushCurrentUserPushConfigProviderTest {
@Test
fun `getCurrentUserPushConfig no session`() = runTest {
val sut = createDefaultUnifiedPushCurrentUserPushConfigProvider()
val result = sut.provide()
assertThat(result).isNull()
}
@Test
fun `getCurrentUserPushConfig no push gateway`() = runTest {
val sut = createDefaultUnifiedPushCurrentUserPushConfigProvider(
appNavigationStateService = FakeAppNavigationStateService(
appNavigationState = MutableStateFlow(
AppNavigationState(
navigationState = NavigationState.Session(owner = "owner", sessionId = A_SESSION_ID),
isInForeground = true
)
)
),
pushClientSecret = FakePushClientSecret(
getSecretForUserResult = { A_SECRET }
),
unifiedPushStore = FakeUnifiedPushStore(
getPushGatewayResult = { null }
),
)
val result = sut.provide()
assertThat(result).isNull()
}
@Test
fun `getCurrentUserPushConfig no push key`() = runTest {
val sut = createDefaultUnifiedPushCurrentUserPushConfigProvider(
appNavigationStateService = FakeAppNavigationStateService(
appNavigationState = MutableStateFlow(
AppNavigationState(
navigationState = NavigationState.Session(owner = "owner", sessionId = A_SESSION_ID),
isInForeground = true
)
)
),
pushClientSecret = FakePushClientSecret(
getSecretForUserResult = { A_SECRET }
),
unifiedPushStore = FakeUnifiedPushStore(
getPushGatewayResult = { "aPushGateway" },
getEndpointResult = { null }
),
)
val result = sut.provide()
assertThat(result).isNull()
}
@Test
fun `getCurrentUserPushConfig ok`() = runTest {
val sut = createDefaultUnifiedPushCurrentUserPushConfigProvider(
appNavigationStateService = FakeAppNavigationStateService(
appNavigationState = MutableStateFlow(
AppNavigationState(
navigationState = NavigationState.Session(owner = "owner", sessionId = A_SESSION_ID),
isInForeground = true
)
)
),
pushClientSecret = FakePushClientSecret(
getSecretForUserResult = { A_SECRET }
),
unifiedPushStore = FakeUnifiedPushStore(
getPushGatewayResult = { "aPushGateway" },
getEndpointResult = { "aEndpoint" }
),
)
val result = sut.provide()
assertThat(result).isEqualTo(CurrentUserPushConfig("aPushGateway", "aEndpoint"))
}
private fun createDefaultUnifiedPushCurrentUserPushConfigProvider(
pushClientSecret: PushClientSecret = FakePushClientSecret(),
unifiedPushStore: UnifiedPushStore = FakeUnifiedPushStore(),
appNavigationStateService: AppNavigationStateService = FakeAppNavigationStateService(),
): DefaultUnifiedPushCurrentUserPushConfigProvider {
return DefaultUnifiedPushCurrentUserPushConfigProvider(
pushClientSecret = pushClientSecret,
unifiedPushStore = unifiedPushStore,
appNavigationStateService = appNavigationStateService,
)
}
}

View File

@@ -25,23 +25,23 @@ import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Test
internal val matrixDiscoveryResponse = {
DiscoveryResponse(
unifiedpush = DiscoveryUnifiedPush(
gateway = "matrix"
)
)
}
internal val invalidDiscoveryResponse = {
DiscoveryResponse(
unifiedpush = DiscoveryUnifiedPush(
gateway = ""
)
)
}
class DefaultUnifiedPushGatewayResolverTest {
private val matrixDiscoveryResponse = {
DiscoveryResponse(
unifiedpush = DiscoveryUnifiedPush(
gateway = "matrix"
)
)
}
private val invalidDiscoveryResponse = {
DiscoveryResponse(
unifiedpush = DiscoveryUnifiedPush(
gateway = ""
)
)
}
@Test
fun `when a custom url provide a correct matrix gateway, the custom url is returned`() = runTest {
val unifiedPushApiFactory = FakeUnifiedPushApiFactory(
@@ -95,7 +95,7 @@ class DefaultUnifiedPushGatewayResolverTest {
}
@Test
fun `when a custom url is not reachable, the default url is returned`() = runTest {
fun `when a custom url is not reachable, the custom url is still returned`() = runTest {
val unifiedPushApiFactory = FakeUnifiedPushApiFactory(
discoveryResponse = { throw AN_EXCEPTION }
)
@@ -104,7 +104,7 @@ class DefaultUnifiedPushGatewayResolverTest {
)
val result = sut.getGateway("http://custom.url")
assertThat(unifiedPushApiFactory.baseUrlParameter).isEqualTo("http://custom.url")
assertThat(result).isEqualTo(UnifiedPushConfig.DEFAULT_PUSH_GATEWAY_HTTP_URL)
assertThat(result).isEqualTo("http://custom.url/_matrix/push/v1/notify")
}
@Test
@@ -121,7 +121,7 @@ class DefaultUnifiedPushGatewayResolverTest {
}
@Test
fun `when a custom url provides a invalid matrix gateway, the default url is returned`() = runTest {
fun `when a custom url provides a invalid matrix gateway, the custom url is still returned`() = runTest {
val unifiedPushApiFactory = FakeUnifiedPushApiFactory(
discoveryResponse = invalidDiscoveryResponse
)
@@ -130,7 +130,7 @@ class DefaultUnifiedPushGatewayResolverTest {
)
val result = sut.getGateway("https://custom.url")
assertThat(unifiedPushApiFactory.baseUrlParameter).isEqualTo("https://custom.url")
assertThat(result).isEqualTo(UnifiedPushConfig.DEFAULT_PUSH_GATEWAY_HTTP_URL)
assertThat(result).isEqualTo("https://custom.url/_matrix/push/v1/notify")
}
private fun TestScope.createDefaultUnifiedPushGatewayResolver(

View File

@@ -24,18 +24,14 @@ import io.element.android.libraries.matrix.test.AN_EXCEPTION
import io.element.android.libraries.matrix.test.A_SECRET
import io.element.android.libraries.matrix.test.A_SESSION_ID
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.pushproviders.api.CurrentUserPushConfig
import io.element.android.libraries.pushproviders.api.Distributor
import io.element.android.libraries.pushproviders.test.aCurrentUserPushConfig
import io.element.android.libraries.pushproviders.unifiedpush.troubleshoot.FakeUnifiedPushCurrentUserPushConfigProvider
import io.element.android.libraries.pushproviders.unifiedpush.troubleshoot.FakeUnifiedPushDistributorProvider
import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret
import io.element.android.libraries.pushstore.test.userpushstore.clientsecret.FakePushClientSecret
import io.element.android.services.appnavstate.api.AppNavigationState
import io.element.android.services.appnavstate.api.AppNavigationStateService
import io.element.android.services.appnavstate.api.NavigationState
import io.element.android.services.appnavstate.test.FakeAppNavigationStateService
import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.lambda.value
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
import org.junit.Test
@@ -226,78 +222,15 @@ class UnifiedPushProviderTest {
}
@Test
fun `getCurrentUserPushConfig no session`() = runTest {
val unifiedPushProvider = createUnifiedPushProvider()
val result = unifiedPushProvider.getCurrentUserPushConfig()
assertThat(result).isNull()
}
@Test
fun `getCurrentUserPushConfig no push gateway`() = runTest {
fun `getCurrentUserPushConfig invokes the provider methods`() = runTest {
val currentUserPushConfig = aCurrentUserPushConfig()
val unifiedPushProvider = createUnifiedPushProvider(
appNavigationStateService = FakeAppNavigationStateService(
appNavigationState = MutableStateFlow(
AppNavigationState(
navigationState = NavigationState.Session(owner = "owner", sessionId = A_SESSION_ID),
isInForeground = true
)
)
),
pushClientSecret = FakePushClientSecret(
getSecretForUserResult = { A_SECRET }
),
unifiedPushStore = FakeUnifiedPushStore(
getPushGatewayResult = { null }
),
unifiedPushCurrentUserPushConfigProvider = FakeUnifiedPushCurrentUserPushConfigProvider(
currentUserPushConfig = { currentUserPushConfig }
)
)
val result = unifiedPushProvider.getCurrentUserPushConfig()
assertThat(result).isNull()
}
@Test
fun `getCurrentUserPushConfig no push key`() = runTest {
val unifiedPushProvider = createUnifiedPushProvider(
appNavigationStateService = FakeAppNavigationStateService(
appNavigationState = MutableStateFlow(
AppNavigationState(
navigationState = NavigationState.Session(owner = "owner", sessionId = A_SESSION_ID),
isInForeground = true
)
)
),
pushClientSecret = FakePushClientSecret(
getSecretForUserResult = { A_SECRET }
),
unifiedPushStore = FakeUnifiedPushStore(
getPushGatewayResult = { "aPushGateway" },
getEndpointResult = { null }
),
)
val result = unifiedPushProvider.getCurrentUserPushConfig()
assertThat(result).isNull()
}
@Test
fun `getCurrentUserPushConfig ok`() = runTest {
val unifiedPushProvider = createUnifiedPushProvider(
appNavigationStateService = FakeAppNavigationStateService(
appNavigationState = MutableStateFlow(
AppNavigationState(
navigationState = NavigationState.Session(owner = "owner", sessionId = A_SESSION_ID),
isInForeground = true
)
)
),
pushClientSecret = FakePushClientSecret(
getSecretForUserResult = { A_SECRET }
),
unifiedPushStore = FakeUnifiedPushStore(
getPushGatewayResult = { "aPushGateway" },
getEndpointResult = { "aEndpoint" }
),
)
val result = unifiedPushProvider.getCurrentUserPushConfig()
assertThat(result).isEqualTo(CurrentUserPushConfig("aPushGateway", "aEndpoint"))
assertThat(result).isEqualTo(currentUserPushConfig)
}
private fun createUnifiedPushProvider(
@@ -306,7 +239,7 @@ class UnifiedPushProviderTest {
unRegisterUnifiedPushUseCase: UnregisterUnifiedPushUseCase = FakeUnregisterUnifiedPushUseCase(),
pushClientSecret: PushClientSecret = FakePushClientSecret(),
unifiedPushStore: UnifiedPushStore = FakeUnifiedPushStore(),
appNavigationStateService: AppNavigationStateService = FakeAppNavigationStateService(),
unifiedPushCurrentUserPushConfigProvider: UnifiedPushCurrentUserPushConfigProvider = FakeUnifiedPushCurrentUserPushConfigProvider(),
): UnifiedPushProvider {
return UnifiedPushProvider(
unifiedPushDistributorProvider = unifiedPushDistributorProvider,
@@ -314,7 +247,7 @@ class UnifiedPushProviderTest {
unRegisterUnifiedPushUseCase = unRegisterUnifiedPushUseCase,
pushClientSecret = pushClientSecret,
unifiedPushStore = unifiedPushStore,
appNavigationStateService = appNavigationStateService
unifiedPushCurrentUserPushConfigProvider = unifiedPushCurrentUserPushConfigProvider,
)
}
}

View File

@@ -0,0 +1,29 @@
/*
* 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
*
* https://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.troubleshoot
import io.element.android.libraries.pushproviders.api.CurrentUserPushConfig
import io.element.android.libraries.pushproviders.unifiedpush.UnifiedPushCurrentUserPushConfigProvider
import io.element.android.tests.testutils.lambda.lambdaError
class FakeUnifiedPushCurrentUserPushConfigProvider(
private val currentUserPushConfig: () -> CurrentUserPushConfig? = { lambdaError() },
) : UnifiedPushCurrentUserPushConfigProvider {
override suspend fun provide(): CurrentUserPushConfig? {
return currentUserPushConfig()
}
}

View File

@@ -0,0 +1,127 @@
/*
* 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.troubleshoot
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.pushproviders.api.CurrentUserPushConfig
import io.element.android.libraries.pushproviders.test.aCurrentUserPushConfig
import io.element.android.libraries.pushproviders.unifiedpush.FakeUnifiedPushApiFactory
import io.element.android.libraries.pushproviders.unifiedpush.UnifiedPushConfig
import io.element.android.libraries.pushproviders.unifiedpush.invalidDiscoveryResponse
import io.element.android.libraries.pushproviders.unifiedpush.matrixDiscoveryResponse
import io.element.android.libraries.pushproviders.unifiedpush.network.DiscoveryResponse
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState
import io.element.android.libraries.troubleshoot.api.test.TestFilterData
import io.element.android.tests.testutils.testCoroutineDispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Test
class UnifiedPushMatrixGatewayTestTest {
@Test
fun `test UnifiedPushMatrixGatewayTest success`() = runTest {
val sut = createUnifiedPushMatrixGatewayTest(
currentUserPushConfig = aCurrentUserPushConfig(),
discoveryResponse = matrixDiscoveryResponse,
)
launch {
sut.run(this)
}
sut.state.test {
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(false))
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
val lastItem = awaitItem()
assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Success)
}
}
@Test
fun `test UnifiedPushMatrixGatewayTest no config found`() = runTest {
val sut = createUnifiedPushMatrixGatewayTest(
currentUserPushConfig = null,
discoveryResponse = matrixDiscoveryResponse,
)
launch {
sut.run(this)
}
sut.state.test {
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(false))
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
val lastItem = awaitItem()
assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(false))
}
}
@Test
fun `test UnifiedPushMatrixGatewayTest not valid gateway`() = runTest {
val sut = createUnifiedPushMatrixGatewayTest(
currentUserPushConfig = aCurrentUserPushConfig(),
discoveryResponse = invalidDiscoveryResponse,
)
launch {
sut.run(this)
}
sut.state.test {
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(false))
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
val lastItem = awaitItem()
assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(false))
// Reset the error
sut.reset()
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(false))
}
}
@Test
fun `test UnifiedPushMatrixGatewayTest network error`() = runTest {
val sut = createUnifiedPushMatrixGatewayTest(
currentUserPushConfig = aCurrentUserPushConfig(),
discoveryResponse = { error("Network error") },
)
launch {
sut.run(this)
}
sut.state.test {
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(false))
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
val lastItem = awaitItem()
assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(false))
}
}
@Test
fun `test isRelevant`() = runTest {
val sut = createUnifiedPushMatrixGatewayTest()
assertThat(sut.isRelevant(TestFilterData(currentPushProviderName = UnifiedPushConfig.NAME))).isTrue()
assertThat(sut.isRelevant(TestFilterData(currentPushProviderName = "other"))).isFalse()
}
private fun TestScope.createUnifiedPushMatrixGatewayTest(
currentUserPushConfig: CurrentUserPushConfig? = null,
discoveryResponse: () -> DiscoveryResponse = matrixDiscoveryResponse,
): UnifiedPushMatrixGatewayTest {
return UnifiedPushMatrixGatewayTest(
unifiedPushApiFactory = FakeUnifiedPushApiFactory(discoveryResponse),
coroutineDispatchers = testCoroutineDispatchers(),
unifiedPushCurrentUserPushConfigProvider = FakeUnifiedPushCurrentUserPushConfigProvider(
currentUserPushConfig = { currentUserPushConfig }
),
)
}
}

View File

@@ -84,6 +84,35 @@ class UnifiedPushTestTest {
}
}
@Test
fun `test UnifiedPushTest error and reset`() = runTest {
val providers = FakeUnifiedPushDistributorProvider()
val sut = UnifiedPushTest(
unifiedPushDistributorProvider = providers,
openDistributorWebPageAction = FakeOpenDistributorWebPageAction(
executeAction = {
providers.setDistributorsResult(
listOf(
Distributor("value", "Name"),
)
)
}
),
stringProvider = FakeStringProvider(),
)
launch {
sut.run(this)
}
sut.state.test {
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(false))
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
val lastItem = awaitItem()
assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(true))
sut.reset()
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(false))
}
}
@Test
fun `test isRelevant`() {
val sut = UnifiedPushTest(