Merge pull request #2899 from element-hq/feature/bma/unitTestPush
Add unit tests in some push classes
This commit is contained in:
@@ -33,6 +33,7 @@ import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||
import io.element.android.libraries.matrix.test.A_THREAD_ID
|
||||
import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
import org.junit.Assert.assertThrows
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
@@ -229,7 +230,7 @@ class IntentResolverTest {
|
||||
}
|
||||
|
||||
private fun createIntentResolver(
|
||||
permalinkParserResult: () -> PermalinkData = { throw NotImplementedError() }
|
||||
permalinkParserResult: () -> PermalinkData = { lambdaError() }
|
||||
): IntentResolver {
|
||||
return IntentResolver(
|
||||
deeplinkParser = DeeplinkParser(),
|
||||
|
||||
@@ -192,9 +192,11 @@ class AcceptDeclineInvitePresenterTest {
|
||||
cancelAndConsumeRemainingEvents()
|
||||
}
|
||||
assert(joinRoomFailure)
|
||||
.isCalledExactly(1)
|
||||
.withSequence(
|
||||
listOf(value(A_ROOM_ID), value(emptyList<String>()), value(JoinedRoom.Trigger.Invite))
|
||||
.isCalledOnce()
|
||||
.with(
|
||||
value(A_ROOM_ID),
|
||||
value(emptyList<String>()),
|
||||
value(JoinedRoom.Trigger.Invite)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -221,9 +223,11 @@ class AcceptDeclineInvitePresenterTest {
|
||||
cancelAndConsumeRemainingEvents()
|
||||
}
|
||||
assert(joinRoomSuccess)
|
||||
.isCalledExactly(1)
|
||||
.withSequence(
|
||||
listOf(value(A_ROOM_ID), value(emptyList<String>()), value(JoinedRoom.Trigger.Invite))
|
||||
.isCalledOnce()
|
||||
.with(
|
||||
value(A_ROOM_ID),
|
||||
value(emptyList<String>()),
|
||||
value(JoinedRoom.Trigger.Invite)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -19,12 +19,12 @@ package io.element.android.features.preferences.impl.notifications
|
||||
import app.cash.molecule.RecompositionMode
|
||||
import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.test
|
||||
import com.element.android.libraries.pushstore.test.userpushstore.FakeUserPushStoreFactory
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||
import io.element.android.libraries.matrix.test.A_THROWABLE
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService
|
||||
import io.element.android.libraries.pushstore.test.userpushstore.FakeUserPushStoreFactory
|
||||
import io.element.android.tests.testutils.consumeItemsUntilPredicate
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
@@ -136,7 +136,7 @@ class BugReportPresenterTest {
|
||||
initialState.eventSink.invoke(BugReportEvents.ResetAll)
|
||||
val resetState = awaitItem()
|
||||
assertThat(resetState.hasCrashLogs).isFalse()
|
||||
logFilesRemoverLambda.assertions().isCalledExactly(1)
|
||||
logFilesRemoverLambda.assertions().isCalledOnce()
|
||||
// TODO Make it live assertThat(resetState.screenshotUri).isNull()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,9 +57,9 @@ class DefaultJoinRoomTest {
|
||||
.isNeverCalled()
|
||||
joinRoomLambda
|
||||
.assertions()
|
||||
.isCalledExactly(1)
|
||||
.withSequence(
|
||||
listOf(value(A_ROOM_ID))
|
||||
.isCalledOnce()
|
||||
.with(
|
||||
value(A_ROOM_ID)
|
||||
)
|
||||
assertThat(analyticsService.capturedEvents).containsExactly(
|
||||
roomResult.toAnalyticsJoinedRoom(aTrigger)
|
||||
@@ -88,9 +88,10 @@ class DefaultJoinRoomTest {
|
||||
sut.invoke(A_ROOM_ID, A_SERVER_LIST, aTrigger)
|
||||
joinRoomByIdOrAliasLambda
|
||||
.assertions()
|
||||
.isCalledExactly(1)
|
||||
.withSequence(
|
||||
listOf(value(A_ROOM_ID), value(A_SERVER_LIST))
|
||||
.isCalledOnce()
|
||||
.with(
|
||||
value(A_ROOM_ID),
|
||||
value(A_SERVER_LIST)
|
||||
)
|
||||
joinRoomLambda
|
||||
.assertions()
|
||||
|
||||
@@ -30,6 +30,7 @@ import io.element.android.libraries.matrix.api.room.RoomNotificationSettings
|
||||
|
||||
const val A_USER_NAME = "alice"
|
||||
const val A_PASSWORD = "password"
|
||||
const val A_SECRET = "secret"
|
||||
|
||||
val A_USER_ID = UserId("@alice:server.org")
|
||||
val A_USER_ID_2 = UserId("@bob:server.org")
|
||||
|
||||
@@ -31,7 +31,9 @@ import kotlinx.coroutines.flow.flowOf
|
||||
|
||||
val A_OIDC_DATA = OidcDetails(url = "a-url")
|
||||
|
||||
class FakeAuthenticationService : MatrixAuthenticationService {
|
||||
class FakeAuthenticationService(
|
||||
private val matrixClientResult: ((SessionId) -> Result<MatrixClient>)? = null
|
||||
) : MatrixAuthenticationService {
|
||||
private val homeserver = MutableStateFlow<MatrixHomeServerDetails?>(null)
|
||||
private var oidcError: Throwable? = null
|
||||
private var oidcCancelError: Throwable? = null
|
||||
@@ -48,6 +50,9 @@ class FakeAuthenticationService : MatrixAuthenticationService {
|
||||
override suspend fun getLatestSessionId(): SessionId? = getLatestSessionIdLambda()
|
||||
|
||||
override suspend fun restoreSession(sessionId: SessionId): Result<MatrixClient> {
|
||||
if (matrixClientResult != null) {
|
||||
return matrixClientResult.invoke(sessionId)
|
||||
}
|
||||
return if (matrixClient != null) {
|
||||
Result.success(matrixClient!!)
|
||||
} else {
|
||||
|
||||
@@ -18,9 +18,10 @@ package io.element.android.libraries.matrix.test.permalink
|
||||
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkData
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
|
||||
class FakePermalinkParser(
|
||||
private var result: () -> PermalinkData = { TODO("Not implemented") }
|
||||
private var result: () -> PermalinkData = { lambdaError() }
|
||||
) : PermalinkParser {
|
||||
fun givenResult(result: PermalinkData) {
|
||||
this.result = { result }
|
||||
|
||||
@@ -19,8 +19,12 @@ package io.element.android.libraries.matrix.test.pushers
|
||||
import io.element.android.libraries.matrix.api.pusher.PushersService
|
||||
import io.element.android.libraries.matrix.api.pusher.SetHttpPusherData
|
||||
import io.element.android.libraries.matrix.api.pusher.UnsetHttpPusherData
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
|
||||
class FakePushersService : PushersService {
|
||||
override suspend fun setHttpPusher(setHttpPusherData: SetHttpPusherData) = Result.success(Unit)
|
||||
override suspend fun unsetHttpPusher(unsetHttpPusherData: UnsetHttpPusherData): Result<Unit> = Result.success(Unit)
|
||||
class FakePushersService(
|
||||
private val setHttpPusherResult: (SetHttpPusherData) -> Result<Unit> = { lambdaError() },
|
||||
private val unsetHttpPusherResult: (UnsetHttpPusherData) -> Result<Unit> = { lambdaError() },
|
||||
) : PushersService {
|
||||
override suspend fun setHttpPusher(setHttpPusherData: SetHttpPusherData) = setHttpPusherResult(setHttpPusherData)
|
||||
override suspend fun unsetHttpPusher(unsetHttpPusherData: UnsetHttpPusherData): Result<Unit> = unsetHttpPusherResult(unsetHttpPusherData)
|
||||
}
|
||||
|
||||
@@ -21,9 +21,6 @@ import io.element.android.libraries.pushproviders.api.Distributor
|
||||
import io.element.android.libraries.pushproviders.api.PushProvider
|
||||
|
||||
interface PushService {
|
||||
// TODO Move away
|
||||
fun notificationStyleChanged()
|
||||
|
||||
/**
|
||||
* Return the current push provider, or null if none.
|
||||
*/
|
||||
|
||||
@@ -72,6 +72,7 @@ dependencies {
|
||||
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.appnavstate.test)
|
||||
testImplementation(projects.services.toolbox.impl)
|
||||
|
||||
@@ -21,7 +21,7 @@ import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.push.api.GetCurrentPushProvider
|
||||
import io.element.android.libraries.push.api.PushService
|
||||
import io.element.android.libraries.push.impl.notifications.DefaultNotificationDrawerManager
|
||||
import io.element.android.libraries.push.impl.test.TestPush
|
||||
import io.element.android.libraries.pushproviders.api.Distributor
|
||||
import io.element.android.libraries.pushproviders.api.PushProvider
|
||||
import io.element.android.libraries.pushstore.api.UserPushStoreFactory
|
||||
@@ -30,16 +30,11 @@ import javax.inject.Inject
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultPushService @Inject constructor(
|
||||
private val defaultNotificationDrawerManager: DefaultNotificationDrawerManager,
|
||||
private val pushersManager: PushersManager,
|
||||
private val testPush: TestPush,
|
||||
private val userPushStoreFactory: UserPushStoreFactory,
|
||||
private val pushProviders: Set<@JvmSuppressWildcards PushProvider>,
|
||||
private val getCurrentPushProvider: GetCurrentPushProvider,
|
||||
) : PushService {
|
||||
override fun notificationStyleChanged() {
|
||||
defaultNotificationDrawerManager.notificationStyleChanged()
|
||||
}
|
||||
|
||||
override suspend fun getCurrentPushProvider(): PushProvider? {
|
||||
val currentPushProvider = getCurrentPushProvider.getCurrentPushProvider()
|
||||
return pushProviders.find { it.name == currentPushProvider }
|
||||
@@ -51,9 +46,6 @@ class DefaultPushService @Inject constructor(
|
||||
.sortedBy { it.index }
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current push provider, compare with provided one, then unregister and register if different, and store change.
|
||||
*/
|
||||
override suspend fun registerWith(
|
||||
matrixClient: MatrixClient,
|
||||
pushProvider: PushProvider,
|
||||
@@ -80,7 +72,7 @@ class DefaultPushService @Inject constructor(
|
||||
override suspend fun testPush(): Boolean {
|
||||
val pushProvider = getCurrentPushProvider() ?: return false
|
||||
val config = pushProvider.getCurrentUserPushConfig() ?: return false
|
||||
pushersManager.testPush(config)
|
||||
testPush.execute(config)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,13 +22,9 @@ import io.element.android.libraries.core.log.logger.LoggerTag
|
||||
import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.pusher.SetHttpPusherData
|
||||
import io.element.android.libraries.matrix.api.pusher.UnsetHttpPusherData
|
||||
import io.element.android.libraries.push.impl.pushgateway.PushGatewayNotifyRequest
|
||||
import io.element.android.libraries.pushproviders.api.CurrentUserPushConfig
|
||||
import io.element.android.libraries.pushproviders.api.PusherSubscriber
|
||||
import io.element.android.libraries.pushstore.api.UserPushStoreFactory
|
||||
import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret
|
||||
@@ -37,29 +33,14 @@ import javax.inject.Inject
|
||||
|
||||
internal const val DEFAULT_PUSHER_FILE_TAG = "mobile"
|
||||
|
||||
private val loggerTag = LoggerTag("PushersManager", LoggerTag.PushLoggerTag)
|
||||
private val loggerTag = LoggerTag("DefaultPusherSubscriber", LoggerTag.PushLoggerTag)
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class PushersManager @Inject constructor(
|
||||
// private val localeProvider: LocaleProvider,
|
||||
class DefaultPusherSubscriber @Inject constructor(
|
||||
private val buildMeta: BuildMeta,
|
||||
// private val getDeviceInfoUseCase: GetDeviceInfoUseCase,
|
||||
private val pushGatewayNotifyRequest: PushGatewayNotifyRequest,
|
||||
private val pushClientSecret: PushClientSecret,
|
||||
private val userPushStoreFactory: UserPushStoreFactory,
|
||||
) : PusherSubscriber {
|
||||
suspend fun testPush(config: CurrentUserPushConfig) {
|
||||
pushGatewayNotifyRequest.execute(
|
||||
PushGatewayNotifyRequest.Params(
|
||||
url = config.url,
|
||||
appId = PushConfig.PUSHER_APP_ID,
|
||||
pushKey = config.pushKey,
|
||||
eventId = TEST_EVENT_ID,
|
||||
roomId = TEST_ROOM_ID,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a pusher to the server if not done yet.
|
||||
*/
|
||||
@@ -131,9 +112,4 @@ class PushersManager @Inject constructor(
|
||||
Timber.tag(loggerTag.value).e(throwable, "Unable to unregister the pusher")
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val TEST_EVENT_ID = EventId("\$THIS_IS_A_FAKE_EVENT_ID")
|
||||
val TEST_ROOM_ID = RoomId("!room:domain")
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,9 @@ package io.element.android.libraries.push.impl.notifications
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.core.content.FileProvider
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.core.log.logger.LoggerTag
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.di.ApplicationContext
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.MatrixClientProvider
|
||||
@@ -55,7 +57,7 @@ import io.element.android.services.toolbox.api.systemclock.SystemClock
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
private val loggerTag = LoggerTag("NotifiableEventResolver", LoggerTag.NotificationLoggerTag)
|
||||
private val loggerTag = LoggerTag("DefaultNotifiableEventResolver", LoggerTag.NotificationLoggerTag)
|
||||
|
||||
/**
|
||||
* The notifiable event resolver is able to create a NotifiableEvent (view model for notifications) from an sdk Event.
|
||||
@@ -63,15 +65,20 @@ private val loggerTag = LoggerTag("NotifiableEventResolver", LoggerTag.Notificat
|
||||
* The NotifiableEventResolver is the only aware of session/store, the NotificationDrawerManager has no knowledge of that,
|
||||
* this pattern allow decoupling between the object responsible of displaying notifications and the matrix sdk.
|
||||
*/
|
||||
class NotifiableEventResolver @Inject constructor(
|
||||
interface NotifiableEventResolver {
|
||||
suspend fun resolveEvent(sessionId: SessionId, roomId: RoomId, eventId: EventId): NotifiableEvent?
|
||||
}
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultNotifiableEventResolver @Inject constructor(
|
||||
private val stringProvider: StringProvider,
|
||||
private val clock: SystemClock,
|
||||
private val matrixClientProvider: MatrixClientProvider,
|
||||
private val notificationMediaRepoFactory: NotificationMediaRepo.Factory,
|
||||
@ApplicationContext private val context: Context,
|
||||
private val permalinkParser: PermalinkParser,
|
||||
) {
|
||||
suspend fun resolveEvent(sessionId: SessionId, roomId: RoomId, eventId: EventId): NotifiableEvent? {
|
||||
) : NotifiableEventResolver {
|
||||
override suspend fun resolveEvent(sessionId: SessionId, roomId: RoomId, eventId: EventId): NotifiableEvent? {
|
||||
// Restore session
|
||||
val client = matrixClientProvider.getOrRestore(sessionId).getOrNull() ?: return null
|
||||
val notificationService = client.notificationService()
|
||||
@@ -221,18 +221,6 @@ class DefaultNotificationDrawerManager @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
// TODO EAx Must be per account
|
||||
fun notificationStyleChanged() {
|
||||
updateEvents(doRender = true) {
|
||||
val newSettings = true // pushDataStore.useCompleteNotificationFormat()
|
||||
if (newSettings != useCompleteNotificationFormat) {
|
||||
// Settings has changed, remove all current notifications
|
||||
notificationRenderer.cancelAllNotifications()
|
||||
useCompleteNotificationFormat = newSettings
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateEvents(
|
||||
doRender: Boolean,
|
||||
action: (NotificationEventQueue) -> Unit,
|
||||
|
||||
@@ -16,27 +16,19 @@
|
||||
|
||||
package io.element.android.libraries.push.impl.push
|
||||
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.core.log.logger.LoggerTag
|
||||
import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
||||
import io.element.android.libraries.push.impl.PushersManager
|
||||
import io.element.android.libraries.push.impl.notifications.DefaultNotificationDrawerManager
|
||||
import io.element.android.libraries.push.impl.notifications.NotifiableEventResolver
|
||||
import io.element.android.libraries.push.impl.store.DefaultPushDataStore
|
||||
import io.element.android.libraries.push.impl.test.DefaultTestPush
|
||||
import io.element.android.libraries.push.impl.troubleshoot.DiagnosticPushHandler
|
||||
import io.element.android.libraries.pushproviders.api.PushData
|
||||
import io.element.android.libraries.pushproviders.api.PushHandler
|
||||
import io.element.android.libraries.pushstore.api.UserPushStoreFactory
|
||||
import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -44,23 +36,15 @@ private val loggerTag = LoggerTag("PushHandler", LoggerTag.PushLoggerTag)
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultPushHandler @Inject constructor(
|
||||
private val defaultNotificationDrawerManager: DefaultNotificationDrawerManager,
|
||||
private val onNotifiableEventReceived: OnNotifiableEventReceived,
|
||||
private val notifiableEventResolver: NotifiableEventResolver,
|
||||
private val defaultPushDataStore: DefaultPushDataStore,
|
||||
private val incrementPushDataStore: IncrementPushDataStore,
|
||||
private val userPushStoreFactory: UserPushStoreFactory,
|
||||
private val pushClientSecret: PushClientSecret,
|
||||
// private val actionIds: NotificationActionIds,
|
||||
private val buildMeta: BuildMeta,
|
||||
private val matrixAuthenticationService: MatrixAuthenticationService,
|
||||
private val diagnosticPushHandler: DiagnosticPushHandler,
|
||||
) : PushHandler {
|
||||
private val coroutineScope = CoroutineScope(SupervisorJob())
|
||||
|
||||
// UI handler
|
||||
private val uiHandler by lazy {
|
||||
Handler(Looper.getMainLooper())
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when message is received.
|
||||
*
|
||||
@@ -68,21 +52,15 @@ class DefaultPushHandler @Inject constructor(
|
||||
*/
|
||||
override suspend fun handle(pushData: PushData) {
|
||||
Timber.tag(loggerTag.value).d("## handling pushData: ${pushData.roomId}/${pushData.eventId}")
|
||||
|
||||
if (buildMeta.lowPrivacyLoggingEnabled) {
|
||||
Timber.tag(loggerTag.value).d("## pushData: $pushData")
|
||||
}
|
||||
|
||||
defaultPushDataStore.incrementPushCounter()
|
||||
|
||||
incrementPushDataStore.incrementPushCounter()
|
||||
// Diagnostic Push
|
||||
if (pushData.eventId == PushersManager.TEST_EVENT_ID) {
|
||||
if (pushData.eventId == DefaultTestPush.TEST_EVENT_ID) {
|
||||
diagnosticPushHandler.handlePush()
|
||||
return
|
||||
}
|
||||
|
||||
uiHandler.post {
|
||||
coroutineScope.launch(Dispatchers.IO) { handleInternal(pushData) }
|
||||
} else {
|
||||
handleInternal(pushData)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +76,6 @@ class DefaultPushHandler @Inject constructor(
|
||||
} else {
|
||||
Timber.tag(loggerTag.value).d("## handleInternal()")
|
||||
}
|
||||
|
||||
val clientSecret = pushData.clientSecret
|
||||
// clientSecret should not be null. If this happens, restore default session
|
||||
val userId = clientSecret
|
||||
@@ -109,27 +86,22 @@ class DefaultPushHandler @Inject constructor(
|
||||
?: run {
|
||||
matrixAuthenticationService.getLatestSessionId()
|
||||
}
|
||||
|
||||
if (userId == null) {
|
||||
Timber.w("Unable to get a session")
|
||||
return
|
||||
}
|
||||
|
||||
val notifiableEvent = notifiableEventResolver.resolveEvent(userId, pushData.roomId, pushData.eventId)
|
||||
|
||||
if (notifiableEvent == null) {
|
||||
Timber.w("Unable to get a notification data")
|
||||
return
|
||||
}
|
||||
|
||||
val userPushStore = userPushStoreFactory.getOrCreate(userId)
|
||||
if (!userPushStore.getNotificationEnabledForDevice().first()) {
|
||||
if (userPushStore.getNotificationEnabledForDevice().first()) {
|
||||
val notifiableEvent = notifiableEventResolver.resolveEvent(userId, pushData.roomId, pushData.eventId)
|
||||
if (notifiableEvent == null) {
|
||||
Timber.w("Unable to get a notification data")
|
||||
return
|
||||
}
|
||||
onNotifiableEventReceived.onNotifiableEventReceived(notifiableEvent)
|
||||
} else {
|
||||
// TODO We need to check if this is an incoming call
|
||||
Timber.tag(loggerTag.value).i("Notification are disabled for this device, ignore push.")
|
||||
return
|
||||
}
|
||||
|
||||
defaultNotificationDrawerManager.onNotifiableEventReceived(notifiableEvent)
|
||||
} catch (e: Exception) {
|
||||
Timber.tag(loggerTag.value).e(e, "## handleInternal() failed")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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.push.impl.push
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.push.impl.store.DefaultPushDataStore
|
||||
import javax.inject.Inject
|
||||
|
||||
interface IncrementPushDataStore {
|
||||
suspend fun incrementPushCounter()
|
||||
}
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultIncrementPushDataStore @Inject constructor(
|
||||
private val defaultPushDataStore: DefaultPushDataStore
|
||||
) : IncrementPushDataStore {
|
||||
override suspend fun incrementPushCounter() {
|
||||
defaultPushDataStore.incrementPushCounter()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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.push.impl.push
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.push.impl.notifications.DefaultNotificationDrawerManager
|
||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent
|
||||
import javax.inject.Inject
|
||||
|
||||
interface OnNotifiableEventReceived {
|
||||
fun onNotifiableEventReceived(notifiableEvent: NotifiableEvent)
|
||||
}
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultOnNotifiableEventReceived @Inject constructor(
|
||||
private val defaultNotificationDrawerManager: DefaultNotificationDrawerManager,
|
||||
) : OnNotifiableEventReceived {
|
||||
override fun onNotifiableEventReceived(notifiableEvent: NotifiableEvent) {
|
||||
defaultNotificationDrawerManager.onNotifiableEventReceived(notifiableEvent)
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ package io.element.android.libraries.push.impl.pushgateway
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.POST
|
||||
|
||||
internal interface PushGatewayAPI {
|
||||
interface PushGatewayAPI {
|
||||
/**
|
||||
* Ask the Push Gateway to send a push to the current device.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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.push.impl.pushgateway
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.network.RetrofitFactory
|
||||
import javax.inject.Inject
|
||||
|
||||
interface PushGatewayApiFactory {
|
||||
fun create(baseUrl: String): PushGatewayAPI
|
||||
}
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultPushGatewayApiFactory @Inject constructor(
|
||||
private val retrofitFactory: RetrofitFactory,
|
||||
) : PushGatewayApiFactory {
|
||||
override fun create(baseUrl: String): PushGatewayAPI {
|
||||
return retrofitFactory.create(baseUrl)
|
||||
.create(PushGatewayAPI::class.java)
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
internal data class PushGatewayDevice(
|
||||
data class PushGatewayDevice(
|
||||
/**
|
||||
* Required. The app_id given when the pusher was created.
|
||||
*/
|
||||
|
||||
@@ -20,7 +20,7 @@ import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
internal data class PushGatewayNotification(
|
||||
data class PushGatewayNotification(
|
||||
@SerialName("event_id")
|
||||
val eventId: String,
|
||||
@SerialName("room_id")
|
||||
|
||||
@@ -20,7 +20,7 @@ import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
internal data class PushGatewayNotifyBody(
|
||||
data class PushGatewayNotifyBody(
|
||||
/**
|
||||
* Required. Information about the push notification
|
||||
*/
|
||||
|
||||
@@ -15,15 +15,14 @@
|
||||
*/
|
||||
package io.element.android.libraries.push.impl.pushgateway
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.network.RetrofitFactory
|
||||
import io.element.android.libraries.push.api.gateway.PushGatewayFailure
|
||||
import javax.inject.Inject
|
||||
|
||||
class PushGatewayNotifyRequest @Inject constructor(
|
||||
private val retrofitFactory: RetrofitFactory,
|
||||
) {
|
||||
interface PushGatewayNotifyRequest {
|
||||
data class Params(
|
||||
val url: String,
|
||||
val appId: String,
|
||||
@@ -32,13 +31,18 @@ class PushGatewayNotifyRequest @Inject constructor(
|
||||
val roomId: RoomId,
|
||||
)
|
||||
|
||||
suspend fun execute(params: Params) {
|
||||
val sygnalApi = retrofitFactory.create(
|
||||
suspend fun execute(params: Params)
|
||||
}
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultPushGatewayNotifyRequest @Inject constructor(
|
||||
private val pushGatewayApiFactory: PushGatewayApiFactory,
|
||||
) : PushGatewayNotifyRequest {
|
||||
override suspend fun execute(params: PushGatewayNotifyRequest.Params) {
|
||||
val pushGatewayApi = pushGatewayApiFactory.create(
|
||||
params.url.substringBefore(PushGatewayConfig.URI_PUSH_GATEWAY_PREFIX_PATH)
|
||||
)
|
||||
.create(PushGatewayAPI::class.java)
|
||||
|
||||
val response = sygnalApi.notify(
|
||||
val response = pushGatewayApi.notify(
|
||||
PushGatewayNotifyBody(
|
||||
PushGatewayNotification(
|
||||
eventId = params.eventId.value,
|
||||
|
||||
@@ -20,7 +20,7 @@ import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
internal data class PushGatewayNotifyResponse(
|
||||
data class PushGatewayNotifyResponse(
|
||||
@SerialName("rejected")
|
||||
val rejectedPushKeys: List<String>
|
||||
)
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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.push.impl.test
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.appconfig.PushConfig
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.push.impl.pushgateway.PushGatewayNotifyRequest
|
||||
import io.element.android.libraries.pushproviders.api.CurrentUserPushConfig
|
||||
import javax.inject.Inject
|
||||
|
||||
interface TestPush {
|
||||
suspend fun execute(config: CurrentUserPushConfig)
|
||||
}
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultTestPush @Inject constructor(
|
||||
private val pushGatewayNotifyRequest: PushGatewayNotifyRequest,
|
||||
) : TestPush {
|
||||
override suspend fun execute(config: CurrentUserPushConfig) {
|
||||
pushGatewayNotifyRequest.execute(
|
||||
PushGatewayNotifyRequest.Params(
|
||||
url = config.url,
|
||||
appId = PushConfig.PUSHER_APP_ID,
|
||||
pushKey = config.pushKey,
|
||||
eventId = TEST_EVENT_ID,
|
||||
roomId = TEST_ROOM_ID,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val TEST_EVENT_ID = EventId("\$THIS_IS_A_FAKE_EVENT_ID")
|
||||
val TEST_ROOM_ID = RoomId("!room:domain")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* 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.push.impl
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.libraries.push.api.GetCurrentPushProvider
|
||||
import io.element.android.libraries.push.impl.test.FakeTestPush
|
||||
import io.element.android.libraries.push.impl.test.TestPush
|
||||
import io.element.android.libraries.push.test.FakeGetCurrentPushProvider
|
||||
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.pushstore.api.UserPushStoreFactory
|
||||
import io.element.android.libraries.pushstore.test.userpushstore.FakeUserPushStore
|
||||
import io.element.android.libraries.pushstore.test.userpushstore.FakeUserPushStoreFactory
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.value
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
class DefaultPushServiceTest {
|
||||
@Test
|
||||
fun `test push no push provider`() = runTest {
|
||||
val defaultPushService = createDefaultPushService()
|
||||
assertThat(defaultPushService.testPush()).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test push no config`() = runTest {
|
||||
val aPushProvider = FakePushProvider()
|
||||
val defaultPushService = createDefaultPushService(
|
||||
pushProviders = setOf(aPushProvider),
|
||||
getCurrentPushProvider = FakeGetCurrentPushProvider(currentPushProvider = aPushProvider.name),
|
||||
)
|
||||
assertThat(defaultPushService.testPush()).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test push ok`() = runTest {
|
||||
val aConfig = CurrentUserPushConfig(
|
||||
url = "aUrl",
|
||||
pushKey = "aPushKey",
|
||||
)
|
||||
val testPushResult = lambdaRecorder<CurrentUserPushConfig, Unit> { }
|
||||
val aPushProvider = FakePushProvider(
|
||||
currentUserPushConfig = aConfig
|
||||
)
|
||||
val defaultPushService = createDefaultPushService(
|
||||
pushProviders = setOf(aPushProvider),
|
||||
getCurrentPushProvider = FakeGetCurrentPushProvider(currentPushProvider = aPushProvider.name),
|
||||
testPush = FakeTestPush(executeResult = testPushResult),
|
||||
)
|
||||
assertThat(defaultPushService.testPush()).isTrue()
|
||||
testPushResult.assertions()
|
||||
.isCalledOnce()
|
||||
.with(value(aConfig))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getCurrentPushProvider null`() = runTest {
|
||||
val defaultPushService = createDefaultPushService()
|
||||
val result = defaultPushService.getCurrentPushProvider()
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getCurrentPushProvider ok`() = runTest {
|
||||
val aPushProvider = FakePushProvider()
|
||||
val defaultPushService = createDefaultPushService(
|
||||
pushProviders = setOf(aPushProvider),
|
||||
getCurrentPushProvider = FakeGetCurrentPushProvider(currentPushProvider = aPushProvider.name),
|
||||
)
|
||||
val result = defaultPushService.getCurrentPushProvider()
|
||||
assertThat(result).isEqualTo(aPushProvider)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getAvailablePushProviders empty`() = runTest {
|
||||
val defaultPushService = createDefaultPushService()
|
||||
val result = defaultPushService.getAvailablePushProviders()
|
||||
assertThat(result).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `registerWith ok`() = runTest {
|
||||
val client = FakeMatrixClient()
|
||||
val aPushProvider = FakePushProvider(
|
||||
registerWithResult = { _, _ -> Result.success(Unit) },
|
||||
)
|
||||
val aDistributor = Distributor("aValue", "aName")
|
||||
val defaultPushService = createDefaultPushService()
|
||||
val result = defaultPushService.registerWith(client, aPushProvider, aDistributor)
|
||||
assertThat(result).isEqualTo(Result.success(Unit))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `registerWith fail to register`() = runTest {
|
||||
val client = FakeMatrixClient()
|
||||
val aPushProvider = FakePushProvider(
|
||||
registerWithResult = { _, _ -> Result.failure(AN_EXCEPTION) },
|
||||
)
|
||||
val aDistributor = Distributor("aValue", "aName")
|
||||
val defaultPushService = createDefaultPushService()
|
||||
val result = defaultPushService.registerWith(client, aPushProvider, aDistributor)
|
||||
assertThat(result.isFailure).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `registerWith fail to unregister previous push provider`() = runTest {
|
||||
val client = FakeMatrixClient()
|
||||
val aCurrentPushProvider = FakePushProvider(
|
||||
unregisterWithResult = { Result.failure(AN_EXCEPTION) },
|
||||
name = "aCurrentPushProvider",
|
||||
)
|
||||
val aPushProvider = FakePushProvider(
|
||||
name = "aPushProvider",
|
||||
)
|
||||
val userPushStore = FakeUserPushStore().apply {
|
||||
setPushProviderName(aCurrentPushProvider.name)
|
||||
}
|
||||
val aDistributor = Distributor("aValue", "aName")
|
||||
val defaultPushService = createDefaultPushService(
|
||||
pushProviders = setOf(aCurrentPushProvider, aPushProvider),
|
||||
getCurrentPushProvider = FakeGetCurrentPushProvider(currentPushProvider = aCurrentPushProvider.name),
|
||||
userPushStoreFactory = FakeUserPushStoreFactory(
|
||||
userPushStore = { userPushStore },
|
||||
),
|
||||
)
|
||||
val result = defaultPushService.registerWith(client, aPushProvider, aDistributor)
|
||||
assertThat(result.isFailure).isTrue()
|
||||
assertThat(userPushStore.getPushProviderName()).isEqualTo(aCurrentPushProvider.name)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `registerWith unregister previous push provider and register new OK`() = runTest {
|
||||
val client = FakeMatrixClient()
|
||||
val unregisterLambda = lambdaRecorder<MatrixClient, Result<Unit>> { Result.success(Unit) }
|
||||
val registerLambda = lambdaRecorder<MatrixClient, Distributor, Result<Unit>> { _, _ -> Result.success(Unit) }
|
||||
val aCurrentPushProvider = FakePushProvider(
|
||||
unregisterWithResult = unregisterLambda,
|
||||
name = "aCurrentPushProvider",
|
||||
)
|
||||
val aPushProvider = FakePushProvider(
|
||||
registerWithResult = registerLambda,
|
||||
name = "aPushProvider",
|
||||
)
|
||||
val userPushStore = FakeUserPushStore().apply {
|
||||
setPushProviderName(aCurrentPushProvider.name)
|
||||
}
|
||||
val aDistributor = Distributor("aValue", "aName")
|
||||
val defaultPushService = createDefaultPushService(
|
||||
pushProviders = setOf(aCurrentPushProvider, aPushProvider),
|
||||
getCurrentPushProvider = FakeGetCurrentPushProvider(currentPushProvider = aCurrentPushProvider.name),
|
||||
userPushStoreFactory = FakeUserPushStoreFactory(
|
||||
userPushStore = { userPushStore },
|
||||
),
|
||||
)
|
||||
val result = defaultPushService.registerWith(client, aPushProvider, aDistributor)
|
||||
assertThat(result.isSuccess).isTrue()
|
||||
assertThat(userPushStore.getPushProviderName()).isEqualTo(aPushProvider.name)
|
||||
unregisterLambda.assertions()
|
||||
.isCalledOnce()
|
||||
.with(value(client))
|
||||
registerLambda.assertions()
|
||||
.isCalledOnce()
|
||||
.with(value(client), value(aDistributor))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getAvailablePushProviders sorted`() = runTest {
|
||||
val aPushProvider1 = FakePushProvider(
|
||||
index = 1,
|
||||
name = "aPushProvider1",
|
||||
)
|
||||
val aPushProvider2 = FakePushProvider(
|
||||
index = 2,
|
||||
name = "aPushProvider2",
|
||||
)
|
||||
val aPushProvider3 = FakePushProvider(
|
||||
index = 3,
|
||||
name = "aPushProvider3",
|
||||
)
|
||||
val defaultPushService = createDefaultPushService(
|
||||
pushProviders = setOf(aPushProvider1, aPushProvider3, aPushProvider2),
|
||||
)
|
||||
val result = defaultPushService.getAvailablePushProviders()
|
||||
assertThat(result).containsExactly(aPushProvider1, aPushProvider2, aPushProvider3).inOrder()
|
||||
}
|
||||
|
||||
private fun createDefaultPushService(
|
||||
testPush: TestPush = FakeTestPush(),
|
||||
userPushStoreFactory: UserPushStoreFactory = FakeUserPushStoreFactory(),
|
||||
pushProviders: Set<@JvmSuppressWildcards PushProvider> = emptySet(),
|
||||
getCurrentPushProvider: GetCurrentPushProvider = FakeGetCurrentPushProvider(currentPushProvider = null),
|
||||
): DefaultPushService {
|
||||
return DefaultPushService(
|
||||
testPush = testPush,
|
||||
userPushStoreFactory = userPushStoreFactory,
|
||||
pushProviders = pushProviders,
|
||||
getCurrentPushProvider = getCurrentPushProvider,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
* 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.push.impl
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.appconfig.PushConfig
|
||||
import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.matrix.api.pusher.SetHttpPusherData
|
||||
import io.element.android.libraries.matrix.api.pusher.UnsetHttpPusherData
|
||||
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.FakeMatrixClient
|
||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||
import io.element.android.libraries.matrix.test.pushers.FakePushersService
|
||||
import io.element.android.libraries.pushstore.api.UserPushStoreFactory
|
||||
import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret
|
||||
import io.element.android.libraries.pushstore.test.userpushstore.FakeUserPushStore
|
||||
import io.element.android.libraries.pushstore.test.userpushstore.FakeUserPushStoreFactory
|
||||
import io.element.android.libraries.pushstore.test.userpushstore.clientsecret.FakePushClientSecret
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.value
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
class DefaultPusherSubscriberTest {
|
||||
@Test
|
||||
fun `test register pusher OK`() = runTest {
|
||||
testRegisterPusher(
|
||||
currentPushKey = null,
|
||||
registerResult = Result.success(Unit),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test re-register pusher OK`() = runTest {
|
||||
testRegisterPusher(
|
||||
currentPushKey = "aPushKey",
|
||||
registerResult = Result.success(Unit),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test register pusher error`() = runTest {
|
||||
testRegisterPusher(
|
||||
currentPushKey = null,
|
||||
registerResult = Result.failure(AN_EXCEPTION),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test re-register pusher error`() = runTest {
|
||||
testRegisterPusher(
|
||||
currentPushKey = "aPushKey",
|
||||
registerResult = Result.failure(AN_EXCEPTION),
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun testRegisterPusher(
|
||||
currentPushKey: String?,
|
||||
registerResult: Result<Unit>,
|
||||
) {
|
||||
val setHttpPusherResult = lambdaRecorder<SetHttpPusherData, Result<Unit>> { registerResult }
|
||||
val userPushStore = FakeUserPushStore().apply {
|
||||
setCurrentRegisteredPushKey(currentPushKey)
|
||||
}
|
||||
assertThat(userPushStore.getCurrentRegisteredPushKey()).isEqualTo(currentPushKey)
|
||||
|
||||
val matrixClient = FakeMatrixClient(
|
||||
pushersService = FakePushersService(
|
||||
setHttpPusherResult = setHttpPusherResult,
|
||||
),
|
||||
)
|
||||
val defaultPusherSubscriber = createDefaultPusherSubscriber(
|
||||
pushClientSecret = FakePushClientSecret(
|
||||
getSecretForUserResult = { A_SECRET },
|
||||
),
|
||||
userPushStoreFactory = FakeUserPushStoreFactory(
|
||||
userPushStore = { userPushStore },
|
||||
),
|
||||
)
|
||||
val result = defaultPusherSubscriber.registerPusher(
|
||||
matrixClient = matrixClient,
|
||||
pushKey = "aPushKey",
|
||||
gateway = "aGateway",
|
||||
)
|
||||
assertThat(result).isEqualTo(registerResult)
|
||||
setHttpPusherResult.assertions()
|
||||
.isCalledOnce()
|
||||
.with(
|
||||
value(
|
||||
SetHttpPusherData(
|
||||
pushKey = "aPushKey",
|
||||
appId = PushConfig.PUSHER_APP_ID,
|
||||
url = "aGateway",
|
||||
appDisplayName = "MyApp",
|
||||
deviceDisplayName = "MyDevice",
|
||||
profileTag = DEFAULT_PUSHER_FILE_TAG + "_",
|
||||
lang = "en",
|
||||
defaultPayload = "{\"cs\":\"$A_SECRET\"}",
|
||||
),
|
||||
)
|
||||
)
|
||||
assertThat(userPushStore.getCurrentRegisteredPushKey()).isEqualTo(
|
||||
if (registerResult.isSuccess) "aPushKey" else currentPushKey
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test unregister pusher OK`() = runTest {
|
||||
testUnregisterPusher(
|
||||
currentPushKey = "aPushKey",
|
||||
unregisterResult = Result.success(Unit),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test unregister pusher error`() = runTest {
|
||||
testUnregisterPusher(
|
||||
currentPushKey = "aPushKey",
|
||||
unregisterResult = Result.failure(AN_EXCEPTION),
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun testUnregisterPusher(
|
||||
currentPushKey: String?,
|
||||
unregisterResult: Result<Unit>,
|
||||
) {
|
||||
val unsetHttpPusherResult = lambdaRecorder<UnsetHttpPusherData, Result<Unit>> { unregisterResult }
|
||||
val userPushStore = FakeUserPushStore().apply {
|
||||
setCurrentRegisteredPushKey(currentPushKey)
|
||||
}
|
||||
assertThat(userPushStore.getCurrentRegisteredPushKey()).isEqualTo(currentPushKey)
|
||||
|
||||
val matrixClient = FakeMatrixClient(
|
||||
pushersService = FakePushersService(
|
||||
unsetHttpPusherResult = unsetHttpPusherResult,
|
||||
),
|
||||
)
|
||||
val defaultPusherSubscriber = createDefaultPusherSubscriber(
|
||||
pushClientSecret = FakePushClientSecret(
|
||||
getSecretForUserResult = { A_SECRET },
|
||||
),
|
||||
userPushStoreFactory = FakeUserPushStoreFactory(
|
||||
userPushStore = { userPushStore },
|
||||
),
|
||||
)
|
||||
val result = defaultPusherSubscriber.unregisterPusher(
|
||||
matrixClient = matrixClient,
|
||||
pushKey = "aPushKey",
|
||||
gateway = "aGateway",
|
||||
)
|
||||
assertThat(result).isEqualTo(unregisterResult)
|
||||
unsetHttpPusherResult.assertions()
|
||||
.isCalledOnce()
|
||||
.with(
|
||||
value(
|
||||
UnsetHttpPusherData(
|
||||
pushKey = "aPushKey",
|
||||
appId = PushConfig.PUSHER_APP_ID,
|
||||
),
|
||||
)
|
||||
)
|
||||
assertThat(userPushStore.getCurrentRegisteredPushKey()).isEqualTo(
|
||||
if (unregisterResult.isSuccess) null else currentPushKey
|
||||
)
|
||||
}
|
||||
|
||||
private fun createDefaultPusherSubscriber(
|
||||
buildMeta: BuildMeta = aBuildMeta(applicationName = "MyApp"),
|
||||
userPushStoreFactory: UserPushStoreFactory = FakeUserPushStoreFactory(),
|
||||
pushClientSecret: PushClientSecret = FakePushClientSecret(),
|
||||
): DefaultPusherSubscriber {
|
||||
return DefaultPusherSubscriber(
|
||||
buildMeta = buildMeta,
|
||||
pushClientSecret = pushClientSecret,
|
||||
userPushStoreFactory = userPushStoreFactory,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -59,17 +59,17 @@ import org.robolectric.RuntimeEnvironment
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class NotifiableEventResolverTest {
|
||||
class DefaultNotifiableEventResolverTest {
|
||||
@Test
|
||||
fun `resolve event no session`() = runTest {
|
||||
val sut = createNotifiableEventResolver(notificationService = null)
|
||||
val sut = createDefaultNotifiableEventResolver(notificationService = null)
|
||||
val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID)
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `resolve event failure`() = runTest {
|
||||
val sut = createNotifiableEventResolver(
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.failure(AN_EXCEPTION)
|
||||
)
|
||||
val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID)
|
||||
@@ -78,7 +78,7 @@ class NotifiableEventResolverTest {
|
||||
|
||||
@Test
|
||||
fun `resolve event null`() = runTest {
|
||||
val sut = createNotifiableEventResolver(
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(null)
|
||||
)
|
||||
val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID)
|
||||
@@ -87,7 +87,7 @@ class NotifiableEventResolverTest {
|
||||
|
||||
@Test
|
||||
fun `resolve event message text`() = runTest {
|
||||
val sut = createNotifiableEventResolver(
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
createNotificationData(
|
||||
content = NotificationContent.MessageLike.RoomMessage(
|
||||
@@ -105,7 +105,7 @@ class NotifiableEventResolverTest {
|
||||
@Test
|
||||
@Config(qualifiers = "en")
|
||||
fun `resolve event message with mention`() = runTest {
|
||||
val sut = createNotifiableEventResolver(
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
createNotificationData(
|
||||
content = NotificationContent.MessageLike.RoomMessage(
|
||||
@@ -123,7 +123,7 @@ class NotifiableEventResolverTest {
|
||||
|
||||
@Test
|
||||
fun `resolve HTML formatted event message text takes plain text version`() = runTest {
|
||||
val sut = createNotifiableEventResolver(
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
createNotificationData(
|
||||
content = NotificationContent.MessageLike.RoomMessage(
|
||||
@@ -146,7 +146,7 @@ class NotifiableEventResolverTest {
|
||||
|
||||
@Test
|
||||
fun `resolve incorrectly formatted event message text uses fallback`() = runTest {
|
||||
val sut = createNotifiableEventResolver(
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
createNotificationData(
|
||||
content = NotificationContent.MessageLike.RoomMessage(
|
||||
@@ -169,7 +169,7 @@ class NotifiableEventResolverTest {
|
||||
|
||||
@Test
|
||||
fun `resolve event message audio`() = runTest {
|
||||
val sut = createNotifiableEventResolver(
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
createNotificationData(
|
||||
content = NotificationContent.MessageLike.RoomMessage(
|
||||
@@ -186,7 +186,7 @@ class NotifiableEventResolverTest {
|
||||
|
||||
@Test
|
||||
fun `resolve event message video`() = runTest {
|
||||
val sut = createNotifiableEventResolver(
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
createNotificationData(
|
||||
content = NotificationContent.MessageLike.RoomMessage(
|
||||
@@ -203,7 +203,7 @@ class NotifiableEventResolverTest {
|
||||
|
||||
@Test
|
||||
fun `resolve event message voice`() = runTest {
|
||||
val sut = createNotifiableEventResolver(
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
createNotificationData(
|
||||
content = NotificationContent.MessageLike.RoomMessage(
|
||||
@@ -220,7 +220,7 @@ class NotifiableEventResolverTest {
|
||||
|
||||
@Test
|
||||
fun `resolve event message image`() = runTest {
|
||||
val sut = createNotifiableEventResolver(
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
createNotificationData(
|
||||
content = NotificationContent.MessageLike.RoomMessage(
|
||||
@@ -237,7 +237,7 @@ class NotifiableEventResolverTest {
|
||||
|
||||
@Test
|
||||
fun `resolve event message sticker`() = runTest {
|
||||
val sut = createNotifiableEventResolver(
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
createNotificationData(
|
||||
content = NotificationContent.MessageLike.RoomMessage(
|
||||
@@ -254,7 +254,7 @@ class NotifiableEventResolverTest {
|
||||
|
||||
@Test
|
||||
fun `resolve event message file`() = runTest {
|
||||
val sut = createNotifiableEventResolver(
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
createNotificationData(
|
||||
content = NotificationContent.MessageLike.RoomMessage(
|
||||
@@ -271,7 +271,7 @@ class NotifiableEventResolverTest {
|
||||
|
||||
@Test
|
||||
fun `resolve event message location`() = runTest {
|
||||
val sut = createNotifiableEventResolver(
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
createNotificationData(
|
||||
content = NotificationContent.MessageLike.RoomMessage(
|
||||
@@ -288,7 +288,7 @@ class NotifiableEventResolverTest {
|
||||
|
||||
@Test
|
||||
fun `resolve event message notice`() = runTest {
|
||||
val sut = createNotifiableEventResolver(
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
createNotificationData(
|
||||
content = NotificationContent.MessageLike.RoomMessage(
|
||||
@@ -305,7 +305,7 @@ class NotifiableEventResolverTest {
|
||||
|
||||
@Test
|
||||
fun `resolve event message emote`() = runTest {
|
||||
val sut = createNotifiableEventResolver(
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
createNotificationData(
|
||||
content = NotificationContent.MessageLike.RoomMessage(
|
||||
@@ -322,7 +322,7 @@ class NotifiableEventResolverTest {
|
||||
|
||||
@Test
|
||||
fun `resolve poll`() = runTest {
|
||||
val sut = createNotifiableEventResolver(
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
createNotificationData(
|
||||
content = NotificationContent.MessageLike.Poll(
|
||||
@@ -339,7 +339,7 @@ class NotifiableEventResolverTest {
|
||||
|
||||
@Test
|
||||
fun `resolve RoomMemberContent invite room`() = runTest {
|
||||
val sut = createNotifiableEventResolver(
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
createNotificationData(
|
||||
content = NotificationContent.StateEvent.RoomMemberContent(
|
||||
@@ -372,7 +372,7 @@ class NotifiableEventResolverTest {
|
||||
|
||||
@Test
|
||||
fun `resolve RoomMemberContent invite direct`() = runTest {
|
||||
val sut = createNotifiableEventResolver(
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
createNotificationData(
|
||||
content = NotificationContent.StateEvent.RoomMemberContent(
|
||||
@@ -405,7 +405,7 @@ class NotifiableEventResolverTest {
|
||||
|
||||
@Test
|
||||
fun `resolve RoomMemberContent other`() = runTest {
|
||||
val sut = createNotifiableEventResolver(
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
createNotificationData(
|
||||
content = NotificationContent.StateEvent.RoomMemberContent(
|
||||
@@ -421,7 +421,7 @@ class NotifiableEventResolverTest {
|
||||
|
||||
@Test
|
||||
fun `resolve RoomEncrypted`() = runTest {
|
||||
val sut = createNotifiableEventResolver(
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
createNotificationData(
|
||||
content = NotificationContent.MessageLike.RoomEncrypted
|
||||
@@ -445,7 +445,7 @@ class NotifiableEventResolverTest {
|
||||
|
||||
@Test
|
||||
fun `resolve CallInvite`() = runTest {
|
||||
val sut = createNotifiableEventResolver(
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
createNotificationData(
|
||||
content = NotificationContent.MessageLike.CallInvite(A_USER_ID_2)
|
||||
@@ -517,7 +517,7 @@ class NotifiableEventResolverTest {
|
||||
}
|
||||
|
||||
private fun testNull(content: NotificationContent) = runTest {
|
||||
val sut = createNotifiableEventResolver(
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
createNotificationData(
|
||||
content = content
|
||||
@@ -528,10 +528,10 @@ class NotifiableEventResolverTest {
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
private fun createNotifiableEventResolver(
|
||||
private fun createDefaultNotifiableEventResolver(
|
||||
notificationService: FakeNotificationService? = FakeNotificationService(),
|
||||
notificationResult: Result<NotificationData?> = Result.success(null),
|
||||
): NotifiableEventResolver {
|
||||
): DefaultNotifiableEventResolver {
|
||||
val context = RuntimeEnvironment.getApplication() as Context
|
||||
notificationService?.givenGetNotificationResult(notificationResult)
|
||||
val matrixClientProvider = FakeMatrixClientProvider(getClient = {
|
||||
@@ -544,7 +544,7 @@ class NotifiableEventResolverTest {
|
||||
val notificationMediaRepoFactory = NotificationMediaRepo.Factory {
|
||||
FakeNotificationMediaRepo()
|
||||
}
|
||||
return NotifiableEventResolver(
|
||||
return DefaultNotifiableEventResolver(
|
||||
stringProvider = AndroidStringProvider(context.resources),
|
||||
clock = FakeSystemClock(),
|
||||
matrixClientProvider = matrixClientProvider,
|
||||
@@ -59,7 +59,6 @@ class DefaultNotificationDrawerManagerTest {
|
||||
fun `cover all APIs`() = runTest {
|
||||
// For now just call all the API. Later, add more valuable tests.
|
||||
val defaultNotificationDrawerManager = createDefaultNotificationDrawerManager()
|
||||
defaultNotificationDrawerManager.notificationStyleChanged()
|
||||
defaultNotificationDrawerManager.clearAllMessagesEvents(A_SESSION_ID, doRender = true)
|
||||
defaultNotificationDrawerManager.clearAllMessagesEvents(A_SESSION_ID, doRender = false)
|
||||
defaultNotificationDrawerManager.clearEvent(A_SESSION_ID, AN_EVENT_ID, doRender = true)
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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.push.impl.notifications
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
|
||||
class FakeNotifiableEventResolver(
|
||||
private val notifiableEventResult: (SessionId, RoomId, EventId) -> NotifiableEvent? = { _, _, _ -> lambdaError() }
|
||||
) : NotifiableEventResolver {
|
||||
override suspend fun resolveEvent(sessionId: SessionId, roomId: RoomId, eventId: EventId): NotifiableEvent? {
|
||||
return notifiableEventResult(sessionId, roomId, eventId)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
/*
|
||||
* 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.push.impl.push
|
||||
|
||||
import app.cash.turbine.test
|
||||
import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
import io.element.android.libraries.matrix.test.A_SECRET
|
||||
import io.element.android.libraries.matrix.test.A_USER_ID
|
||||
import io.element.android.libraries.matrix.test.auth.FakeAuthenticationService
|
||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||
import io.element.android.libraries.push.impl.notifications.FakeNotifiableEventResolver
|
||||
import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent
|
||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent
|
||||
import io.element.android.libraries.push.impl.test.DefaultTestPush
|
||||
import io.element.android.libraries.push.impl.troubleshoot.DiagnosticPushHandler
|
||||
import io.element.android.libraries.pushproviders.api.PushData
|
||||
import io.element.android.libraries.pushstore.api.UserPushStore
|
||||
import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret
|
||||
import io.element.android.libraries.pushstore.test.userpushstore.FakeUserPushStore
|
||||
import io.element.android.libraries.pushstore.test.userpushstore.FakeUserPushStoreFactory
|
||||
import io.element.android.libraries.pushstore.test.userpushstore.clientsecret.FakePushClientSecret
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.value
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
class DefaultPushHandlerTest {
|
||||
@Test
|
||||
fun `when classical PushData is received, the notification drawer is informed`() = runTest {
|
||||
val aNotifiableMessageEvent = aNotifiableMessageEvent()
|
||||
val notifiableEventResult =
|
||||
lambdaRecorder<SessionId, RoomId, EventId, NotifiableEvent> { _, _, _ -> aNotifiableMessageEvent }
|
||||
val onNotifiableEventReceived = lambdaRecorder<NotifiableEvent, Unit> {}
|
||||
val incrementPushCounterResult = lambdaRecorder<Unit> {}
|
||||
val aPushData = PushData(
|
||||
eventId = AN_EVENT_ID,
|
||||
roomId = A_ROOM_ID,
|
||||
unread = 0,
|
||||
clientSecret = A_SECRET,
|
||||
)
|
||||
val defaultPushHandler = createDefaultPushHandler(
|
||||
onNotifiableEventReceived = onNotifiableEventReceived,
|
||||
notifiableEventResult = notifiableEventResult,
|
||||
pushClientSecret = FakePushClientSecret(
|
||||
getUserIdFromSecretResult = { A_USER_ID }
|
||||
),
|
||||
incrementPushCounterResult = incrementPushCounterResult
|
||||
)
|
||||
defaultPushHandler.handle(aPushData)
|
||||
incrementPushCounterResult.assertions()
|
||||
.isCalledOnce()
|
||||
notifiableEventResult.assertions()
|
||||
.isCalledOnce()
|
||||
.with(value(A_USER_ID), value(A_ROOM_ID), value(AN_EVENT_ID))
|
||||
onNotifiableEventReceived.assertions()
|
||||
.isCalledOnce()
|
||||
.with(value(aNotifiableMessageEvent))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when classical PushData is received, but notifications are disabled, nothing happen`() =
|
||||
runTest {
|
||||
val aNotifiableMessageEvent = aNotifiableMessageEvent()
|
||||
val notifiableEventResult =
|
||||
lambdaRecorder<SessionId, RoomId, EventId, NotifiableEvent> { _, _, _ -> aNotifiableMessageEvent }
|
||||
val onNotifiableEventReceived = lambdaRecorder<NotifiableEvent, Unit> {}
|
||||
val incrementPushCounterResult = lambdaRecorder<Unit> {}
|
||||
val aPushData = PushData(
|
||||
eventId = AN_EVENT_ID,
|
||||
roomId = A_ROOM_ID,
|
||||
unread = 0,
|
||||
clientSecret = A_SECRET,
|
||||
)
|
||||
val defaultPushHandler = createDefaultPushHandler(
|
||||
onNotifiableEventReceived = onNotifiableEventReceived,
|
||||
notifiableEventResult = notifiableEventResult,
|
||||
pushClientSecret = FakePushClientSecret(
|
||||
getUserIdFromSecretResult = { A_USER_ID }
|
||||
),
|
||||
userPushStore = FakeUserPushStore().apply {
|
||||
setNotificationEnabledForDevice(false)
|
||||
},
|
||||
incrementPushCounterResult = incrementPushCounterResult
|
||||
)
|
||||
defaultPushHandler.handle(aPushData)
|
||||
incrementPushCounterResult.assertions()
|
||||
.isCalledOnce()
|
||||
notifiableEventResult.assertions()
|
||||
.isNeverCalled()
|
||||
onNotifiableEventReceived.assertions()
|
||||
.isNeverCalled()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when PushData is received, but client secret is not known, fallback the latest session`() =
|
||||
runTest {
|
||||
val aNotifiableMessageEvent = aNotifiableMessageEvent()
|
||||
val notifiableEventResult =
|
||||
lambdaRecorder<SessionId, RoomId, EventId, NotifiableEvent> { _, _, _ -> aNotifiableMessageEvent }
|
||||
val onNotifiableEventReceived = lambdaRecorder<NotifiableEvent, Unit> {}
|
||||
val incrementPushCounterResult = lambdaRecorder<Unit> {}
|
||||
val aPushData = PushData(
|
||||
eventId = AN_EVENT_ID,
|
||||
roomId = A_ROOM_ID,
|
||||
unread = 0,
|
||||
clientSecret = A_SECRET,
|
||||
)
|
||||
val defaultPushHandler = createDefaultPushHandler(
|
||||
onNotifiableEventReceived = onNotifiableEventReceived,
|
||||
notifiableEventResult = notifiableEventResult,
|
||||
pushClientSecret = FakePushClientSecret(
|
||||
getUserIdFromSecretResult = { null }
|
||||
),
|
||||
matrixAuthenticationService = FakeAuthenticationService().apply {
|
||||
getLatestSessionIdLambda = { A_USER_ID }
|
||||
},
|
||||
incrementPushCounterResult = incrementPushCounterResult
|
||||
)
|
||||
defaultPushHandler.handle(aPushData)
|
||||
incrementPushCounterResult.assertions()
|
||||
.isCalledOnce()
|
||||
notifiableEventResult.assertions()
|
||||
.isCalledOnce()
|
||||
.with(value(A_USER_ID), value(A_ROOM_ID), value(AN_EVENT_ID))
|
||||
onNotifiableEventReceived.assertions()
|
||||
.isCalledOnce()
|
||||
.with(value(aNotifiableMessageEvent))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when PushData is received, but client secret is not known, and there is no latest session, nothing happen`() =
|
||||
runTest {
|
||||
val aNotifiableMessageEvent = aNotifiableMessageEvent()
|
||||
val notifiableEventResult =
|
||||
lambdaRecorder<SessionId, RoomId, EventId, NotifiableEvent> { _, _, _ -> aNotifiableMessageEvent }
|
||||
val onNotifiableEventReceived = lambdaRecorder<NotifiableEvent, Unit> {}
|
||||
val incrementPushCounterResult = lambdaRecorder<Unit> {}
|
||||
val aPushData = PushData(
|
||||
eventId = AN_EVENT_ID,
|
||||
roomId = A_ROOM_ID,
|
||||
unread = 0,
|
||||
clientSecret = A_SECRET,
|
||||
)
|
||||
val defaultPushHandler = createDefaultPushHandler(
|
||||
onNotifiableEventReceived = onNotifiableEventReceived,
|
||||
notifiableEventResult = notifiableEventResult,
|
||||
pushClientSecret = FakePushClientSecret(
|
||||
getUserIdFromSecretResult = { null }
|
||||
),
|
||||
matrixAuthenticationService = FakeAuthenticationService().apply {
|
||||
getLatestSessionIdLambda = { null }
|
||||
},
|
||||
incrementPushCounterResult = incrementPushCounterResult
|
||||
)
|
||||
defaultPushHandler.handle(aPushData)
|
||||
incrementPushCounterResult.assertions()
|
||||
.isCalledOnce()
|
||||
notifiableEventResult.assertions()
|
||||
.isNeverCalled()
|
||||
onNotifiableEventReceived.assertions()
|
||||
.isNeverCalled()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when classical PushData is received, but not able to resolve the event, nothing happen`() =
|
||||
runTest {
|
||||
val notifiableEventResult =
|
||||
lambdaRecorder<SessionId, RoomId, EventId, NotifiableEvent?> { _, _, _ -> null }
|
||||
val onNotifiableEventReceived = lambdaRecorder<NotifiableEvent, Unit> {}
|
||||
val incrementPushCounterResult = lambdaRecorder<Unit> {}
|
||||
val aPushData = PushData(
|
||||
eventId = AN_EVENT_ID,
|
||||
roomId = A_ROOM_ID,
|
||||
unread = 0,
|
||||
clientSecret = A_SECRET,
|
||||
)
|
||||
val defaultPushHandler = createDefaultPushHandler(
|
||||
onNotifiableEventReceived = onNotifiableEventReceived,
|
||||
notifiableEventResult = notifiableEventResult,
|
||||
buildMeta = aBuildMeta(
|
||||
// Also test `lowPrivacyLoggingEnabled = false` here
|
||||
lowPrivacyLoggingEnabled = false
|
||||
),
|
||||
pushClientSecret = FakePushClientSecret(
|
||||
getUserIdFromSecretResult = { A_USER_ID }
|
||||
),
|
||||
incrementPushCounterResult = incrementPushCounterResult
|
||||
)
|
||||
defaultPushHandler.handle(aPushData)
|
||||
incrementPushCounterResult.assertions()
|
||||
.isCalledOnce()
|
||||
notifiableEventResult.assertions()
|
||||
.isCalledOnce()
|
||||
.with(value(A_USER_ID), value(A_ROOM_ID), value(AN_EVENT_ID))
|
||||
onNotifiableEventReceived.assertions()
|
||||
.isNeverCalled()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when diagnostic PushData is received, the diagnostic push handler is informed `() =
|
||||
runTest {
|
||||
val aPushData = PushData(
|
||||
eventId = DefaultTestPush.TEST_EVENT_ID,
|
||||
roomId = A_ROOM_ID,
|
||||
unread = 0,
|
||||
clientSecret = A_SECRET,
|
||||
)
|
||||
val diagnosticPushHandler = DiagnosticPushHandler()
|
||||
val defaultPushHandler = createDefaultPushHandler(
|
||||
diagnosticPushHandler = diagnosticPushHandler,
|
||||
incrementPushCounterResult = { }
|
||||
)
|
||||
diagnosticPushHandler.state.test {
|
||||
defaultPushHandler.handle(aPushData)
|
||||
awaitItem()
|
||||
}
|
||||
}
|
||||
|
||||
private fun createDefaultPushHandler(
|
||||
onNotifiableEventReceived: (NotifiableEvent) -> Unit = { lambdaError() },
|
||||
notifiableEventResult: (SessionId, RoomId, EventId) -> NotifiableEvent? = { _, _, _ -> lambdaError() },
|
||||
incrementPushCounterResult: () -> Unit = { lambdaError() },
|
||||
userPushStore: UserPushStore = FakeUserPushStore(),
|
||||
pushClientSecret: PushClientSecret = FakePushClientSecret(),
|
||||
buildMeta: BuildMeta = aBuildMeta(),
|
||||
matrixAuthenticationService: MatrixAuthenticationService = FakeAuthenticationService(),
|
||||
diagnosticPushHandler: DiagnosticPushHandler = DiagnosticPushHandler(),
|
||||
): DefaultPushHandler {
|
||||
return DefaultPushHandler(
|
||||
onNotifiableEventReceived = FakeOnNotifiableEventReceived(onNotifiableEventReceived),
|
||||
notifiableEventResolver = FakeNotifiableEventResolver(notifiableEventResult),
|
||||
incrementPushDataStore = object : IncrementPushDataStore {
|
||||
override suspend fun incrementPushCounter() {
|
||||
incrementPushCounterResult()
|
||||
}
|
||||
},
|
||||
userPushStoreFactory = FakeUserPushStoreFactory { userPushStore },
|
||||
pushClientSecret = pushClientSecret,
|
||||
buildMeta = buildMeta,
|
||||
matrixAuthenticationService = matrixAuthenticationService,
|
||||
diagnosticPushHandler = diagnosticPushHandler,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
*
|
||||
* 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.push.impl.push
|
||||
|
||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent
|
||||
|
||||
class FakeOnNotifiableEventReceived(
|
||||
private val onNotifiableEventReceivedResult: (NotifiableEvent) -> Unit,
|
||||
) : OnNotifiableEventReceived {
|
||||
override fun onNotifiableEventReceived(notifiableEvent: NotifiableEvent) {
|
||||
onNotifiableEventReceivedResult(notifiableEvent)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* 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.push.impl.pushgateway
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.push.api.gateway.PushGatewayFailure
|
||||
import io.element.android.libraries.push.impl.test.DefaultTestPush
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Assert.assertThrows
|
||||
import org.junit.Test
|
||||
|
||||
class DefaultPushGatewayNotifyRequestTest {
|
||||
@Test
|
||||
fun `notify success`() = runTest {
|
||||
val factory = FakePushGatewayApiFactory(
|
||||
notifyResponse = {
|
||||
PushGatewayNotifyResponse(
|
||||
rejectedPushKeys = emptyList()
|
||||
)
|
||||
}
|
||||
)
|
||||
val pushGatewayNotifyRequest = DefaultPushGatewayNotifyRequest(
|
||||
pushGatewayApiFactory = factory,
|
||||
)
|
||||
pushGatewayNotifyRequest.execute(
|
||||
PushGatewayNotifyRequest.Params(
|
||||
url = "aUrl",
|
||||
appId = "anAppId",
|
||||
pushKey = "aPushKey",
|
||||
eventId = DefaultTestPush.TEST_EVENT_ID,
|
||||
roomId = DefaultTestPush.TEST_ROOM_ID,
|
||||
)
|
||||
)
|
||||
assertThat(factory.baseUrlParameter).isEqualTo("aUrl")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `notify success, url is stripped`() = runTest {
|
||||
val factory = FakePushGatewayApiFactory(
|
||||
notifyResponse = {
|
||||
PushGatewayNotifyResponse(
|
||||
rejectedPushKeys = emptyList()
|
||||
)
|
||||
}
|
||||
)
|
||||
val pushGatewayNotifyRequest = DefaultPushGatewayNotifyRequest(
|
||||
pushGatewayApiFactory = factory,
|
||||
)
|
||||
pushGatewayNotifyRequest.execute(
|
||||
PushGatewayNotifyRequest.Params(
|
||||
url = "aUrl" + PushGatewayConfig.URI_PUSH_GATEWAY_PREFIX_PATH,
|
||||
appId = "anAppId",
|
||||
pushKey = "aPushKey",
|
||||
eventId = DefaultTestPush.TEST_EVENT_ID,
|
||||
roomId = DefaultTestPush.TEST_ROOM_ID,
|
||||
)
|
||||
)
|
||||
assertThat(factory.baseUrlParameter).isEqualTo("aUrl")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `notify with rejected push key should throw expected Exception`() {
|
||||
val factory = FakePushGatewayApiFactory(
|
||||
notifyResponse = {
|
||||
PushGatewayNotifyResponse(
|
||||
rejectedPushKeys = listOf("aPushKey")
|
||||
)
|
||||
}
|
||||
)
|
||||
val pushGatewayNotifyRequest = DefaultPushGatewayNotifyRequest(
|
||||
pushGatewayApiFactory = factory,
|
||||
)
|
||||
assertThrows(PushGatewayFailure.PusherRejected::class.java) {
|
||||
runTest {
|
||||
pushGatewayNotifyRequest.execute(
|
||||
PushGatewayNotifyRequest.Params(
|
||||
url = "aUrl",
|
||||
appId = "anAppId",
|
||||
pushKey = "aPushKey",
|
||||
eventId = DefaultTestPush.TEST_EVENT_ID,
|
||||
roomId = DefaultTestPush.TEST_ROOM_ID,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
assertThat(factory.baseUrlParameter).isEqualTo("aUrl")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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.push.impl.pushgateway
|
||||
|
||||
class FakePushGatewayApiFactory(
|
||||
private val notifyResponse: () -> PushGatewayNotifyResponse
|
||||
) : PushGatewayApiFactory {
|
||||
var baseUrlParameter: String? = null
|
||||
private set
|
||||
|
||||
override fun create(baseUrl: String): PushGatewayAPI {
|
||||
baseUrlParameter = baseUrl
|
||||
return FakePushGatewayAPI(notifyResponse)
|
||||
}
|
||||
}
|
||||
|
||||
class FakePushGatewayAPI(
|
||||
private val notifyResponse: () -> PushGatewayNotifyResponse
|
||||
) : PushGatewayAPI {
|
||||
override suspend fun notify(body: PushGatewayNotifyBody): PushGatewayNotifyResponse {
|
||||
return notifyResponse()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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.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.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.value
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
class DefaultTestPushTest {
|
||||
@Test
|
||||
fun `test DefaultTestPush`() = runTest {
|
||||
val executeResult = lambdaRecorder<PushGatewayNotifyRequest.Params, Unit> { }
|
||||
val defaultTestPush = DefaultTestPush(
|
||||
pushGatewayNotifyRequest = FakePushGatewayNotifyRequest(
|
||||
executeResult = executeResult,
|
||||
)
|
||||
)
|
||||
val aConfig = CurrentUserPushConfig(
|
||||
url = "aUrl",
|
||||
pushKey = "aPushKey",
|
||||
)
|
||||
defaultTestPush.execute(aConfig)
|
||||
executeResult.assertions()
|
||||
.isCalledOnce()
|
||||
.with(
|
||||
value(
|
||||
PushGatewayNotifyRequest.Params(
|
||||
url = aConfig.url,
|
||||
appId = PushConfig.PUSHER_APP_ID,
|
||||
pushKey = aConfig.pushKey,
|
||||
eventId = DefaultTestPush.TEST_EVENT_ID,
|
||||
roomId = DefaultTestPush.TEST_ROOM_ID,
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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.push.impl.test
|
||||
|
||||
import io.element.android.libraries.push.impl.pushgateway.PushGatewayNotifyRequest
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
|
||||
class FakePushGatewayNotifyRequest(
|
||||
private val executeResult: (PushGatewayNotifyRequest.Params) -> Unit = { lambdaError() }
|
||||
) : PushGatewayNotifyRequest {
|
||||
override suspend fun execute(params: PushGatewayNotifyRequest.Params) {
|
||||
executeResult(params)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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.push.impl.test
|
||||
|
||||
import io.element.android.libraries.pushproviders.api.CurrentUserPushConfig
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
|
||||
class FakeTestPush(
|
||||
private val executeResult: (CurrentUserPushConfig) -> Unit = { lambdaError() }
|
||||
) : TestPush {
|
||||
override suspend fun execute(config: CurrentUserPushConfig) {
|
||||
executeResult(config)
|
||||
}
|
||||
}
|
||||
@@ -29,9 +29,6 @@ class FakePushService(
|
||||
Result.success(Unit)
|
||||
},
|
||||
) : PushService {
|
||||
override fun notificationStyleChanged() {
|
||||
}
|
||||
|
||||
override suspend fun getCurrentPushProvider(): PushProvider? {
|
||||
return availablePushProviders.firstOrNull()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.push.test
|
||||
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.pushproviders.api.PusherSubscriber
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
|
||||
class FakePusherSubscriber(
|
||||
private val registerPusherResult: (MatrixClient, String, String) -> Result<Unit> = { _, _, _ -> lambdaError() },
|
||||
private val unregisterPusherResult: (MatrixClient, String, String) -> Result<Unit> = { _, _, _ -> lambdaError() },
|
||||
) : PusherSubscriber {
|
||||
override suspend fun registerPusher(matrixClient: MatrixClient, pushKey: String, gateway: String): Result<Unit> {
|
||||
return registerPusherResult(matrixClient, pushKey, gateway)
|
||||
}
|
||||
|
||||
override suspend fun unregisterPusher(matrixClient: MatrixClient, pushKey: String, gateway: String): Result<Unit> {
|
||||
return unregisterPusherResult(matrixClient, pushKey, gateway)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
*
|
||||
* 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.push.test.test
|
||||
|
||||
import io.element.android.libraries.pushproviders.api.PushData
|
||||
import io.element.android.libraries.pushproviders.api.PushHandler
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
|
||||
class FakePushHandler(
|
||||
private val handleResult: (PushData) -> Unit = { lambdaError() }
|
||||
) : PushHandler {
|
||||
override suspend fun handle(pushData: PushData) {
|
||||
handleResult(pushData)
|
||||
}
|
||||
}
|
||||
@@ -58,7 +58,11 @@ dependencies {
|
||||
testImplementation(libs.test.junit)
|
||||
testImplementation(libs.test.truth)
|
||||
testImplementation(libs.test.turbine)
|
||||
testImplementation(libs.test.robolectric)
|
||||
testImplementation(projects.libraries.matrix.test)
|
||||
testImplementation(projects.libraries.push.test)
|
||||
testImplementation(projects.libraries.pushstore.test)
|
||||
testImplementation(projects.libraries.sessionStorage.implMemory)
|
||||
testImplementation(projects.tests.testutils)
|
||||
testImplementation(projects.services.toolbox.test)
|
||||
}
|
||||
|
||||
@@ -16,8 +16,10 @@
|
||||
|
||||
package io.element.android.libraries.pushproviders.firebase
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.core.extensions.flatMap
|
||||
import io.element.android.libraries.core.log.logger.LoggerTag
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.pushproviders.api.PusherSubscriber
|
||||
@@ -32,14 +34,19 @@ private val loggerTag = LoggerTag("FirebaseNewTokenHandler", LoggerTag.PushLogge
|
||||
/**
|
||||
* Handle new token receive from Firebase. Will update all the sessions which are using Firebase as a push provider.
|
||||
*/
|
||||
class FirebaseNewTokenHandler @Inject constructor(
|
||||
interface FirebaseNewTokenHandler {
|
||||
suspend fun handle(firebaseToken: String)
|
||||
}
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultFirebaseNewTokenHandler @Inject constructor(
|
||||
private val pusherSubscriber: PusherSubscriber,
|
||||
private val sessionStore: SessionStore,
|
||||
private val userPushStoreFactory: UserPushStoreFactory,
|
||||
private val matrixAuthenticationService: MatrixAuthenticationService,
|
||||
private val firebaseStore: FirebaseStore,
|
||||
) {
|
||||
suspend fun handle(firebaseToken: String) {
|
||||
) : FirebaseNewTokenHandler {
|
||||
override suspend fun handle(firebaseToken: String) {
|
||||
firebaseStore.storeFcmToken(firebaseToken)
|
||||
// Register the pusher for all the sessions
|
||||
sessionStore.getAllSessions().toUserList()
|
||||
@@ -53,14 +60,15 @@ class FirebaseNewTokenHandler @Inject constructor(
|
||||
Timber.tag(loggerTag.value).e(it, "Failed to restore session $sessionId")
|
||||
}
|
||||
.flatMap { client ->
|
||||
pusherSubscriber.registerPusher(
|
||||
matrixClient = client,
|
||||
pushKey = firebaseToken,
|
||||
gateway = FirebaseConfig.PUSHER_HTTP_URL,
|
||||
)
|
||||
}
|
||||
.onFailure {
|
||||
Timber.tag(loggerTag.value).e(it, "Failed to register pusher for session $sessionId")
|
||||
pusherSubscriber
|
||||
.registerPusher(
|
||||
matrixClient = client,
|
||||
pushKey = firebaseToken,
|
||||
gateway = FirebaseConfig.PUSHER_HTTP_URL,
|
||||
)
|
||||
.onFailure {
|
||||
Timber.tag(loggerTag.value).e(it, "Failed to register pusher for session $sessionId")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Timber.tag(loggerTag.value).d("This session is not using Firebase pusher")
|
||||
|
||||
@@ -22,7 +22,6 @@ import io.element.android.libraries.architecture.bindings
|
||||
import io.element.android.libraries.core.log.logger.LoggerTag
|
||||
import io.element.android.libraries.pushproviders.api.PushHandler
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
@@ -33,8 +32,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
|
||||
@Inject lateinit var firebaseNewTokenHandler: FirebaseNewTokenHandler
|
||||
@Inject lateinit var pushParser: FirebasePushParser
|
||||
@Inject lateinit var pushHandler: PushHandler
|
||||
|
||||
private val coroutineScope = CoroutineScope(SupervisorJob())
|
||||
@Inject lateinit var coroutineScope: CoroutineScope
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* 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.firebase
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||
import io.element.android.libraries.matrix.test.A_USER_ID
|
||||
import io.element.android.libraries.matrix.test.A_USER_ID_2
|
||||
import io.element.android.libraries.matrix.test.A_USER_ID_3
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.libraries.matrix.test.auth.FakeAuthenticationService
|
||||
import io.element.android.libraries.push.test.FakePusherSubscriber
|
||||
import io.element.android.libraries.pushproviders.api.PusherSubscriber
|
||||
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
|
||||
import io.element.android.libraries.sessionstorage.api.LoginType
|
||||
import io.element.android.libraries.sessionstorage.api.SessionData
|
||||
import io.element.android.libraries.sessionstorage.api.SessionStore
|
||||
import io.element.android.libraries.sessionstorage.impl.memory.InMemoryMultiSessionsStore
|
||||
import io.element.android.libraries.sessionstorage.impl.memory.InMemorySessionStore
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.value
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
class DefaultFirebaseNewTokenHandlerTest {
|
||||
@Test
|
||||
fun `when a new token is received it is stored in the firebase store`() = runTest {
|
||||
val firebaseStore = InMemoryFirebaseStore()
|
||||
assertThat(firebaseStore.getFcmToken()).isNull()
|
||||
val firebaseNewTokenHandler = createDefaultFirebaseNewTokenHandler(
|
||||
firebaseStore = firebaseStore,
|
||||
)
|
||||
firebaseNewTokenHandler.handle("aToken")
|
||||
assertThat(firebaseStore.getFcmToken()).isEqualTo("aToken")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when a new token is received, the handler registers the pusher again to all sessions using Firebase`() = runTest {
|
||||
val aMatrixClient1 = FakeMatrixClient(A_USER_ID)
|
||||
val aMatrixClient2 = FakeMatrixClient(A_USER_ID_2)
|
||||
val aMatrixClient3 = FakeMatrixClient(A_USER_ID_3)
|
||||
val registerPusherResult = lambdaRecorder<MatrixClient, String, String, Result<Unit>> { _, _, _ -> Result.success(Unit) }
|
||||
val pusherSubscriber = FakePusherSubscriber(registerPusherResult = registerPusherResult)
|
||||
val firebaseNewTokenHandler = createDefaultFirebaseNewTokenHandler(
|
||||
sessionStore = InMemoryMultiSessionsStore().apply {
|
||||
storeData(aSessionData(A_USER_ID))
|
||||
storeData(aSessionData(A_USER_ID_2))
|
||||
storeData(aSessionData(A_USER_ID_3))
|
||||
},
|
||||
matrixAuthenticationService = FakeAuthenticationService(
|
||||
matrixClientResult = { sessionId ->
|
||||
when (sessionId) {
|
||||
A_USER_ID -> Result.success(aMatrixClient1)
|
||||
A_USER_ID_2 -> Result.success(aMatrixClient2)
|
||||
A_USER_ID_3 -> Result.success(aMatrixClient3)
|
||||
else -> Result.failure(IllegalStateException())
|
||||
}
|
||||
}
|
||||
),
|
||||
userPushStoreFactory = FakeUserPushStoreFactory(
|
||||
userPushStore = { sessionId ->
|
||||
when (sessionId) {
|
||||
A_USER_ID -> FakeUserPushStore(pushProviderName = FirebaseConfig.NAME)
|
||||
A_USER_ID_2 -> FakeUserPushStore(pushProviderName = "Other")
|
||||
A_USER_ID_3 -> FakeUserPushStore(pushProviderName = FirebaseConfig.NAME)
|
||||
else -> error("Unexpected sessionId: $sessionId")
|
||||
}
|
||||
}
|
||||
),
|
||||
pusherSubscriber = pusherSubscriber,
|
||||
)
|
||||
firebaseNewTokenHandler.handle("aToken")
|
||||
registerPusherResult.assertions()
|
||||
.isCalledExactly(2)
|
||||
.withSequence(
|
||||
listOf(value(aMatrixClient1), value("aToken"), value(FirebaseConfig.PUSHER_HTTP_URL)),
|
||||
listOf(value(aMatrixClient3), value("aToken"), value(FirebaseConfig.PUSHER_HTTP_URL)),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when a new token is received, if the session cannot be restore, nothing happen`() = runTest {
|
||||
val registerPusherResult = lambdaRecorder<MatrixClient, String, String, Result<Unit>> { _, _, _ -> Result.success(Unit) }
|
||||
val pusherSubscriber = FakePusherSubscriber(registerPusherResult = registerPusherResult)
|
||||
val firebaseNewTokenHandler = createDefaultFirebaseNewTokenHandler(
|
||||
sessionStore = InMemoryMultiSessionsStore().apply {
|
||||
storeData(aSessionData(A_USER_ID))
|
||||
},
|
||||
matrixAuthenticationService = FakeAuthenticationService(
|
||||
matrixClientResult = { _ ->
|
||||
Result.failure(IllegalStateException())
|
||||
}
|
||||
),
|
||||
userPushStoreFactory = FakeUserPushStoreFactory(
|
||||
userPushStore = { _ ->
|
||||
FakeUserPushStore(pushProviderName = FirebaseConfig.NAME)
|
||||
}
|
||||
),
|
||||
pusherSubscriber = pusherSubscriber,
|
||||
)
|
||||
firebaseNewTokenHandler.handle("aToken")
|
||||
registerPusherResult.assertions()
|
||||
.isNeverCalled()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when a new token is received, error when registering the pusher is ignored`() = runTest {
|
||||
val aMatrixClient1 = FakeMatrixClient(A_USER_ID)
|
||||
val registerPusherResult = lambdaRecorder<MatrixClient, String, String, Result<Unit>> { _, _, _ -> Result.failure(AN_EXCEPTION) }
|
||||
val pusherSubscriber = FakePusherSubscriber(registerPusherResult = registerPusherResult)
|
||||
val firebaseNewTokenHandler = createDefaultFirebaseNewTokenHandler(
|
||||
sessionStore = InMemoryMultiSessionsStore().apply {
|
||||
storeData(aSessionData(A_USER_ID))
|
||||
},
|
||||
matrixAuthenticationService = FakeAuthenticationService(
|
||||
matrixClientResult = { _ ->
|
||||
Result.success(aMatrixClient1)
|
||||
}
|
||||
),
|
||||
userPushStoreFactory = FakeUserPushStoreFactory(
|
||||
userPushStore = { _ ->
|
||||
FakeUserPushStore(pushProviderName = FirebaseConfig.NAME)
|
||||
}
|
||||
),
|
||||
pusherSubscriber = pusherSubscriber,
|
||||
)
|
||||
firebaseNewTokenHandler.handle("aToken")
|
||||
registerPusherResult.assertions()
|
||||
registerPusherResult.assertions()
|
||||
.isCalledOnce()
|
||||
.with(value(aMatrixClient1), value("aToken"), value(FirebaseConfig.PUSHER_HTTP_URL))
|
||||
}
|
||||
|
||||
private fun createDefaultFirebaseNewTokenHandler(
|
||||
pusherSubscriber: PusherSubscriber = FakePusherSubscriber(),
|
||||
sessionStore: SessionStore = InMemorySessionStore(),
|
||||
userPushStoreFactory: UserPushStoreFactory = FakeUserPushStoreFactory(),
|
||||
matrixAuthenticationService: MatrixAuthenticationService = FakeAuthenticationService(),
|
||||
firebaseStore: FirebaseStore = InMemoryFirebaseStore(),
|
||||
): FirebaseNewTokenHandler {
|
||||
return DefaultFirebaseNewTokenHandler(
|
||||
pusherSubscriber = pusherSubscriber,
|
||||
sessionStore = sessionStore,
|
||||
userPushStoreFactory = userPushStoreFactory,
|
||||
matrixAuthenticationService = matrixAuthenticationService,
|
||||
firebaseStore = firebaseStore
|
||||
)
|
||||
}
|
||||
|
||||
private fun aSessionData(
|
||||
sessionId: SessionId,
|
||||
): SessionData {
|
||||
return SessionData(
|
||||
userId = sessionId.value,
|
||||
deviceId = "aDeviceId",
|
||||
accessToken = "anAccessToken",
|
||||
refreshToken = "aRefreshToken",
|
||||
homeserverUrl = "aHomeserverUrl",
|
||||
oidcData = null,
|
||||
slidingSyncProxy = null,
|
||||
loginTimestamp = null,
|
||||
isTokenValid = true,
|
||||
loginType = LoginType.UNKNOWN,
|
||||
passphrase = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
*
|
||||
* 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.firebase
|
||||
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
|
||||
class FakeFirebaseNewTokenHandler(
|
||||
private val handleResult: (String) -> Unit = { lambdaError() }
|
||||
) : FirebaseNewTokenHandler {
|
||||
override suspend fun handle(firebaseToken: String) {
|
||||
handleResult(firebaseToken)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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.firebase
|
||||
|
||||
class FakeIsPlayServiceAvailable(
|
||||
private val isAvailable: Boolean,
|
||||
) : IsPlayServiceAvailable {
|
||||
override fun isAvailable() = isAvailable
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
* 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.firebase
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.libraries.push.test.FakePusherSubscriber
|
||||
import io.element.android.libraries.pushproviders.api.CurrentUserPushConfig
|
||||
import io.element.android.libraries.pushproviders.api.Distributor
|
||||
import io.element.android.libraries.pushproviders.api.PusherSubscriber
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.value
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
class FirebasePushProviderTest {
|
||||
@Test
|
||||
fun `test index and name`() {
|
||||
val firebasePushProvider = createFirebasePushProvider()
|
||||
assertThat(firebasePushProvider.name).isEqualTo(FirebaseConfig.NAME)
|
||||
assertThat(firebasePushProvider.index).isEqualTo(FirebaseConfig.INDEX)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getDistributors return the unique distributor`() {
|
||||
val firebasePushProvider = createFirebasePushProvider()
|
||||
val result = firebasePushProvider.getDistributors()
|
||||
assertThat(result).containsExactly(Distributor("Firebase", "Firebase"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getCurrentDistributor always return the unique distributor`() = runTest {
|
||||
val firebasePushProvider = createFirebasePushProvider()
|
||||
val result = firebasePushProvider.getCurrentDistributor(FakeMatrixClient())
|
||||
assertThat(result).isEqualTo(Distributor("Firebase", "Firebase"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `isAvailable true`() {
|
||||
val firebasePushProvider = createFirebasePushProvider(
|
||||
isPlayServiceAvailable = FakeIsPlayServiceAvailable(isAvailable = true)
|
||||
)
|
||||
assertThat(firebasePushProvider.isAvailable()).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `isAvailable false`() {
|
||||
val firebasePushProvider = createFirebasePushProvider(
|
||||
isPlayServiceAvailable = FakeIsPlayServiceAvailable(isAvailable = false)
|
||||
)
|
||||
assertThat(firebasePushProvider.isAvailable()).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `register ok`() = runTest {
|
||||
val matrixClient = FakeMatrixClient()
|
||||
val registerPusherResultLambda = lambdaRecorder<MatrixClient, String, String, Result<Unit>> { _, _, _ -> Result.success(Unit) }
|
||||
val firebasePushProvider = createFirebasePushProvider(
|
||||
firebaseStore = InMemoryFirebaseStore(
|
||||
token = "aToken"
|
||||
),
|
||||
pusherSubscriber = FakePusherSubscriber(
|
||||
registerPusherResult = registerPusherResultLambda
|
||||
)
|
||||
)
|
||||
val result = firebasePushProvider.registerWith(matrixClient, Distributor("value", "Name"))
|
||||
assertThat(result).isEqualTo(Result.success(Unit))
|
||||
registerPusherResultLambda.assertions()
|
||||
.isCalledOnce()
|
||||
.with(value(matrixClient), value("aToken"), value(FirebaseConfig.PUSHER_HTTP_URL))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `register ko no token`() = runTest {
|
||||
val firebasePushProvider = createFirebasePushProvider(
|
||||
firebaseStore = InMemoryFirebaseStore(
|
||||
token = null
|
||||
),
|
||||
pusherSubscriber = FakePusherSubscriber(
|
||||
registerPusherResult = { _, _, _ -> Result.success(Unit) }
|
||||
)
|
||||
)
|
||||
val result = firebasePushProvider.registerWith(FakeMatrixClient(), Distributor("value", "Name"))
|
||||
assertThat(result.isFailure).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `register ko error`() = runTest {
|
||||
val firebasePushProvider = createFirebasePushProvider(
|
||||
firebaseStore = InMemoryFirebaseStore(
|
||||
token = "aToken"
|
||||
),
|
||||
pusherSubscriber = FakePusherSubscriber(
|
||||
registerPusherResult = { _, _, _ -> Result.failure(AN_EXCEPTION) }
|
||||
)
|
||||
)
|
||||
val result = firebasePushProvider.registerWith(FakeMatrixClient(), Distributor("value", "Name"))
|
||||
assertThat(result.isFailure).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unregister ok`() = runTest {
|
||||
val matrixClient = FakeMatrixClient()
|
||||
val unregisterPusherResultLambda = lambdaRecorder<MatrixClient, String, String, Result<Unit>> { _, _, _ -> Result.success(Unit) }
|
||||
val firebasePushProvider = createFirebasePushProvider(
|
||||
firebaseStore = InMemoryFirebaseStore(
|
||||
token = "aToken"
|
||||
),
|
||||
pusherSubscriber = FakePusherSubscriber(
|
||||
unregisterPusherResult = unregisterPusherResultLambda
|
||||
)
|
||||
)
|
||||
val result = firebasePushProvider.unregister(matrixClient)
|
||||
assertThat(result).isEqualTo(Result.success(Unit))
|
||||
unregisterPusherResultLambda.assertions()
|
||||
.isCalledOnce()
|
||||
.with(value(matrixClient), value("aToken"), value(FirebaseConfig.PUSHER_HTTP_URL))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unregister ko no token`() = runTest {
|
||||
val firebasePushProvider = createFirebasePushProvider(
|
||||
firebaseStore = InMemoryFirebaseStore(
|
||||
token = null
|
||||
),
|
||||
pusherSubscriber = FakePusherSubscriber(
|
||||
unregisterPusherResult = { _, _, _ -> Result.success(Unit) }
|
||||
)
|
||||
)
|
||||
val result = firebasePushProvider.unregister(FakeMatrixClient())
|
||||
assertThat(result.isFailure).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unregister ko error`() = runTest {
|
||||
val firebasePushProvider = createFirebasePushProvider(
|
||||
firebaseStore = InMemoryFirebaseStore(
|
||||
token = "aToken"
|
||||
),
|
||||
pusherSubscriber = FakePusherSubscriber(
|
||||
unregisterPusherResult = { _, _, _ -> Result.failure(AN_EXCEPTION) }
|
||||
)
|
||||
)
|
||||
val result = firebasePushProvider.unregister(FakeMatrixClient())
|
||||
assertThat(result.isFailure).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getCurrentUserPushConfig no push ket`() = runTest {
|
||||
val firebasePushProvider = createFirebasePushProvider(
|
||||
firebaseStore = InMemoryFirebaseStore(
|
||||
token = null
|
||||
)
|
||||
)
|
||||
val result = firebasePushProvider.getCurrentUserPushConfig()
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getCurrentUserPushConfig ok`() = runTest {
|
||||
val firebasePushProvider = createFirebasePushProvider(
|
||||
firebaseStore = InMemoryFirebaseStore(
|
||||
token = "aToken"
|
||||
),
|
||||
)
|
||||
val result = firebasePushProvider.getCurrentUserPushConfig()
|
||||
assertThat(result).isEqualTo(CurrentUserPushConfig(FirebaseConfig.PUSHER_HTTP_URL, "aToken"))
|
||||
}
|
||||
|
||||
private fun createFirebasePushProvider(
|
||||
firebaseStore: FirebaseStore = InMemoryFirebaseStore(),
|
||||
pusherSubscriber: PusherSubscriber = FakePusherSubscriber(),
|
||||
isPlayServiceAvailable: IsPlayServiceAvailable = FakeIsPlayServiceAvailable(false),
|
||||
): FirebasePushProvider {
|
||||
return FirebasePushProvider(
|
||||
firebaseStore = firebaseStore,
|
||||
pusherSubscriber = pusherSubscriber,
|
||||
isPlayServiceAvailable = isPlayServiceAvailable,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 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.firebase
|
||||
|
||||
import android.os.Bundle
|
||||
import com.google.firebase.messaging.RemoteMessage
|
||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||
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.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 VectorFirebaseMessagingServiceTest {
|
||||
@Test
|
||||
fun `test receiving invalid data`() = runTest {
|
||||
val lambda = lambdaRecorder<PushData, Unit>(ensureNeverCalled = true) { }
|
||||
val vectorFirebaseMessagingService = createVectorFirebaseMessagingService(
|
||||
pushHandler = FakePushHandler(handleResult = lambda)
|
||||
)
|
||||
vectorFirebaseMessagingService.onMessageReceived(RemoteMessage(Bundle()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test receiving valid data`() = runTest {
|
||||
val lambda = lambdaRecorder<PushData, Unit> { }
|
||||
val vectorFirebaseMessagingService = createVectorFirebaseMessagingService(
|
||||
pushHandler = FakePushHandler(handleResult = lambda)
|
||||
)
|
||||
vectorFirebaseMessagingService.onMessageReceived(
|
||||
message = RemoteMessage(
|
||||
Bundle().apply {
|
||||
putString("event_id", AN_EVENT_ID.value)
|
||||
putString("room_id", A_ROOM_ID.value)
|
||||
putString("cs", A_SECRET)
|
||||
},
|
||||
)
|
||||
)
|
||||
advanceUntilIdle()
|
||||
lambda.assertions()
|
||||
.isCalledOnce()
|
||||
.with(value(PushData(AN_EVENT_ID, A_ROOM_ID, null, A_SECRET)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test new token is forwarded to the handler`() = runTest {
|
||||
val lambda = lambdaRecorder<String, Unit> { }
|
||||
val vectorFirebaseMessagingService = createVectorFirebaseMessagingService(
|
||||
firebaseNewTokenHandler = FakeFirebaseNewTokenHandler(handleResult = lambda)
|
||||
)
|
||||
vectorFirebaseMessagingService.onNewToken("aToken")
|
||||
advanceUntilIdle()
|
||||
lambda.assertions()
|
||||
.isCalledOnce()
|
||||
.with(value("aToken"))
|
||||
}
|
||||
|
||||
private fun TestScope.createVectorFirebaseMessagingService(
|
||||
firebaseNewTokenHandler: FirebaseNewTokenHandler = FakeFirebaseNewTokenHandler(),
|
||||
pushHandler: PushHandler = FakePushHandler(),
|
||||
): VectorFirebaseMessagingService {
|
||||
return VectorFirebaseMessagingService().apply {
|
||||
this.firebaseNewTokenHandler = firebaseNewTokenHandler
|
||||
this.pushParser = FirebasePushParser()
|
||||
this.pushHandler = pushHandler
|
||||
this.coroutineScope = this@createVectorFirebaseMessagingService
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,8 +18,10 @@ package io.element.android.libraries.pushproviders.firebase.troubleshoot
|
||||
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.pushproviders.firebase.IsPlayServiceAvailable
|
||||
import io.element.android.libraries.pushproviders.firebase.FakeIsPlayServiceAvailable
|
||||
import io.element.android.libraries.pushproviders.firebase.FirebaseConfig
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState
|
||||
import io.element.android.libraries.troubleshoot.api.test.TestFilterData
|
||||
import io.element.android.services.toolbox.test.strings.FakeStringProvider
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.test.runTest
|
||||
@@ -29,11 +31,7 @@ class FirebaseAvailabilityTestTest {
|
||||
@Test
|
||||
fun `test FirebaseAvailabilityTest success`() = runTest {
|
||||
val sut = FirebaseAvailabilityTest(
|
||||
isPlayServiceAvailable = object : IsPlayServiceAvailable {
|
||||
override fun isAvailable(): Boolean {
|
||||
return true
|
||||
}
|
||||
},
|
||||
isPlayServiceAvailable = FakeIsPlayServiceAvailable(true),
|
||||
stringProvider = FakeStringProvider(),
|
||||
)
|
||||
launch {
|
||||
@@ -50,11 +48,7 @@ class FirebaseAvailabilityTestTest {
|
||||
@Test
|
||||
fun `test FirebaseAvailabilityTest failure`() = runTest {
|
||||
val sut = FirebaseAvailabilityTest(
|
||||
isPlayServiceAvailable = object : IsPlayServiceAvailable {
|
||||
override fun isAvailable(): Boolean {
|
||||
return false
|
||||
}
|
||||
},
|
||||
isPlayServiceAvailable = FakeIsPlayServiceAvailable(false),
|
||||
stringProvider = FakeStringProvider(),
|
||||
)
|
||||
launch {
|
||||
@@ -67,4 +61,14 @@ class FirebaseAvailabilityTestTest {
|
||||
assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(false))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test FirebaseAvailabilityTest isRelevant`() {
|
||||
val sut = FirebaseAvailabilityTest(
|
||||
isPlayServiceAvailable = FakeIsPlayServiceAvailable(false),
|
||||
stringProvider = FakeStringProvider(),
|
||||
)
|
||||
assertThat(sut.isRelevant(TestFilterData(currentPushProviderName = "unknown"))).isFalse()
|
||||
assertThat(sut.isRelevant(TestFilterData(currentPushProviderName = FirebaseConfig.NAME))).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,8 +19,10 @@ package io.element.android.libraries.pushproviders.firebase.troubleshoot
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.pushproviders.firebase.FakeFirebaseTroubleshooter
|
||||
import io.element.android.libraries.pushproviders.firebase.FirebaseConfig
|
||||
import io.element.android.libraries.pushproviders.firebase.InMemoryFirebaseStore
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState
|
||||
import io.element.android.libraries.troubleshoot.api.test.TestFilterData
|
||||
import io.element.android.services.toolbox.test.strings.FakeStringProvider
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.test.runTest
|
||||
@@ -75,6 +77,17 @@ class FirebaseTokenTestTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test FirebaseTokenTest isRelevant`() {
|
||||
val sut = FirebaseTokenTest(
|
||||
firebaseStore = InMemoryFirebaseStore(null),
|
||||
firebaseTroubleshooter = FakeFirebaseTroubleshooter(),
|
||||
stringProvider = FakeStringProvider(),
|
||||
)
|
||||
assertThat(sut.isRelevant(TestFilterData(currentPushProviderName = "unknown"))).isFalse()
|
||||
assertThat(sut.isRelevant(TestFilterData(currentPushProviderName = FirebaseConfig.NAME))).isTrue()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val FAKE_TOKEN = "abcdefghijk"
|
||||
}
|
||||
|
||||
@@ -24,4 +24,5 @@ android {
|
||||
dependencies {
|
||||
implementation(projects.libraries.matrix.api)
|
||||
implementation(projects.libraries.pushproviders.api)
|
||||
implementation(projects.tests.testutils)
|
||||
}
|
||||
|
||||
@@ -20,19 +20,23 @@ import io.element.android.libraries.matrix.api.MatrixClient
|
||||
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.tests.testutils.lambda.lambdaError
|
||||
|
||||
class FakePushProvider(
|
||||
override val index: Int = 0,
|
||||
override val name: String = "aFakePushProvider",
|
||||
private val isAvailable: Boolean = true,
|
||||
private val distributors: List<Distributor> = listOf(Distributor("aDistributorValue", "aDistributorName")),
|
||||
private val currentUserPushConfig: CurrentUserPushConfig? = null,
|
||||
private val registerWithResult: (MatrixClient, Distributor) -> Result<Unit> = { _, _ -> lambdaError() },
|
||||
private val unregisterWithResult: (MatrixClient) -> Result<Unit> = { lambdaError() },
|
||||
) : PushProvider {
|
||||
override fun isAvailable(): Boolean = isAvailable
|
||||
|
||||
override fun getDistributors(): List<Distributor> = distributors
|
||||
|
||||
override suspend fun registerWith(matrixClient: MatrixClient, distributor: Distributor): Result<Unit> {
|
||||
return Result.success(Unit)
|
||||
return registerWithResult(matrixClient, distributor)
|
||||
}
|
||||
|
||||
override suspend fun getCurrentDistributor(matrixClient: MatrixClient): Distributor? {
|
||||
@@ -40,10 +44,10 @@ class FakePushProvider(
|
||||
}
|
||||
|
||||
override suspend fun unregister(matrixClient: MatrixClient): Result<Unit> {
|
||||
return Result.success(Unit)
|
||||
return unregisterWithResult(matrixClient)
|
||||
}
|
||||
|
||||
override suspend fun getCurrentUserPushConfig(): CurrentUserPushConfig? {
|
||||
return null
|
||||
return currentUserPushConfig
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,9 +55,13 @@ 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)
|
||||
testImplementation(projects.services.appnavstate.test)
|
||||
}
|
||||
|
||||
@@ -17,43 +17,41 @@
|
||||
package io.element.android.libraries.pushproviders.unifiedpush
|
||||
|
||||
import android.content.Context
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.di.ApplicationContext
|
||||
import io.element.android.libraries.pushproviders.api.Distributor
|
||||
import io.element.android.libraries.pushproviders.unifiedpush.registration.EndpointRegistrationHandler
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import org.unifiedpush.android.connector.UnifiedPush
|
||||
import javax.inject.Inject
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
class RegisterUnifiedPushUseCase @Inject constructor(
|
||||
interface RegisterUnifiedPushUseCase {
|
||||
suspend fun execute(distributor: Distributor, clientSecret: String): Result<Unit>
|
||||
}
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultRegisterUnifiedPushUseCase @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val endpointRegistrationHandler: EndpointRegistrationHandler,
|
||||
private val coroutineScope: CoroutineScope,
|
||||
) {
|
||||
suspend fun execute(distributor: Distributor, clientSecret: String): Result<Unit> {
|
||||
) : RegisterUnifiedPushUseCase {
|
||||
override suspend fun execute(distributor: Distributor, clientSecret: String): Result<Unit> {
|
||||
UnifiedPush.saveDistributor(context, distributor.value)
|
||||
val completable = CompletableDeferred<Result<Unit>>()
|
||||
val job = coroutineScope.launch {
|
||||
val result = endpointRegistrationHandler.state
|
||||
.filter { it.clientSecret == clientSecret }
|
||||
.first()
|
||||
.result
|
||||
completable.complete(result)
|
||||
}
|
||||
// This will trigger the callback
|
||||
// VectorUnifiedPushMessagingReceiver.onNewEndpoint
|
||||
UnifiedPush.registerApp(context = context, instance = clientSecret)
|
||||
// Wait for VectorUnifiedPushMessagingReceiver.onNewEndpoint to proceed
|
||||
return withTimeout(30.seconds) {
|
||||
completable.await()
|
||||
}
|
||||
.onFailure {
|
||||
job.cancel()
|
||||
return runCatching {
|
||||
withTimeout(30.seconds) {
|
||||
val result = endpointRegistrationHandler.state
|
||||
.filter { it.clientSecret == clientSecret }
|
||||
.first()
|
||||
.result
|
||||
result.getOrThrow()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.network.RetrofitFactory
|
||||
import io.element.android.libraries.pushproviders.unifiedpush.network.UnifiedPushApi
|
||||
import javax.inject.Inject
|
||||
|
||||
interface UnifiedPushApiFactory {
|
||||
fun create(baseUrl: String): UnifiedPushApi
|
||||
}
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultUnifiedPushApiFactory @Inject constructor(
|
||||
private val retrofitFactory: RetrofitFactory,
|
||||
) : UnifiedPushApiFactory {
|
||||
override fun create(baseUrl: String): UnifiedPushApi {
|
||||
return retrofitFactory.create(baseUrl)
|
||||
.create(UnifiedPushApi::class.java)
|
||||
}
|
||||
}
|
||||
@@ -16,29 +16,33 @@
|
||||
|
||||
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.network.RetrofitFactory
|
||||
import io.element.android.libraries.pushproviders.unifiedpush.network.UnifiedPushApi
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import java.net.URL
|
||||
import javax.inject.Inject
|
||||
|
||||
class UnifiedPushGatewayResolver @Inject constructor(
|
||||
private val retrofitFactory: RetrofitFactory,
|
||||
interface UnifiedPushGatewayResolver {
|
||||
suspend fun getGateway(endpoint: String): String
|
||||
}
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
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
|
||||
val url = URL(endpoint)
|
||||
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")
|
||||
try {
|
||||
val url = URL(endpoint)
|
||||
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")
|
||||
return withContext(coroutineDispatchers.io) {
|
||||
val api = retrofitFactory.create(customBase)
|
||||
.create(UnifiedPushApi::class.java)
|
||||
val api = unifiedPushApiFactory.create(customBase)
|
||||
try {
|
||||
val discoveryResponse = api.discover()
|
||||
if (discoveryResponse.unifiedpush.gateway == "matrix") {
|
||||
|
||||
@@ -16,8 +16,10 @@
|
||||
|
||||
package io.element.android.libraries.pushproviders.unifiedpush
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.core.extensions.flatMap
|
||||
import io.element.android.libraries.core.log.logger.LoggerTag
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
||||
import io.element.android.libraries.pushproviders.api.PusherSubscriber
|
||||
import io.element.android.libraries.pushstore.api.UserPushStoreFactory
|
||||
@@ -25,18 +27,23 @@ import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
private val loggerTag = LoggerTag("UnifiedPushNewGatewayHandler", LoggerTag.PushLoggerTag)
|
||||
private val loggerTag = LoggerTag("DefaultUnifiedPushNewGatewayHandler", LoggerTag.PushLoggerTag)
|
||||
|
||||
/**
|
||||
* Handle new endpoint received from UnifiedPush. Will update the session matching the client secret.
|
||||
*/
|
||||
class UnifiedPushNewGatewayHandler @Inject constructor(
|
||||
interface UnifiedPushNewGatewayHandler {
|
||||
suspend fun handle(endpoint: String, pushGateway: String, clientSecret: String): Result<Unit>
|
||||
}
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultUnifiedPushNewGatewayHandler @Inject constructor(
|
||||
private val pusherSubscriber: PusherSubscriber,
|
||||
private val userPushStoreFactory: UserPushStoreFactory,
|
||||
private val pushClientSecret: PushClientSecret,
|
||||
private val matrixAuthenticationService: MatrixAuthenticationService,
|
||||
) {
|
||||
suspend fun handle(endpoint: String, pushGateway: String, clientSecret: String): Result<Unit> {
|
||||
) : UnifiedPushNewGatewayHandler {
|
||||
override suspend fun handle(endpoint: String, pushGateway: String, clientSecret: String): Result<Unit> {
|
||||
// Register the pusher for the session with this client secret, if is it using UnifiedPush.
|
||||
val userId = pushClientSecret.getUserIdFromSecret(clientSecret) ?: return Result.failure<Unit>(
|
||||
IllegalStateException("Unable to retrieve session")
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -19,32 +19,44 @@ package io.element.android.libraries.pushproviders.unifiedpush
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.core.content.edit
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.di.ApplicationContext
|
||||
import io.element.android.libraries.di.DefaultPreferences
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import javax.inject.Inject
|
||||
|
||||
class UnifiedPushStore @Inject constructor(
|
||||
interface UnifiedPushStore {
|
||||
fun getEndpoint(clientSecret: String): String?
|
||||
fun storeUpEndpoint(clientSecret: String, endpoint: String?)
|
||||
fun getPushGateway(clientSecret: String): String?
|
||||
fun storePushGateway(clientSecret: String, gateway: String?)
|
||||
fun getDistributorValue(userId: UserId): String?
|
||||
fun setDistributorValue(userId: UserId, value: String)
|
||||
}
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultUnifiedPushStore @Inject constructor(
|
||||
@ApplicationContext val context: Context,
|
||||
@DefaultPreferences private val defaultPrefs: SharedPreferences,
|
||||
) {
|
||||
) : UnifiedPushStore {
|
||||
/**
|
||||
* Retrieves the UnifiedPush Endpoint.
|
||||
*
|
||||
* @param clientSecret the client secret, to identify the session
|
||||
* @return the UnifiedPush Endpoint or null if not received
|
||||
*/
|
||||
fun getEndpoint(clientSecret: String): String? {
|
||||
override fun getEndpoint(clientSecret: String): String? {
|
||||
return defaultPrefs.getString(PREFS_ENDPOINT_OR_TOKEN + clientSecret, null)
|
||||
}
|
||||
|
||||
/**
|
||||
* Store UnifiedPush Endpoint to the SharedPrefs.
|
||||
*
|
||||
* @param endpoint the endpoint to store
|
||||
* @param clientSecret the client secret, to identify the session
|
||||
* @param endpoint the endpoint to store
|
||||
*/
|
||||
fun storeUpEndpoint(endpoint: String?, clientSecret: String) {
|
||||
override fun storeUpEndpoint(clientSecret: String, endpoint: String?) {
|
||||
defaultPrefs.edit {
|
||||
putString(PREFS_ENDPOINT_OR_TOKEN + clientSecret, endpoint)
|
||||
}
|
||||
@@ -56,27 +68,27 @@ class UnifiedPushStore @Inject constructor(
|
||||
* @param clientSecret the client secret, to identify the session
|
||||
* @return the Push Gateway or null if not defined
|
||||
*/
|
||||
fun getPushGateway(clientSecret: String): String? {
|
||||
override fun getPushGateway(clientSecret: String): String? {
|
||||
return defaultPrefs.getString(PREFS_PUSH_GATEWAY + clientSecret, null)
|
||||
}
|
||||
|
||||
/**
|
||||
* Store Push Gateway to the SharedPrefs.
|
||||
*
|
||||
* @param gateway the push gateway to store
|
||||
* @param clientSecret the client secret, to identify the session
|
||||
* @param gateway the push gateway to store
|
||||
*/
|
||||
fun storePushGateway(gateway: String?, clientSecret: String) {
|
||||
override fun storePushGateway(clientSecret: String, gateway: String?) {
|
||||
defaultPrefs.edit {
|
||||
putString(PREFS_PUSH_GATEWAY + clientSecret, gateway)
|
||||
}
|
||||
}
|
||||
|
||||
fun getDistributorValue(userId: UserId): String? {
|
||||
override fun getDistributorValue(userId: UserId): String? {
|
||||
return defaultPrefs.getString(PREFS_DISTRIBUTOR + userId, null)
|
||||
}
|
||||
|
||||
fun setDistributorValue(userId: UserId, value: String) {
|
||||
override fun setDistributorValue(userId: UserId, value: String) {
|
||||
defaultPrefs.edit {
|
||||
putString(PREFS_DISTRIBUTOR + userId, value)
|
||||
}
|
||||
|
||||
@@ -17,18 +17,25 @@
|
||||
package io.element.android.libraries.pushproviders.unifiedpush
|
||||
|
||||
import android.content.Context
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.di.ApplicationContext
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.pushproviders.api.PusherSubscriber
|
||||
import org.unifiedpush.android.connector.UnifiedPush
|
||||
import javax.inject.Inject
|
||||
|
||||
class UnregisterUnifiedPushUseCase @Inject constructor(
|
||||
interface UnregisterUnifiedPushUseCase {
|
||||
suspend fun execute(matrixClient: MatrixClient, clientSecret: String): Result<Unit>
|
||||
}
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultUnregisterUnifiedPushUseCase @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val unifiedPushStore: UnifiedPushStore,
|
||||
private val pusherSubscriber: PusherSubscriber,
|
||||
) {
|
||||
suspend fun execute(matrixClient: MatrixClient, clientSecret: String): Result<Unit> {
|
||||
) : UnregisterUnifiedPushUseCase {
|
||||
override suspend fun execute(matrixClient: MatrixClient, clientSecret: String): Result<Unit> {
|
||||
val endpoint = unifiedPushStore.getEndpoint(clientSecret)
|
||||
val gateway = unifiedPushStore.getPushGateway(clientSecret)
|
||||
if (endpoint == null || gateway == null) {
|
||||
@@ -36,8 +43,8 @@ class UnregisterUnifiedPushUseCase @Inject constructor(
|
||||
}
|
||||
return pusherSubscriber.unregisterPusher(matrixClient, endpoint, gateway)
|
||||
.onSuccess {
|
||||
unifiedPushStore.storeUpEndpoint(null, clientSecret)
|
||||
unifiedPushStore.storePushGateway(null, clientSecret)
|
||||
unifiedPushStore.storeUpEndpoint(clientSecret, null)
|
||||
unifiedPushStore.storePushGateway(clientSecret, null)
|
||||
UnifiedPush.unregisterApp(context)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<VectorUnifiedPushMessagingReceiverBindings>().inject(this)
|
||||
@@ -75,30 +73,20 @@ class VectorUnifiedPushMessagingReceiver : MessagingReceiver() {
|
||||
Timber.tag(loggerTag.value).i("onNewEndpoint: $endpoint")
|
||||
coroutineScope.launch {
|
||||
val gateway = unifiedPushGatewayResolver.getGateway(endpoint)
|
||||
unifiedPushStore.storePushGateway(gateway, instance)
|
||||
if (gateway == null) {
|
||||
Timber.tag(loggerTag.value).w("No gateway found for endpoint $endpoint")
|
||||
endpointRegistrationHandler.registrationDone(
|
||||
RegistrationResult(
|
||||
clientSecret = instance,
|
||||
result = Result.failure(IllegalStateException("No gateway found for endpoint $endpoint")),
|
||||
)
|
||||
unifiedPushStore.storePushGateway(instance, gateway)
|
||||
val result = newGatewayHandler.handle(endpoint, gateway, instance)
|
||||
.onFailure {
|
||||
Timber.tag(loggerTag.value).e(it, "Failed to handle new gateway")
|
||||
}
|
||||
.onSuccess {
|
||||
unifiedPushStore.storeUpEndpoint(instance, endpoint)
|
||||
}
|
||||
endpointRegistrationHandler.registrationDone(
|
||||
RegistrationResult(
|
||||
clientSecret = instance,
|
||||
result = result,
|
||||
)
|
||||
} else {
|
||||
val result = newGatewayHandler.handle(endpoint, gateway, instance)
|
||||
.onFailure {
|
||||
Timber.tag(loggerTag.value).e(it, "Failed to handle new gateway")
|
||||
}
|
||||
.onSuccess {
|
||||
unifiedPushStore.storeUpEndpoint(endpoint, instance)
|
||||
}
|
||||
endpointRegistrationHandler.registrationDone(
|
||||
RegistrationResult(
|
||||
clientSecret = instance,
|
||||
result = result,
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
guardServiceStarter.stop()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||
import io.element.android.libraries.matrix.test.A_SECRET
|
||||
import io.element.android.libraries.pushproviders.api.Distributor
|
||||
import io.element.android.libraries.pushproviders.unifiedpush.registration.EndpointRegistrationHandler
|
||||
import io.element.android.libraries.pushproviders.unifiedpush.registration.RegistrationResult
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class DefaultRegisterUnifiedPushUseCaseTest {
|
||||
@Test
|
||||
fun `test registration successful`() = runTest {
|
||||
val endpointRegistrationHandler = EndpointRegistrationHandler()
|
||||
val useCase = createDefaultRegisterUnifiedPushUseCase(
|
||||
endpointRegistrationHandler = endpointRegistrationHandler
|
||||
)
|
||||
val aDistributor = Distributor("aValue", "aName")
|
||||
launch {
|
||||
delay(100)
|
||||
endpointRegistrationHandler.registrationDone(RegistrationResult(A_SECRET, Result.success(Unit)))
|
||||
}
|
||||
val result = useCase.execute(aDistributor, A_SECRET)
|
||||
assertThat(result.isSuccess).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test registration error`() = runTest {
|
||||
val endpointRegistrationHandler = EndpointRegistrationHandler()
|
||||
val useCase = createDefaultRegisterUnifiedPushUseCase(
|
||||
endpointRegistrationHandler = endpointRegistrationHandler
|
||||
)
|
||||
val aDistributor = Distributor("aValue", "aName")
|
||||
launch {
|
||||
delay(100)
|
||||
endpointRegistrationHandler.registrationDone(RegistrationResult(A_SECRET, Result.failure(AN_EXCEPTION)))
|
||||
}
|
||||
val result = useCase.execute(aDistributor, A_SECRET)
|
||||
assertThat(result.isSuccess).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test registration timeout`() = runTest {
|
||||
val endpointRegistrationHandler = EndpointRegistrationHandler()
|
||||
val useCase = createDefaultRegisterUnifiedPushUseCase(
|
||||
endpointRegistrationHandler = endpointRegistrationHandler
|
||||
)
|
||||
val aDistributor = Distributor("aValue", "aName")
|
||||
val result = useCase.execute(aDistributor, A_SECRET)
|
||||
assertThat(result.isSuccess).isFalse()
|
||||
}
|
||||
|
||||
private fun TestScope.createDefaultRegisterUnifiedPushUseCase(
|
||||
endpointRegistrationHandler: EndpointRegistrationHandler
|
||||
): DefaultRegisterUnifiedPushUseCase {
|
||||
val context = InstrumentationRegistry.getInstrumentation().context
|
||||
return DefaultRegisterUnifiedPushUseCase(
|
||||
context = context,
|
||||
endpointRegistrationHandler = endpointRegistrationHandler,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||
import io.element.android.libraries.pushproviders.unifiedpush.network.DiscoveryResponse
|
||||
import io.element.android.libraries.pushproviders.unifiedpush.network.DiscoveryUnifiedPush
|
||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
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(
|
||||
discoveryResponse = matrixDiscoveryResponse
|
||||
)
|
||||
val sut = createDefaultUnifiedPushGatewayResolver(
|
||||
unifiedPushApiFactory = unifiedPushApiFactory
|
||||
)
|
||||
val result = sut.getGateway("https://custom.url")
|
||||
assertThat(unifiedPushApiFactory.baseUrlParameter).isEqualTo("https://custom.url")
|
||||
assertThat(result).isEqualTo("https://custom.url/_matrix/push/v1/notify")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when a custom url with port provides a correct matrix gateway, the custom url is returned`() = runTest {
|
||||
val unifiedPushApiFactory = FakeUnifiedPushApiFactory(
|
||||
discoveryResponse = matrixDiscoveryResponse
|
||||
)
|
||||
val sut = createDefaultUnifiedPushGatewayResolver(
|
||||
unifiedPushApiFactory = unifiedPushApiFactory
|
||||
)
|
||||
val result = sut.getGateway("https://custom.url:123")
|
||||
assertThat(unifiedPushApiFactory.baseUrlParameter).isEqualTo("https://custom.url:123")
|
||||
assertThat(result).isEqualTo("https://custom.url:123/_matrix/push/v1/notify")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when a custom url with port and path provides a correct matrix gateway, the custom url is returned`() = runTest {
|
||||
val unifiedPushApiFactory = FakeUnifiedPushApiFactory(
|
||||
discoveryResponse = matrixDiscoveryResponse
|
||||
)
|
||||
val sut = createDefaultUnifiedPushGatewayResolver(
|
||||
unifiedPushApiFactory = unifiedPushApiFactory
|
||||
)
|
||||
val result = sut.getGateway("https://custom.url:123/some/path")
|
||||
assertThat(unifiedPushApiFactory.baseUrlParameter).isEqualTo("https://custom.url:123")
|
||||
assertThat(result).isEqualTo("https://custom.url:123/_matrix/push/v1/notify")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when a custom url with http scheme provides a correct matrix gateway, the custom url is returned`() = runTest {
|
||||
val unifiedPushApiFactory = FakeUnifiedPushApiFactory(
|
||||
discoveryResponse = matrixDiscoveryResponse
|
||||
)
|
||||
val sut = createDefaultUnifiedPushGatewayResolver(
|
||||
unifiedPushApiFactory = unifiedPushApiFactory
|
||||
)
|
||||
val result = sut.getGateway("http://custom.url:123/some/path")
|
||||
assertThat(unifiedPushApiFactory.baseUrlParameter).isEqualTo("http://custom.url:123")
|
||||
assertThat(result).isEqualTo("http://custom.url:123/_matrix/push/v1/notify")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when a custom url is not reachable, the default url is returned`() = runTest {
|
||||
val unifiedPushApiFactory = FakeUnifiedPushApiFactory(
|
||||
discoveryResponse = { throw AN_EXCEPTION }
|
||||
)
|
||||
val sut = createDefaultUnifiedPushGatewayResolver(
|
||||
unifiedPushApiFactory = unifiedPushApiFactory
|
||||
)
|
||||
val result = sut.getGateway("http://custom.url")
|
||||
assertThat(unifiedPushApiFactory.baseUrlParameter).isEqualTo("http://custom.url")
|
||||
assertThat(result).isEqualTo(UnifiedPushConfig.DEFAULT_PUSH_GATEWAY_HTTP_URL)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when a custom url is invalid, the default url is returned`() = runTest {
|
||||
val unifiedPushApiFactory = FakeUnifiedPushApiFactory(
|
||||
discoveryResponse = matrixDiscoveryResponse
|
||||
)
|
||||
val sut = createDefaultUnifiedPushGatewayResolver(
|
||||
unifiedPushApiFactory = unifiedPushApiFactory
|
||||
)
|
||||
val result = sut.getGateway("invalid")
|
||||
assertThat(unifiedPushApiFactory.baseUrlParameter).isNull()
|
||||
assertThat(result).isEqualTo(UnifiedPushConfig.DEFAULT_PUSH_GATEWAY_HTTP_URL)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when a custom url provides a invalid matrix gateway, the default url is returned`() = runTest {
|
||||
val unifiedPushApiFactory = FakeUnifiedPushApiFactory(
|
||||
discoveryResponse = invalidDiscoveryResponse
|
||||
)
|
||||
val sut = createDefaultUnifiedPushGatewayResolver(
|
||||
unifiedPushApiFactory = unifiedPushApiFactory
|
||||
)
|
||||
val result = sut.getGateway("https://custom.url")
|
||||
assertThat(unifiedPushApiFactory.baseUrlParameter).isEqualTo("https://custom.url")
|
||||
assertThat(result).isEqualTo(UnifiedPushConfig.DEFAULT_PUSH_GATEWAY_HTTP_URL)
|
||||
}
|
||||
|
||||
private fun TestScope.createDefaultUnifiedPushGatewayResolver(
|
||||
unifiedPushApiFactory: UnifiedPushApiFactory = FakeUnifiedPushApiFactory(
|
||||
discoveryResponse = { DiscoveryResponse() }
|
||||
)
|
||||
) = DefaultUnifiedPushGatewayResolver(
|
||||
unifiedPushApiFactory = unifiedPushApiFactory,
|
||||
coroutineDispatchers = testCoroutineDispatchers()
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
||||
import io.element.android.libraries.matrix.test.A_SECRET
|
||||
import io.element.android.libraries.matrix.test.A_USER_ID
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.libraries.matrix.test.auth.FakeAuthenticationService
|
||||
import io.element.android.libraries.push.test.FakePusherSubscriber
|
||||
import io.element.android.libraries.pushproviders.api.PusherSubscriber
|
||||
import io.element.android.libraries.pushstore.api.UserPushStoreFactory
|
||||
import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret
|
||||
import io.element.android.libraries.pushstore.test.userpushstore.FakeUserPushStore
|
||||
import io.element.android.libraries.pushstore.test.userpushstore.FakeUserPushStoreFactory
|
||||
import io.element.android.libraries.pushstore.test.userpushstore.clientsecret.FakePushClientSecret
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.value
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
class DefaultUnifiedPushNewGatewayHandlerTest {
|
||||
@Test
|
||||
fun `error when fail to retrieve the session`() = runTest {
|
||||
val defaultUnifiedPushNewGatewayHandler = createDefaultUnifiedPushNewGatewayHandler(
|
||||
pushClientSecret = FakePushClientSecret(
|
||||
getUserIdFromSecretResult = { null }
|
||||
)
|
||||
)
|
||||
val result = defaultUnifiedPushNewGatewayHandler.handle(
|
||||
endpoint = "aEndpoint",
|
||||
pushGateway = "aPushGateway",
|
||||
clientSecret = A_SECRET,
|
||||
)
|
||||
assertThat(result.isFailure).isTrue()
|
||||
assertThat(result.exceptionOrNull()).isInstanceOf(IllegalStateException::class.java)
|
||||
assertThat(result.exceptionOrNull()?.message).isEqualTo("Unable to retrieve session")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `error when the session is not using UnifiedPush`() = runTest {
|
||||
val defaultUnifiedPushNewGatewayHandler = createDefaultUnifiedPushNewGatewayHandler(
|
||||
pushClientSecret = FakePushClientSecret(
|
||||
getUserIdFromSecretResult = { A_USER_ID }
|
||||
),
|
||||
userPushStoreFactory = FakeUserPushStoreFactory(
|
||||
userPushStore = { FakeUserPushStore(pushProviderName = "other") }
|
||||
)
|
||||
)
|
||||
val result = defaultUnifiedPushNewGatewayHandler.handle(
|
||||
endpoint = "aEndpoint",
|
||||
pushGateway = "aPushGateway",
|
||||
clientSecret = A_SECRET,
|
||||
)
|
||||
assertThat(result.isFailure).isTrue()
|
||||
assertThat(result.exceptionOrNull()).isInstanceOf(IllegalStateException::class.java)
|
||||
assertThat(result.exceptionOrNull()?.message).isEqualTo("This session is not using UnifiedPush pusher")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `error when the registration fails`() = runTest {
|
||||
val aMatrixClient = FakeMatrixClient()
|
||||
val defaultUnifiedPushNewGatewayHandler = createDefaultUnifiedPushNewGatewayHandler(
|
||||
pushClientSecret = FakePushClientSecret(
|
||||
getUserIdFromSecretResult = { A_USER_ID }
|
||||
),
|
||||
userPushStoreFactory = FakeUserPushStoreFactory(
|
||||
userPushStore = { FakeUserPushStore(pushProviderName = UnifiedPushConfig.NAME) }
|
||||
),
|
||||
pusherSubscriber = FakePusherSubscriber(
|
||||
registerPusherResult = { _, _, _ -> Result.failure(IllegalStateException("an error")) }
|
||||
),
|
||||
matrixAuthenticationService = FakeAuthenticationService(matrixClientResult = { Result.success(aMatrixClient) }),
|
||||
)
|
||||
val result = defaultUnifiedPushNewGatewayHandler.handle(
|
||||
endpoint = "aEndpoint",
|
||||
pushGateway = "aPushGateway",
|
||||
clientSecret = A_SECRET,
|
||||
)
|
||||
assertThat(result.isFailure).isTrue()
|
||||
assertThat(result.exceptionOrNull()).isInstanceOf(IllegalStateException::class.java)
|
||||
assertThat(result.exceptionOrNull()?.message).isEqualTo("an error")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `happy path`() = runTest {
|
||||
val aMatrixClient = FakeMatrixClient()
|
||||
val lambda = lambdaRecorder { _: MatrixClient, _: String, _: String ->
|
||||
Result.success(Unit)
|
||||
}
|
||||
val defaultUnifiedPushNewGatewayHandler = createDefaultUnifiedPushNewGatewayHandler(
|
||||
pushClientSecret = FakePushClientSecret(
|
||||
getUserIdFromSecretResult = { A_USER_ID }
|
||||
),
|
||||
userPushStoreFactory = FakeUserPushStoreFactory(
|
||||
userPushStore = { FakeUserPushStore(pushProviderName = UnifiedPushConfig.NAME) }
|
||||
),
|
||||
pusherSubscriber = FakePusherSubscriber(
|
||||
registerPusherResult = lambda
|
||||
),
|
||||
matrixAuthenticationService = FakeAuthenticationService(matrixClientResult = { Result.success(aMatrixClient) }),
|
||||
)
|
||||
val result = defaultUnifiedPushNewGatewayHandler.handle(
|
||||
endpoint = "aEndpoint",
|
||||
pushGateway = "aPushGateway",
|
||||
clientSecret = A_SECRET,
|
||||
)
|
||||
assertThat(result).isEqualTo(Result.success(Unit))
|
||||
lambda.assertions()
|
||||
.isCalledOnce()
|
||||
.with(value(aMatrixClient), value("aEndpoint"), value("aPushGateway"))
|
||||
}
|
||||
|
||||
private fun createDefaultUnifiedPushNewGatewayHandler(
|
||||
pusherSubscriber: PusherSubscriber = FakePusherSubscriber(),
|
||||
userPushStoreFactory: UserPushStoreFactory = FakeUserPushStoreFactory(),
|
||||
pushClientSecret: PushClientSecret = FakePushClientSecret(),
|
||||
matrixAuthenticationService: MatrixAuthenticationService = FakeAuthenticationService()
|
||||
): DefaultUnifiedPushNewGatewayHandler {
|
||||
return DefaultUnifiedPushNewGatewayHandler(
|
||||
pusherSubscriber = pusherSubscriber,
|
||||
userPushStoreFactory = userPushStoreFactory,
|
||||
pushClientSecret = pushClientSecret,
|
||||
matrixAuthenticationService = matrixAuthenticationService
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
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.FakeMatrixClient
|
||||
import io.element.android.libraries.push.test.FakePusherSubscriber
|
||||
import io.element.android.libraries.pushproviders.api.PusherSubscriber
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.value
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class DefaultUnregisterUnifiedPushUseCaseTest {
|
||||
@Test
|
||||
fun `test un registration successful`() = runTest {
|
||||
val lambda = lambdaRecorder { _: MatrixClient, _: String, _: String -> Result.success(Unit) }
|
||||
val storeUpEndpointResult = lambdaRecorder { _: String, _: String? -> }
|
||||
val storePushGatewayResult = lambdaRecorder { _: String, _: String? -> }
|
||||
val matrixClient = FakeMatrixClient()
|
||||
val useCase = createDefaultUnregisterUnifiedPushUseCase(
|
||||
unifiedPushStore = FakeUnifiedPushStore(
|
||||
getEndpointResult = { "aEndpoint" },
|
||||
getPushGatewayResult = { "aGateway" },
|
||||
storeUpEndpointResult = storeUpEndpointResult,
|
||||
storePushGatewayResult = storePushGatewayResult,
|
||||
),
|
||||
pusherSubscriber = FakePusherSubscriber(
|
||||
unregisterPusherResult = lambda
|
||||
)
|
||||
)
|
||||
val result = useCase.execute(matrixClient, A_SECRET)
|
||||
assertThat(result.isSuccess).isTrue()
|
||||
lambda.assertions()
|
||||
.isCalledOnce()
|
||||
.with(value(matrixClient), value("aEndpoint"), value("aGateway"))
|
||||
storeUpEndpointResult.assertions()
|
||||
.isCalledOnce()
|
||||
.with(value(A_SECRET), value(null))
|
||||
storePushGatewayResult.assertions()
|
||||
.isCalledOnce()
|
||||
.with(value(A_SECRET), value(null))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test un registration error - no endpoint`() = runTest {
|
||||
val matrixClient = FakeMatrixClient()
|
||||
val useCase = createDefaultUnregisterUnifiedPushUseCase(
|
||||
unifiedPushStore = FakeUnifiedPushStore(
|
||||
getEndpointResult = { null },
|
||||
getPushGatewayResult = { "aGateway" },
|
||||
),
|
||||
)
|
||||
val result = useCase.execute(matrixClient, A_SECRET)
|
||||
assertThat(result.isFailure).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test un registration error - no gateway`() = runTest {
|
||||
val matrixClient = FakeMatrixClient()
|
||||
val useCase = createDefaultUnregisterUnifiedPushUseCase(
|
||||
unifiedPushStore = FakeUnifiedPushStore(
|
||||
getEndpointResult = { "aEndpoint" },
|
||||
getPushGatewayResult = { null },
|
||||
),
|
||||
)
|
||||
val result = useCase.execute(matrixClient, A_SECRET)
|
||||
assertThat(result.isFailure).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test un registration error`() = runTest {
|
||||
val matrixClient = FakeMatrixClient()
|
||||
val useCase = createDefaultUnregisterUnifiedPushUseCase(
|
||||
unifiedPushStore = FakeUnifiedPushStore(
|
||||
getEndpointResult = { "aEndpoint" },
|
||||
getPushGatewayResult = { "aGateway" },
|
||||
),
|
||||
pusherSubscriber = FakePusherSubscriber(
|
||||
unregisterPusherResult = { _, _, _ -> Result.failure(AN_EXCEPTION) }
|
||||
)
|
||||
)
|
||||
val result = useCase.execute(matrixClient, A_SECRET)
|
||||
assertThat(result.isFailure).isTrue()
|
||||
}
|
||||
|
||||
private fun createDefaultUnregisterUnifiedPushUseCase(
|
||||
unifiedPushStore: UnifiedPushStore = FakeUnifiedPushStore(),
|
||||
pusherSubscriber: PusherSubscriber = FakePusherSubscriber()
|
||||
): DefaultUnregisterUnifiedPushUseCase {
|
||||
val context = InstrumentationRegistry.getInstrumentation().context
|
||||
return DefaultUnregisterUnifiedPushUseCase(
|
||||
context = context,
|
||||
unifiedPushStore = unifiedPushStore,
|
||||
pusherSubscriber = pusherSubscriber
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import io.element.android.libraries.pushproviders.api.Distributor
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
|
||||
class FakeRegisterUnifiedPushUseCase(
|
||||
private val result: (Distributor, String) -> Result<Unit> = { _, _ -> lambdaError() }
|
||||
) : RegisterUnifiedPushUseCase {
|
||||
override suspend fun execute(distributor: Distributor, clientSecret: String): Result<Unit> {
|
||||
return result(distributor, clientSecret)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import io.element.android.libraries.pushproviders.unifiedpush.network.DiscoveryResponse
|
||||
import io.element.android.libraries.pushproviders.unifiedpush.network.UnifiedPushApi
|
||||
|
||||
class FakeUnifiedPushApiFactory(
|
||||
private val discoveryResponse: () -> DiscoveryResponse
|
||||
) : UnifiedPushApiFactory {
|
||||
var baseUrlParameter: String? = null
|
||||
private set
|
||||
|
||||
override fun create(baseUrl: String): UnifiedPushApi {
|
||||
baseUrlParameter = baseUrl
|
||||
return FakeUnifiedPushApi(discoveryResponse)
|
||||
}
|
||||
}
|
||||
|
||||
class FakeUnifiedPushApi(
|
||||
private val discoveryResponse: () -> DiscoveryResponse
|
||||
) : UnifiedPushApi {
|
||||
override suspend fun discover(): DiscoveryResponse {
|
||||
return discoveryResponse()
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
*
|
||||
* 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
|
||||
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
|
||||
class FakeUnifiedPushGatewayResolver(
|
||||
private val getGatewayResult: (String) -> String = { lambdaError() },
|
||||
) : UnifiedPushGatewayResolver {
|
||||
override suspend fun getGateway(endpoint: String): String {
|
||||
return getGatewayResult(endpoint)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
*
|
||||
* 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
|
||||
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
|
||||
class FakeUnifiedPushNewGatewayHandler(
|
||||
private val handleResult: suspend (String, String, String) -> Result<Unit> = { _, _, _ -> lambdaError() },
|
||||
) : UnifiedPushNewGatewayHandler {
|
||||
override suspend fun handle(endpoint: String, pushGateway: String, clientSecret: String): Result<Unit> {
|
||||
return handleResult(endpoint, pushGateway, clientSecret)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
|
||||
class FakeUnifiedPushStore(
|
||||
private val getEndpointResult: (String) -> String? = { lambdaError() },
|
||||
private val storeUpEndpointResult: (String, String?) -> Unit = { _, _ -> lambdaError() },
|
||||
private val getPushGatewayResult: (String) -> String? = { lambdaError() },
|
||||
private val storePushGatewayResult: (String, String?) -> Unit = { _, _ -> lambdaError() },
|
||||
private val getDistributorValueResult: (UserId) -> String? = { lambdaError() },
|
||||
private val setDistributorValueResult: (UserId, String) -> Unit = { _, _ -> lambdaError() },
|
||||
) : UnifiedPushStore {
|
||||
override fun getEndpoint(clientSecret: String): String? {
|
||||
return getEndpointResult(clientSecret)
|
||||
}
|
||||
|
||||
override fun storeUpEndpoint(clientSecret: String, endpoint: String?) {
|
||||
storeUpEndpointResult(clientSecret, endpoint)
|
||||
}
|
||||
|
||||
override fun getPushGateway(clientSecret: String): String? {
|
||||
return getPushGatewayResult(clientSecret)
|
||||
}
|
||||
|
||||
override fun storePushGateway(clientSecret: String, gateway: String?) {
|
||||
storePushGatewayResult(clientSecret, gateway)
|
||||
}
|
||||
|
||||
override fun getDistributorValue(userId: UserId): String? {
|
||||
return getDistributorValueResult(userId)
|
||||
}
|
||||
|
||||
override fun setDistributorValue(userId: UserId, value: String) {
|
||||
setDistributorValueResult(userId, value)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
|
||||
class FakeUnregisterUnifiedPushUseCase(
|
||||
private val result: (MatrixClient, String) -> Result<Unit> = { _, _ -> lambdaError() }
|
||||
) : UnregisterUnifiedPushUseCase {
|
||||
override suspend fun execute(matrixClient: MatrixClient, clientSecret: String): Result<Unit> {
|
||||
return result(matrixClient, clientSecret)
|
||||
}
|
||||
}
|
||||
@@ -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?
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,322 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
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.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
|
||||
|
||||
class UnifiedPushProviderTest {
|
||||
@Test
|
||||
fun `test index and name`() {
|
||||
val unifiedPushProvider = createUnifiedPushProvider()
|
||||
assertThat(unifiedPushProvider.name).isEqualTo(UnifiedPushConfig.NAME)
|
||||
assertThat(unifiedPushProvider.index).isEqualTo(UnifiedPushConfig.INDEX)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getDistributors return the available distributors`() {
|
||||
val unifiedPushProvider = createUnifiedPushProvider(
|
||||
unifiedPushDistributorProvider = FakeUnifiedPushDistributorProvider(
|
||||
getDistributorsResult = listOf(
|
||||
Distributor("value", "Name"),
|
||||
)
|
||||
)
|
||||
)
|
||||
val result = unifiedPushProvider.getDistributors()
|
||||
assertThat(result).containsExactly(Distributor("value", "Name"))
|
||||
assertThat(unifiedPushProvider.isAvailable()).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getDistributors return empty`() {
|
||||
val unifiedPushProvider = createUnifiedPushProvider(
|
||||
unifiedPushDistributorProvider = FakeUnifiedPushDistributorProvider(
|
||||
getDistributorsResult = emptyList()
|
||||
)
|
||||
)
|
||||
val result = unifiedPushProvider.getDistributors()
|
||||
assertThat(result).isEmpty()
|
||||
assertThat(unifiedPushProvider.isAvailable()).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `register ok`() = runTest {
|
||||
val getSecretForUserResultLambda = lambdaRecorder<SessionId, String> { A_SECRET }
|
||||
val executeLambda = lambdaRecorder<Distributor, String, Result<Unit>> { _, _ -> Result.success(Unit) }
|
||||
val setDistributorValueResultLambda = lambdaRecorder<UserId, String, Unit> { _, _ -> }
|
||||
val unifiedPushProvider = createUnifiedPushProvider(
|
||||
pushClientSecret = FakePushClientSecret(
|
||||
getSecretForUserResult = getSecretForUserResultLambda,
|
||||
),
|
||||
registerUnifiedPushUseCase = FakeRegisterUnifiedPushUseCase(
|
||||
result = executeLambda,
|
||||
),
|
||||
unifiedPushStore = FakeUnifiedPushStore(
|
||||
setDistributorValueResult = setDistributorValueResultLambda,
|
||||
),
|
||||
)
|
||||
val result = unifiedPushProvider.registerWith(FakeMatrixClient(), Distributor("value", "Name"))
|
||||
assertThat(result).isEqualTo(Result.success(Unit))
|
||||
getSecretForUserResultLambda.assertions()
|
||||
.isCalledOnce()
|
||||
.with(value(A_SESSION_ID))
|
||||
executeLambda.assertions()
|
||||
.isCalledOnce()
|
||||
.with(value(Distributor("value", "Name")), value(A_SECRET))
|
||||
setDistributorValueResultLambda.assertions()
|
||||
.isCalledOnce()
|
||||
.with(value(A_SESSION_ID), value("value"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `register ko`() = runTest {
|
||||
val getSecretForUserResultLambda = lambdaRecorder<SessionId, String> { A_SECRET }
|
||||
val executeLambda = lambdaRecorder<Distributor, String, Result<Unit>> { _, _ -> Result.failure(AN_EXCEPTION) }
|
||||
val setDistributorValueResultLambda = lambdaRecorder<UserId, String, Unit>(ensureNeverCalled = true) { _, _ -> }
|
||||
val unifiedPushProvider = createUnifiedPushProvider(
|
||||
pushClientSecret = FakePushClientSecret(
|
||||
getSecretForUserResult = getSecretForUserResultLambda,
|
||||
),
|
||||
registerUnifiedPushUseCase = FakeRegisterUnifiedPushUseCase(
|
||||
result = executeLambda,
|
||||
),
|
||||
unifiedPushStore = FakeUnifiedPushStore(
|
||||
setDistributorValueResult = setDistributorValueResultLambda,
|
||||
),
|
||||
)
|
||||
val result = unifiedPushProvider.registerWith(FakeMatrixClient(), Distributor("value", "Name"))
|
||||
assertThat(result).isEqualTo(Result.failure<Unit>(AN_EXCEPTION))
|
||||
getSecretForUserResultLambda.assertions()
|
||||
.isCalledOnce()
|
||||
.with(value(A_SESSION_ID))
|
||||
executeLambda.assertions()
|
||||
.isCalledOnce()
|
||||
.with(value(Distributor("value", "Name")), value(A_SECRET))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unregister ok`() = runTest {
|
||||
val matrixClient = FakeMatrixClient()
|
||||
val getSecretForUserResultLambda = lambdaRecorder<SessionId, String> { A_SECRET }
|
||||
val executeLambda = lambdaRecorder<MatrixClient, String, Result<Unit>> { _, _ -> Result.success(Unit) }
|
||||
val unifiedPushProvider = createUnifiedPushProvider(
|
||||
pushClientSecret = FakePushClientSecret(
|
||||
getSecretForUserResult = getSecretForUserResultLambda,
|
||||
),
|
||||
unRegisterUnifiedPushUseCase = FakeUnregisterUnifiedPushUseCase(
|
||||
result = executeLambda,
|
||||
),
|
||||
)
|
||||
val result = unifiedPushProvider.unregister(matrixClient)
|
||||
assertThat(result).isEqualTo(Result.success(Unit))
|
||||
getSecretForUserResultLambda.assertions()
|
||||
.isCalledOnce()
|
||||
.with(value(A_SESSION_ID))
|
||||
executeLambda.assertions()
|
||||
.isCalledOnce()
|
||||
.with(value(matrixClient), value(A_SECRET))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unregister ko`() = runTest {
|
||||
val matrixClient = FakeMatrixClient()
|
||||
val getSecretForUserResultLambda = lambdaRecorder<SessionId, String> { A_SECRET }
|
||||
val executeLambda = lambdaRecorder<MatrixClient, String, Result<Unit>> { _, _ -> Result.failure(AN_EXCEPTION) }
|
||||
val unifiedPushProvider = createUnifiedPushProvider(
|
||||
pushClientSecret = FakePushClientSecret(
|
||||
getSecretForUserResult = getSecretForUserResultLambda,
|
||||
),
|
||||
unRegisterUnifiedPushUseCase = FakeUnregisterUnifiedPushUseCase(
|
||||
result = executeLambda,
|
||||
),
|
||||
)
|
||||
val result = unifiedPushProvider.unregister(matrixClient)
|
||||
assertThat(result).isEqualTo(Result.failure<Unit>(AN_EXCEPTION))
|
||||
getSecretForUserResultLambda.assertions()
|
||||
.isCalledOnce()
|
||||
.with(value(A_SESSION_ID))
|
||||
executeLambda.assertions()
|
||||
.isCalledOnce()
|
||||
.with(value(matrixClient), value(A_SECRET))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getCurrentDistributor ok`() = runTest {
|
||||
val distributor = Distributor("value", "Name")
|
||||
val matrixClient = FakeMatrixClient()
|
||||
val unifiedPushProvider = createUnifiedPushProvider(
|
||||
unifiedPushStore = FakeUnifiedPushStore(
|
||||
getDistributorValueResult = { distributor.value }
|
||||
),
|
||||
unifiedPushDistributorProvider = FakeUnifiedPushDistributorProvider(
|
||||
getDistributorsResult = listOf(
|
||||
Distributor("value2", "Name2"),
|
||||
distributor,
|
||||
)
|
||||
)
|
||||
)
|
||||
val result = unifiedPushProvider.getCurrentDistributor(matrixClient)
|
||||
assertThat(result).isEqualTo(distributor)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getCurrentDistributor not know`() = runTest {
|
||||
val distributor = Distributor("value", "Name")
|
||||
val matrixClient = FakeMatrixClient()
|
||||
val unifiedPushProvider = createUnifiedPushProvider(
|
||||
unifiedPushStore = FakeUnifiedPushStore(
|
||||
getDistributorValueResult = { "unknown" }
|
||||
),
|
||||
unifiedPushDistributorProvider = FakeUnifiedPushDistributorProvider(
|
||||
getDistributorsResult = listOf(
|
||||
distributor,
|
||||
)
|
||||
)
|
||||
)
|
||||
val result = unifiedPushProvider.getCurrentDistributor(matrixClient)
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getCurrentDistributor not found`() = runTest {
|
||||
val distributor = Distributor("value", "Name")
|
||||
val matrixClient = FakeMatrixClient()
|
||||
val unifiedPushProvider = createUnifiedPushProvider(
|
||||
unifiedPushStore = FakeUnifiedPushStore(
|
||||
getDistributorValueResult = { distributor.value }
|
||||
),
|
||||
unifiedPushDistributorProvider = FakeUnifiedPushDistributorProvider(
|
||||
getDistributorsResult = emptyList()
|
||||
)
|
||||
)
|
||||
val result = unifiedPushProvider.getCurrentDistributor(matrixClient)
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getCurrentUserPushConfig no session`() = runTest {
|
||||
val unifiedPushProvider = createUnifiedPushProvider()
|
||||
val result = unifiedPushProvider.getCurrentUserPushConfig()
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getCurrentUserPushConfig no push gateway`() = 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 = { null }
|
||||
),
|
||||
)
|
||||
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"))
|
||||
}
|
||||
|
||||
private fun createUnifiedPushProvider(
|
||||
unifiedPushDistributorProvider: UnifiedPushDistributorProvider = FakeUnifiedPushDistributorProvider(),
|
||||
registerUnifiedPushUseCase: RegisterUnifiedPushUseCase = FakeRegisterUnifiedPushUseCase(),
|
||||
unRegisterUnifiedPushUseCase: UnregisterUnifiedPushUseCase = FakeUnregisterUnifiedPushUseCase(),
|
||||
pushClientSecret: PushClientSecret = FakePushClientSecret(),
|
||||
unifiedPushStore: UnifiedPushStore = FakeUnifiedPushStore(),
|
||||
appNavigationStateService: AppNavigationStateService = FakeAppNavigationStateService(),
|
||||
): UnifiedPushProvider {
|
||||
return UnifiedPushProvider(
|
||||
unifiedPushDistributorProvider = unifiedPushDistributorProvider,
|
||||
registerUnifiedPushUseCase = registerUnifiedPushUseCase,
|
||||
unRegisterUnifiedPushUseCase = unRegisterUnifiedPushUseCase,
|
||||
pushClientSecret = pushClientSecret,
|
||||
unifiedPushStore = unifiedPushStore,
|
||||
appNavigationStateService = appNavigationStateService
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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<PushData, Unit> {}
|
||||
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<PushData, Unit> {}
|
||||
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<String, String?, Unit> { _, _ -> }
|
||||
val storeUpEndpointResult = lambdaRecorder<String, String?, Unit> { _, _ -> }
|
||||
val unifiedPushStore = FakeUnifiedPushStore(
|
||||
storePushGatewayResult = storePushGatewayResult,
|
||||
storeUpEndpointResult = storeUpEndpointResult,
|
||||
)
|
||||
val endpointRegistrationHandler = EndpointRegistrationHandler()
|
||||
val handleResult = lambdaRecorder<String, String, String, Result<Unit>> { _, _, _ -> 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(A_SECRET), value("aGateway"))
|
||||
storeUpEndpointResult.assertions()
|
||||
.isCalledOnce()
|
||||
.with(value(A_SECRET), value("anEndpoint"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `onNewEndpoint, if registration fails, the endpoint should not be stored`() = runTest {
|
||||
val context = InstrumentationRegistry.getInstrumentation().context
|
||||
val storePushGatewayResult = lambdaRecorder<String, String?, Unit> { _, _ -> }
|
||||
val storeUpEndpointResult = lambdaRecorder<String, String?, Unit> { _, _ -> }
|
||||
val unifiedPushStore = FakeUnifiedPushStore(
|
||||
storePushGatewayResult = storePushGatewayResult,
|
||||
storeUpEndpointResult = storeUpEndpointResult,
|
||||
)
|
||||
val endpointRegistrationHandler = EndpointRegistrationHandler()
|
||||
val handleResult = lambdaRecorder<String, String, String, Result<Unit>> { _, _, _ -> 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(A_SECRET), value("aGateway"))
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,9 @@ 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.Distributor
|
||||
import io.element.android.libraries.pushproviders.unifiedpush.UnifiedPushConfig
|
||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState
|
||||
import io.element.android.libraries.troubleshoot.api.test.TestFilterData
|
||||
import io.element.android.services.toolbox.test.strings.FakeStringProvider
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.test.runTest
|
||||
@@ -81,4 +83,15 @@ class UnifiedPushTestTest {
|
||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Success)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test isRelevant`() {
|
||||
val sut = UnifiedPushTest(
|
||||
unifiedPushDistributorProvider = FakeUnifiedPushDistributorProvider(),
|
||||
openDistributorWebPageAction = FakeOpenDistributorWebPageAction(),
|
||||
stringProvider = FakeStringProvider(),
|
||||
)
|
||||
assertThat(sut.isRelevant(TestFilterData(currentPushProviderName = UnifiedPushConfig.NAME))).isTrue()
|
||||
assertThat(sut.isRelevant(TestFilterData(currentPushProviderName = "other"))).isFalse()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ dependencies {
|
||||
testImplementation(libs.coroutines.test)
|
||||
testImplementation(projects.libraries.matrix.test)
|
||||
testImplementation(projects.services.appnavstate.test)
|
||||
testImplementation(projects.libraries.pushstore.test)
|
||||
testImplementation(projects.libraries.sessionStorage.test)
|
||||
|
||||
androidTestImplementation(libs.coroutines.test)
|
||||
|
||||
@@ -18,6 +18,7 @@ package io.element.android.libraries.pushstore.impl.clientsecret
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.pushstore.test.userpushstore.clientsecret.InMemoryPushClientSecretStore
|
||||
import io.element.android.libraries.sessionstorage.test.observer.NoOpSessionObserver
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
@@ -14,14 +14,15 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.element.android.libraries.pushstore.test.userpushstore
|
||||
package io.element.android.libraries.pushstore.test.userpushstore
|
||||
|
||||
import io.element.android.libraries.pushstore.api.UserPushStore
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
class FakeUserPushStore : UserPushStore {
|
||||
class FakeUserPushStore(
|
||||
private var pushProviderName: String? = null
|
||||
) : UserPushStore {
|
||||
private var currentRegisteredPushKey: String? = null
|
||||
private val notificationEnabledForDevice = MutableStateFlow(true)
|
||||
override suspend fun getPushProviderName(): String? {
|
||||
@@ -14,14 +14,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.element.android.libraries.pushstore.test.userpushstore
|
||||
package io.element.android.libraries.pushstore.test.userpushstore
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.pushstore.api.UserPushStore
|
||||
import io.element.android.libraries.pushstore.api.UserPushStoreFactory
|
||||
|
||||
class FakeUserPushStoreFactory : UserPushStoreFactory {
|
||||
class FakeUserPushStoreFactory(
|
||||
val userPushStore: (SessionId) -> UserPushStore = { FakeUserPushStore() }
|
||||
) : UserPushStoreFactory {
|
||||
override fun getOrCreate(userId: SessionId): UserPushStore {
|
||||
return FakeUserPushStore()
|
||||
return userPushStore(userId)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.pushstore.test.userpushstore.clientsecret
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
|
||||
class FakePushClientSecret(
|
||||
private val getSecretForUserResult: (SessionId) -> String = { lambdaError() },
|
||||
private val getUserIdFromSecretResult: (String) -> SessionId? = { lambdaError() }
|
||||
) : PushClientSecret {
|
||||
override suspend fun getSecretForUser(userId: SessionId): String {
|
||||
return getSecretForUserResult(userId)
|
||||
}
|
||||
|
||||
override suspend fun getUserIdFromSecret(clientSecret: String): SessionId? {
|
||||
return getUserIdFromSecretResult(clientSecret)
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.pushstore.impl.clientsecret
|
||||
package io.element.android.libraries.pushstore.test.userpushstore.clientsecret
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecretStore
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (c) 2023 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.sessionstorage.impl.memory
|
||||
|
||||
import io.element.android.libraries.sessionstorage.api.LoggedInState
|
||||
import io.element.android.libraries.sessionstorage.api.SessionData
|
||||
import io.element.android.libraries.sessionstorage.api.SessionStore
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
class InMemoryMultiSessionsStore : SessionStore {
|
||||
private val sessions = mutableListOf<SessionData>()
|
||||
|
||||
override fun isLoggedIn(): Flow<LoggedInState> = error("Not implemented")
|
||||
|
||||
override fun sessionsFlow(): Flow<List<SessionData>> = error("Not implemented")
|
||||
|
||||
override suspend fun storeData(sessionData: SessionData) {
|
||||
sessions.add(sessionData)
|
||||
}
|
||||
|
||||
override suspend fun updateData(sessionData: SessionData) = error("Not implemented")
|
||||
|
||||
override suspend fun getSession(sessionId: String): SessionData? = error("Not implemented")
|
||||
|
||||
override suspend fun getAllSessions(): List<SessionData> = sessions
|
||||
|
||||
override suspend fun getLatestSession(): SessionData = error("Not implemented")
|
||||
|
||||
override suspend fun removeSession(sessionId: String) = error("Not implemented")
|
||||
}
|
||||
@@ -164,7 +164,7 @@ internal fun MentionSpanPreview() {
|
||||
eventId = null,
|
||||
viaParameters = persistentListOf(),
|
||||
)
|
||||
else -> TODO()
|
||||
else -> throw AssertionError("Unexpected value $uriString")
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -16,26 +16,28 @@
|
||||
|
||||
package io.element.android.tests.testutils
|
||||
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
|
||||
class EnsureNeverCalled : () -> Unit {
|
||||
override fun invoke() {
|
||||
throw AssertionError("Should not be called")
|
||||
lambdaError()
|
||||
}
|
||||
}
|
||||
|
||||
class EnsureNeverCalledWithParam<T> : (T) -> Unit {
|
||||
override fun invoke(p1: T) {
|
||||
throw AssertionError("Should not be called and is called with $p1")
|
||||
lambdaError("Should not be called and is called with $p1")
|
||||
}
|
||||
}
|
||||
|
||||
class EnsureNeverCalledWithParamAndResult<T, R> : (T) -> R {
|
||||
override fun invoke(p1: T): R {
|
||||
throw AssertionError("Should not be called and is called with $p1")
|
||||
lambdaError("Should not be called and is called with $p1")
|
||||
}
|
||||
}
|
||||
|
||||
class EnsureNeverCalledWithTwoParams<T, U> : (T, U) -> Unit {
|
||||
override fun invoke(p1: T, p2: U) {
|
||||
throw AssertionError("Should not be called and is called with $p1 and $p2")
|
||||
lambdaError("Should not be called and is called with $p1 and $p2")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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.tests.testutils.lambda
|
||||
|
||||
fun lambdaError(
|
||||
message: String = "This lambda should never be called."
|
||||
): Nothing {
|
||||
throw AssertionError(message)
|
||||
}
|
||||
@@ -26,7 +26,7 @@ abstract class LambdaRecorder internal constructor(
|
||||
|
||||
internal fun onInvoke(vararg params: Any?) {
|
||||
if (assertNoInvocation) {
|
||||
throw AssertionError("This lambda should never be called.")
|
||||
lambdaError()
|
||||
}
|
||||
parametersSequence.add(params.toList())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user