Merge pull request #2899 from element-hq/feature/bma/unitTestPush
Add unit tests in some push classes
This commit is contained in:
commit
498e8e7897
87 changed files with 3396 additions and 285 deletions
|
|
@ -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_SESSION_ID
|
||||||
import io.element.android.libraries.matrix.test.A_THREAD_ID
|
import io.element.android.libraries.matrix.test.A_THREAD_ID
|
||||||
import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser
|
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.Assert.assertThrows
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
@ -229,7 +230,7 @@ class IntentResolverTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createIntentResolver(
|
private fun createIntentResolver(
|
||||||
permalinkParserResult: () -> PermalinkData = { throw NotImplementedError() }
|
permalinkParserResult: () -> PermalinkData = { lambdaError() }
|
||||||
): IntentResolver {
|
): IntentResolver {
|
||||||
return IntentResolver(
|
return IntentResolver(
|
||||||
deeplinkParser = DeeplinkParser(),
|
deeplinkParser = DeeplinkParser(),
|
||||||
|
|
|
||||||
|
|
@ -192,9 +192,11 @@ class AcceptDeclineInvitePresenterTest {
|
||||||
cancelAndConsumeRemainingEvents()
|
cancelAndConsumeRemainingEvents()
|
||||||
}
|
}
|
||||||
assert(joinRoomFailure)
|
assert(joinRoomFailure)
|
||||||
.isCalledExactly(1)
|
.isCalledOnce()
|
||||||
.withSequence(
|
.with(
|
||||||
listOf(value(A_ROOM_ID), value(emptyList<String>()), value(JoinedRoom.Trigger.Invite))
|
value(A_ROOM_ID),
|
||||||
|
value(emptyList<String>()),
|
||||||
|
value(JoinedRoom.Trigger.Invite)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -221,9 +223,11 @@ class AcceptDeclineInvitePresenterTest {
|
||||||
cancelAndConsumeRemainingEvents()
|
cancelAndConsumeRemainingEvents()
|
||||||
}
|
}
|
||||||
assert(joinRoomSuccess)
|
assert(joinRoomSuccess)
|
||||||
.isCalledExactly(1)
|
.isCalledOnce()
|
||||||
.withSequence(
|
.with(
|
||||||
listOf(value(A_ROOM_ID), value(emptyList<String>()), value(JoinedRoom.Trigger.Invite))
|
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.RecompositionMode
|
||||||
import app.cash.molecule.moleculeFlow
|
import app.cash.molecule.moleculeFlow
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
import com.element.android.libraries.pushstore.test.userpushstore.FakeUserPushStoreFactory
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
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.A_THROWABLE
|
||||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||||
import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService
|
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 io.element.android.tests.testutils.consumeItemsUntilPredicate
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
|
||||||
|
|
@ -136,7 +136,7 @@ class BugReportPresenterTest {
|
||||||
initialState.eventSink.invoke(BugReportEvents.ResetAll)
|
initialState.eventSink.invoke(BugReportEvents.ResetAll)
|
||||||
val resetState = awaitItem()
|
val resetState = awaitItem()
|
||||||
assertThat(resetState.hasCrashLogs).isFalse()
|
assertThat(resetState.hasCrashLogs).isFalse()
|
||||||
logFilesRemoverLambda.assertions().isCalledExactly(1)
|
logFilesRemoverLambda.assertions().isCalledOnce()
|
||||||
// TODO Make it live assertThat(resetState.screenshotUri).isNull()
|
// TODO Make it live assertThat(resetState.screenshotUri).isNull()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -57,9 +57,9 @@ class DefaultJoinRoomTest {
|
||||||
.isNeverCalled()
|
.isNeverCalled()
|
||||||
joinRoomLambda
|
joinRoomLambda
|
||||||
.assertions()
|
.assertions()
|
||||||
.isCalledExactly(1)
|
.isCalledOnce()
|
||||||
.withSequence(
|
.with(
|
||||||
listOf(value(A_ROOM_ID))
|
value(A_ROOM_ID)
|
||||||
)
|
)
|
||||||
assertThat(analyticsService.capturedEvents).containsExactly(
|
assertThat(analyticsService.capturedEvents).containsExactly(
|
||||||
roomResult.toAnalyticsJoinedRoom(aTrigger)
|
roomResult.toAnalyticsJoinedRoom(aTrigger)
|
||||||
|
|
@ -88,9 +88,10 @@ class DefaultJoinRoomTest {
|
||||||
sut.invoke(A_ROOM_ID, A_SERVER_LIST, aTrigger)
|
sut.invoke(A_ROOM_ID, A_SERVER_LIST, aTrigger)
|
||||||
joinRoomByIdOrAliasLambda
|
joinRoomByIdOrAliasLambda
|
||||||
.assertions()
|
.assertions()
|
||||||
.isCalledExactly(1)
|
.isCalledOnce()
|
||||||
.withSequence(
|
.with(
|
||||||
listOf(value(A_ROOM_ID), value(A_SERVER_LIST))
|
value(A_ROOM_ID),
|
||||||
|
value(A_SERVER_LIST)
|
||||||
)
|
)
|
||||||
joinRoomLambda
|
joinRoomLambda
|
||||||
.assertions()
|
.assertions()
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ import io.element.android.libraries.matrix.api.room.RoomNotificationSettings
|
||||||
|
|
||||||
const val A_USER_NAME = "alice"
|
const val A_USER_NAME = "alice"
|
||||||
const val A_PASSWORD = "password"
|
const val A_PASSWORD = "password"
|
||||||
|
const val A_SECRET = "secret"
|
||||||
|
|
||||||
val A_USER_ID = UserId("@alice:server.org")
|
val A_USER_ID = UserId("@alice:server.org")
|
||||||
val A_USER_ID_2 = UserId("@bob: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")
|
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 val homeserver = MutableStateFlow<MatrixHomeServerDetails?>(null)
|
||||||
private var oidcError: Throwable? = null
|
private var oidcError: Throwable? = null
|
||||||
private var oidcCancelError: Throwable? = null
|
private var oidcCancelError: Throwable? = null
|
||||||
|
|
@ -48,6 +50,9 @@ class FakeAuthenticationService : MatrixAuthenticationService {
|
||||||
override suspend fun getLatestSessionId(): SessionId? = getLatestSessionIdLambda()
|
override suspend fun getLatestSessionId(): SessionId? = getLatestSessionIdLambda()
|
||||||
|
|
||||||
override suspend fun restoreSession(sessionId: SessionId): Result<MatrixClient> {
|
override suspend fun restoreSession(sessionId: SessionId): Result<MatrixClient> {
|
||||||
|
if (matrixClientResult != null) {
|
||||||
|
return matrixClientResult.invoke(sessionId)
|
||||||
|
}
|
||||||
return if (matrixClient != null) {
|
return if (matrixClient != null) {
|
||||||
Result.success(matrixClient!!)
|
Result.success(matrixClient!!)
|
||||||
} else {
|
} 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.PermalinkData
|
||||||
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
|
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
|
||||||
|
import io.element.android.tests.testutils.lambda.lambdaError
|
||||||
|
|
||||||
class FakePermalinkParser(
|
class FakePermalinkParser(
|
||||||
private var result: () -> PermalinkData = { TODO("Not implemented") }
|
private var result: () -> PermalinkData = { lambdaError() }
|
||||||
) : PermalinkParser {
|
) : PermalinkParser {
|
||||||
fun givenResult(result: PermalinkData) {
|
fun givenResult(result: PermalinkData) {
|
||||||
this.result = { result }
|
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.PushersService
|
||||||
import io.element.android.libraries.matrix.api.pusher.SetHttpPusherData
|
import io.element.android.libraries.matrix.api.pusher.SetHttpPusherData
|
||||||
import io.element.android.libraries.matrix.api.pusher.UnsetHttpPusherData
|
import io.element.android.libraries.matrix.api.pusher.UnsetHttpPusherData
|
||||||
|
import io.element.android.tests.testutils.lambda.lambdaError
|
||||||
|
|
||||||
class FakePushersService : PushersService {
|
class FakePushersService(
|
||||||
override suspend fun setHttpPusher(setHttpPusherData: SetHttpPusherData) = Result.success(Unit)
|
private val setHttpPusherResult: (SetHttpPusherData) -> Result<Unit> = { lambdaError() },
|
||||||
override suspend fun unsetHttpPusher(unsetHttpPusherData: UnsetHttpPusherData): Result<Unit> = Result.success(Unit)
|
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
|
import io.element.android.libraries.pushproviders.api.PushProvider
|
||||||
|
|
||||||
interface PushService {
|
interface PushService {
|
||||||
// TODO Move away
|
|
||||||
fun notificationStyleChanged()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the current push provider, or null if none.
|
* Return the current push provider, or null if none.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,7 @@ dependencies {
|
||||||
testImplementation(projects.libraries.matrix.test)
|
testImplementation(projects.libraries.matrix.test)
|
||||||
testImplementation(projects.libraries.push.test)
|
testImplementation(projects.libraries.push.test)
|
||||||
testImplementation(projects.libraries.pushproviders.test)
|
testImplementation(projects.libraries.pushproviders.test)
|
||||||
|
testImplementation(projects.libraries.pushstore.test)
|
||||||
testImplementation(projects.tests.testutils)
|
testImplementation(projects.tests.testutils)
|
||||||
testImplementation(projects.services.appnavstate.test)
|
testImplementation(projects.services.appnavstate.test)
|
||||||
testImplementation(projects.services.toolbox.impl)
|
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.matrix.api.MatrixClient
|
||||||
import io.element.android.libraries.push.api.GetCurrentPushProvider
|
import io.element.android.libraries.push.api.GetCurrentPushProvider
|
||||||
import io.element.android.libraries.push.api.PushService
|
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.Distributor
|
||||||
import io.element.android.libraries.pushproviders.api.PushProvider
|
import io.element.android.libraries.pushproviders.api.PushProvider
|
||||||
import io.element.android.libraries.pushstore.api.UserPushStoreFactory
|
import io.element.android.libraries.pushstore.api.UserPushStoreFactory
|
||||||
|
|
@ -30,16 +30,11 @@ import javax.inject.Inject
|
||||||
|
|
||||||
@ContributesBinding(AppScope::class)
|
@ContributesBinding(AppScope::class)
|
||||||
class DefaultPushService @Inject constructor(
|
class DefaultPushService @Inject constructor(
|
||||||
private val defaultNotificationDrawerManager: DefaultNotificationDrawerManager,
|
private val testPush: TestPush,
|
||||||
private val pushersManager: PushersManager,
|
|
||||||
private val userPushStoreFactory: UserPushStoreFactory,
|
private val userPushStoreFactory: UserPushStoreFactory,
|
||||||
private val pushProviders: Set<@JvmSuppressWildcards PushProvider>,
|
private val pushProviders: Set<@JvmSuppressWildcards PushProvider>,
|
||||||
private val getCurrentPushProvider: GetCurrentPushProvider,
|
private val getCurrentPushProvider: GetCurrentPushProvider,
|
||||||
) : PushService {
|
) : PushService {
|
||||||
override fun notificationStyleChanged() {
|
|
||||||
defaultNotificationDrawerManager.notificationStyleChanged()
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getCurrentPushProvider(): PushProvider? {
|
override suspend fun getCurrentPushProvider(): PushProvider? {
|
||||||
val currentPushProvider = getCurrentPushProvider.getCurrentPushProvider()
|
val currentPushProvider = getCurrentPushProvider.getCurrentPushProvider()
|
||||||
return pushProviders.find { it.name == currentPushProvider }
|
return pushProviders.find { it.name == currentPushProvider }
|
||||||
|
|
@ -51,9 +46,6 @@ class DefaultPushService @Inject constructor(
|
||||||
.sortedBy { it.index }
|
.sortedBy { it.index }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get current push provider, compare with provided one, then unregister and register if different, and store change.
|
|
||||||
*/
|
|
||||||
override suspend fun registerWith(
|
override suspend fun registerWith(
|
||||||
matrixClient: MatrixClient,
|
matrixClient: MatrixClient,
|
||||||
pushProvider: PushProvider,
|
pushProvider: PushProvider,
|
||||||
|
|
@ -80,7 +72,7 @@ class DefaultPushService @Inject constructor(
|
||||||
override suspend fun testPush(): Boolean {
|
override suspend fun testPush(): Boolean {
|
||||||
val pushProvider = getCurrentPushProvider() ?: return false
|
val pushProvider = getCurrentPushProvider() ?: return false
|
||||||
val config = pushProvider.getCurrentUserPushConfig() ?: return false
|
val config = pushProvider.getCurrentUserPushConfig() ?: return false
|
||||||
pushersManager.testPush(config)
|
testPush.execute(config)
|
||||||
return true
|
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.core.meta.BuildMeta
|
||||||
import io.element.android.libraries.di.AppScope
|
import io.element.android.libraries.di.AppScope
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
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.core.SessionId
|
||||||
import io.element.android.libraries.matrix.api.pusher.SetHttpPusherData
|
import io.element.android.libraries.matrix.api.pusher.SetHttpPusherData
|
||||||
import io.element.android.libraries.matrix.api.pusher.UnsetHttpPusherData
|
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.pushproviders.api.PusherSubscriber
|
||||||
import io.element.android.libraries.pushstore.api.UserPushStoreFactory
|
import io.element.android.libraries.pushstore.api.UserPushStoreFactory
|
||||||
import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret
|
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"
|
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)
|
@ContributesBinding(AppScope::class)
|
||||||
class PushersManager @Inject constructor(
|
class DefaultPusherSubscriber @Inject constructor(
|
||||||
// private val localeProvider: LocaleProvider,
|
|
||||||
private val buildMeta: BuildMeta,
|
private val buildMeta: BuildMeta,
|
||||||
// private val getDeviceInfoUseCase: GetDeviceInfoUseCase,
|
|
||||||
private val pushGatewayNotifyRequest: PushGatewayNotifyRequest,
|
|
||||||
private val pushClientSecret: PushClientSecret,
|
private val pushClientSecret: PushClientSecret,
|
||||||
private val userPushStoreFactory: UserPushStoreFactory,
|
private val userPushStoreFactory: UserPushStoreFactory,
|
||||||
) : PusherSubscriber {
|
) : 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.
|
* 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")
|
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.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.core.content.FileProvider
|
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.core.log.logger.LoggerTag
|
||||||
|
import io.element.android.libraries.di.AppScope
|
||||||
import io.element.android.libraries.di.ApplicationContext
|
import io.element.android.libraries.di.ApplicationContext
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
import io.element.android.libraries.matrix.api.MatrixClientProvider
|
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 timber.log.Timber
|
||||||
import javax.inject.Inject
|
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.
|
* 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,
|
* 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.
|
* 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 stringProvider: StringProvider,
|
||||||
private val clock: SystemClock,
|
private val clock: SystemClock,
|
||||||
private val matrixClientProvider: MatrixClientProvider,
|
private val matrixClientProvider: MatrixClientProvider,
|
||||||
private val notificationMediaRepoFactory: NotificationMediaRepo.Factory,
|
private val notificationMediaRepoFactory: NotificationMediaRepo.Factory,
|
||||||
@ApplicationContext private val context: Context,
|
@ApplicationContext private val context: Context,
|
||||||
private val permalinkParser: PermalinkParser,
|
private val permalinkParser: PermalinkParser,
|
||||||
) {
|
) : NotifiableEventResolver {
|
||||||
suspend fun resolveEvent(sessionId: SessionId, roomId: RoomId, eventId: EventId): NotifiableEvent? {
|
override suspend fun resolveEvent(sessionId: SessionId, roomId: RoomId, eventId: EventId): NotifiableEvent? {
|
||||||
// Restore session
|
// Restore session
|
||||||
val client = matrixClientProvider.getOrRestore(sessionId).getOrNull() ?: return null
|
val client = matrixClientProvider.getOrRestore(sessionId).getOrNull() ?: return null
|
||||||
val notificationService = client.notificationService()
|
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(
|
private fun updateEvents(
|
||||||
doRender: Boolean,
|
doRender: Boolean,
|
||||||
action: (NotificationEventQueue) -> Unit,
|
action: (NotificationEventQueue) -> Unit,
|
||||||
|
|
|
||||||
|
|
@ -16,27 +16,19 @@
|
||||||
|
|
||||||
package io.element.android.libraries.push.impl.push
|
package io.element.android.libraries.push.impl.push
|
||||||
|
|
||||||
import android.os.Handler
|
|
||||||
import android.os.Looper
|
|
||||||
import com.squareup.anvil.annotations.ContributesBinding
|
import com.squareup.anvil.annotations.ContributesBinding
|
||||||
import io.element.android.libraries.core.log.logger.LoggerTag
|
import io.element.android.libraries.core.log.logger.LoggerTag
|
||||||
import io.element.android.libraries.core.meta.BuildMeta
|
import io.element.android.libraries.core.meta.BuildMeta
|
||||||
import io.element.android.libraries.di.AppScope
|
import io.element.android.libraries.di.AppScope
|
||||||
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
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.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.push.impl.troubleshoot.DiagnosticPushHandler
|
||||||
import io.element.android.libraries.pushproviders.api.PushData
|
import io.element.android.libraries.pushproviders.api.PushData
|
||||||
import io.element.android.libraries.pushproviders.api.PushHandler
|
import io.element.android.libraries.pushproviders.api.PushHandler
|
||||||
import io.element.android.libraries.pushstore.api.UserPushStoreFactory
|
import io.element.android.libraries.pushstore.api.UserPushStoreFactory
|
||||||
import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret
|
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.flow.first
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
@ -44,23 +36,15 @@ private val loggerTag = LoggerTag("PushHandler", LoggerTag.PushLoggerTag)
|
||||||
|
|
||||||
@ContributesBinding(AppScope::class)
|
@ContributesBinding(AppScope::class)
|
||||||
class DefaultPushHandler @Inject constructor(
|
class DefaultPushHandler @Inject constructor(
|
||||||
private val defaultNotificationDrawerManager: DefaultNotificationDrawerManager,
|
private val onNotifiableEventReceived: OnNotifiableEventReceived,
|
||||||
private val notifiableEventResolver: NotifiableEventResolver,
|
private val notifiableEventResolver: NotifiableEventResolver,
|
||||||
private val defaultPushDataStore: DefaultPushDataStore,
|
private val incrementPushDataStore: IncrementPushDataStore,
|
||||||
private val userPushStoreFactory: UserPushStoreFactory,
|
private val userPushStoreFactory: UserPushStoreFactory,
|
||||||
private val pushClientSecret: PushClientSecret,
|
private val pushClientSecret: PushClientSecret,
|
||||||
// private val actionIds: NotificationActionIds,
|
|
||||||
private val buildMeta: BuildMeta,
|
private val buildMeta: BuildMeta,
|
||||||
private val matrixAuthenticationService: MatrixAuthenticationService,
|
private val matrixAuthenticationService: MatrixAuthenticationService,
|
||||||
private val diagnosticPushHandler: DiagnosticPushHandler,
|
private val diagnosticPushHandler: DiagnosticPushHandler,
|
||||||
) : PushHandler {
|
) : PushHandler {
|
||||||
private val coroutineScope = CoroutineScope(SupervisorJob())
|
|
||||||
|
|
||||||
// UI handler
|
|
||||||
private val uiHandler by lazy {
|
|
||||||
Handler(Looper.getMainLooper())
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when message is received.
|
* Called when message is received.
|
||||||
*
|
*
|
||||||
|
|
@ -68,21 +52,15 @@ class DefaultPushHandler @Inject constructor(
|
||||||
*/
|
*/
|
||||||
override suspend fun handle(pushData: PushData) {
|
override suspend fun handle(pushData: PushData) {
|
||||||
Timber.tag(loggerTag.value).d("## handling pushData: ${pushData.roomId}/${pushData.eventId}")
|
Timber.tag(loggerTag.value).d("## handling pushData: ${pushData.roomId}/${pushData.eventId}")
|
||||||
|
|
||||||
if (buildMeta.lowPrivacyLoggingEnabled) {
|
if (buildMeta.lowPrivacyLoggingEnabled) {
|
||||||
Timber.tag(loggerTag.value).d("## pushData: $pushData")
|
Timber.tag(loggerTag.value).d("## pushData: $pushData")
|
||||||
}
|
}
|
||||||
|
incrementPushDataStore.incrementPushCounter()
|
||||||
defaultPushDataStore.incrementPushCounter()
|
|
||||||
|
|
||||||
// Diagnostic Push
|
// Diagnostic Push
|
||||||
if (pushData.eventId == PushersManager.TEST_EVENT_ID) {
|
if (pushData.eventId == DefaultTestPush.TEST_EVENT_ID) {
|
||||||
diagnosticPushHandler.handlePush()
|
diagnosticPushHandler.handlePush()
|
||||||
return
|
} else {
|
||||||
}
|
handleInternal(pushData)
|
||||||
|
|
||||||
uiHandler.post {
|
|
||||||
coroutineScope.launch(Dispatchers.IO) { handleInternal(pushData) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -98,7 +76,6 @@ class DefaultPushHandler @Inject constructor(
|
||||||
} else {
|
} else {
|
||||||
Timber.tag(loggerTag.value).d("## handleInternal()")
|
Timber.tag(loggerTag.value).d("## handleInternal()")
|
||||||
}
|
}
|
||||||
|
|
||||||
val clientSecret = pushData.clientSecret
|
val clientSecret = pushData.clientSecret
|
||||||
// clientSecret should not be null. If this happens, restore default session
|
// clientSecret should not be null. If this happens, restore default session
|
||||||
val userId = clientSecret
|
val userId = clientSecret
|
||||||
|
|
@ -109,27 +86,22 @@ class DefaultPushHandler @Inject constructor(
|
||||||
?: run {
|
?: run {
|
||||||
matrixAuthenticationService.getLatestSessionId()
|
matrixAuthenticationService.getLatestSessionId()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userId == null) {
|
if (userId == null) {
|
||||||
Timber.w("Unable to get a session")
|
Timber.w("Unable to get a session")
|
||||||
return
|
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)
|
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
|
// TODO We need to check if this is an incoming call
|
||||||
Timber.tag(loggerTag.value).i("Notification are disabled for this device, ignore push.")
|
Timber.tag(loggerTag.value).i("Notification are disabled for this device, ignore push.")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultNotificationDrawerManager.onNotifiableEventReceived(notifiableEvent)
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Timber.tag(loggerTag.value).e(e, "## handleInternal() failed")
|
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.Body
|
||||||
import retrofit2.http.POST
|
import retrofit2.http.POST
|
||||||
|
|
||||||
internal interface PushGatewayAPI {
|
interface PushGatewayAPI {
|
||||||
/**
|
/**
|
||||||
* Ask the Push Gateway to send a push to the current device.
|
* 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
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal data class PushGatewayDevice(
|
data class PushGatewayDevice(
|
||||||
/**
|
/**
|
||||||
* Required. The app_id given when the pusher was created.
|
* Required. The app_id given when the pusher was created.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal data class PushGatewayNotification(
|
data class PushGatewayNotification(
|
||||||
@SerialName("event_id")
|
@SerialName("event_id")
|
||||||
val eventId: String,
|
val eventId: String,
|
||||||
@SerialName("room_id")
|
@SerialName("room_id")
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal data class PushGatewayNotifyBody(
|
data class PushGatewayNotifyBody(
|
||||||
/**
|
/**
|
||||||
* Required. Information about the push notification
|
* Required. Information about the push notification
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -15,15 +15,14 @@
|
||||||
*/
|
*/
|
||||||
package io.element.android.libraries.push.impl.pushgateway
|
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.EventId
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
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 io.element.android.libraries.push.api.gateway.PushGatewayFailure
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class PushGatewayNotifyRequest @Inject constructor(
|
interface PushGatewayNotifyRequest {
|
||||||
private val retrofitFactory: RetrofitFactory,
|
|
||||||
) {
|
|
||||||
data class Params(
|
data class Params(
|
||||||
val url: String,
|
val url: String,
|
||||||
val appId: String,
|
val appId: String,
|
||||||
|
|
@ -32,13 +31,18 @@ class PushGatewayNotifyRequest @Inject constructor(
|
||||||
val roomId: RoomId,
|
val roomId: RoomId,
|
||||||
)
|
)
|
||||||
|
|
||||||
suspend fun execute(params: Params) {
|
suspend fun execute(params: Params)
|
||||||
val sygnalApi = retrofitFactory.create(
|
}
|
||||||
|
|
||||||
|
@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)
|
params.url.substringBefore(PushGatewayConfig.URI_PUSH_GATEWAY_PREFIX_PATH)
|
||||||
)
|
)
|
||||||
.create(PushGatewayAPI::class.java)
|
val response = pushGatewayApi.notify(
|
||||||
|
|
||||||
val response = sygnalApi.notify(
|
|
||||||
PushGatewayNotifyBody(
|
PushGatewayNotifyBody(
|
||||||
PushGatewayNotification(
|
PushGatewayNotification(
|
||||||
eventId = params.eventId.value,
|
eventId = params.eventId.value,
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal data class PushGatewayNotifyResponse(
|
data class PushGatewayNotifyResponse(
|
||||||
@SerialName("rejected")
|
@SerialName("rejected")
|
||||||
val rejectedPushKeys: List<String>
|
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
|
import org.robolectric.annotation.Config
|
||||||
|
|
||||||
@RunWith(RobolectricTestRunner::class)
|
@RunWith(RobolectricTestRunner::class)
|
||||||
class NotifiableEventResolverTest {
|
class DefaultNotifiableEventResolverTest {
|
||||||
@Test
|
@Test
|
||||||
fun `resolve event no session`() = runTest {
|
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)
|
val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID)
|
||||||
assertThat(result).isNull()
|
assertThat(result).isNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `resolve event failure`() = runTest {
|
fun `resolve event failure`() = runTest {
|
||||||
val sut = createNotifiableEventResolver(
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
notificationResult = Result.failure(AN_EXCEPTION)
|
notificationResult = Result.failure(AN_EXCEPTION)
|
||||||
)
|
)
|
||||||
val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID)
|
val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID)
|
||||||
|
|
@ -78,7 +78,7 @@ class NotifiableEventResolverTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `resolve event null`() = runTest {
|
fun `resolve event null`() = runTest {
|
||||||
val sut = createNotifiableEventResolver(
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
notificationResult = Result.success(null)
|
notificationResult = Result.success(null)
|
||||||
)
|
)
|
||||||
val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID)
|
val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID)
|
||||||
|
|
@ -87,7 +87,7 @@ class NotifiableEventResolverTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `resolve event message text`() = runTest {
|
fun `resolve event message text`() = runTest {
|
||||||
val sut = createNotifiableEventResolver(
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
notificationResult = Result.success(
|
notificationResult = Result.success(
|
||||||
createNotificationData(
|
createNotificationData(
|
||||||
content = NotificationContent.MessageLike.RoomMessage(
|
content = NotificationContent.MessageLike.RoomMessage(
|
||||||
|
|
@ -105,7 +105,7 @@ class NotifiableEventResolverTest {
|
||||||
@Test
|
@Test
|
||||||
@Config(qualifiers = "en")
|
@Config(qualifiers = "en")
|
||||||
fun `resolve event message with mention`() = runTest {
|
fun `resolve event message with mention`() = runTest {
|
||||||
val sut = createNotifiableEventResolver(
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
notificationResult = Result.success(
|
notificationResult = Result.success(
|
||||||
createNotificationData(
|
createNotificationData(
|
||||||
content = NotificationContent.MessageLike.RoomMessage(
|
content = NotificationContent.MessageLike.RoomMessage(
|
||||||
|
|
@ -123,7 +123,7 @@ class NotifiableEventResolverTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `resolve HTML formatted event message text takes plain text version`() = runTest {
|
fun `resolve HTML formatted event message text takes plain text version`() = runTest {
|
||||||
val sut = createNotifiableEventResolver(
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
notificationResult = Result.success(
|
notificationResult = Result.success(
|
||||||
createNotificationData(
|
createNotificationData(
|
||||||
content = NotificationContent.MessageLike.RoomMessage(
|
content = NotificationContent.MessageLike.RoomMessage(
|
||||||
|
|
@ -146,7 +146,7 @@ class NotifiableEventResolverTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `resolve incorrectly formatted event message text uses fallback`() = runTest {
|
fun `resolve incorrectly formatted event message text uses fallback`() = runTest {
|
||||||
val sut = createNotifiableEventResolver(
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
notificationResult = Result.success(
|
notificationResult = Result.success(
|
||||||
createNotificationData(
|
createNotificationData(
|
||||||
content = NotificationContent.MessageLike.RoomMessage(
|
content = NotificationContent.MessageLike.RoomMessage(
|
||||||
|
|
@ -169,7 +169,7 @@ class NotifiableEventResolverTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `resolve event message audio`() = runTest {
|
fun `resolve event message audio`() = runTest {
|
||||||
val sut = createNotifiableEventResolver(
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
notificationResult = Result.success(
|
notificationResult = Result.success(
|
||||||
createNotificationData(
|
createNotificationData(
|
||||||
content = NotificationContent.MessageLike.RoomMessage(
|
content = NotificationContent.MessageLike.RoomMessage(
|
||||||
|
|
@ -186,7 +186,7 @@ class NotifiableEventResolverTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `resolve event message video`() = runTest {
|
fun `resolve event message video`() = runTest {
|
||||||
val sut = createNotifiableEventResolver(
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
notificationResult = Result.success(
|
notificationResult = Result.success(
|
||||||
createNotificationData(
|
createNotificationData(
|
||||||
content = NotificationContent.MessageLike.RoomMessage(
|
content = NotificationContent.MessageLike.RoomMessage(
|
||||||
|
|
@ -203,7 +203,7 @@ class NotifiableEventResolverTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `resolve event message voice`() = runTest {
|
fun `resolve event message voice`() = runTest {
|
||||||
val sut = createNotifiableEventResolver(
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
notificationResult = Result.success(
|
notificationResult = Result.success(
|
||||||
createNotificationData(
|
createNotificationData(
|
||||||
content = NotificationContent.MessageLike.RoomMessage(
|
content = NotificationContent.MessageLike.RoomMessage(
|
||||||
|
|
@ -220,7 +220,7 @@ class NotifiableEventResolverTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `resolve event message image`() = runTest {
|
fun `resolve event message image`() = runTest {
|
||||||
val sut = createNotifiableEventResolver(
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
notificationResult = Result.success(
|
notificationResult = Result.success(
|
||||||
createNotificationData(
|
createNotificationData(
|
||||||
content = NotificationContent.MessageLike.RoomMessage(
|
content = NotificationContent.MessageLike.RoomMessage(
|
||||||
|
|
@ -237,7 +237,7 @@ class NotifiableEventResolverTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `resolve event message sticker`() = runTest {
|
fun `resolve event message sticker`() = runTest {
|
||||||
val sut = createNotifiableEventResolver(
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
notificationResult = Result.success(
|
notificationResult = Result.success(
|
||||||
createNotificationData(
|
createNotificationData(
|
||||||
content = NotificationContent.MessageLike.RoomMessage(
|
content = NotificationContent.MessageLike.RoomMessage(
|
||||||
|
|
@ -254,7 +254,7 @@ class NotifiableEventResolverTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `resolve event message file`() = runTest {
|
fun `resolve event message file`() = runTest {
|
||||||
val sut = createNotifiableEventResolver(
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
notificationResult = Result.success(
|
notificationResult = Result.success(
|
||||||
createNotificationData(
|
createNotificationData(
|
||||||
content = NotificationContent.MessageLike.RoomMessage(
|
content = NotificationContent.MessageLike.RoomMessage(
|
||||||
|
|
@ -271,7 +271,7 @@ class NotifiableEventResolverTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `resolve event message location`() = runTest {
|
fun `resolve event message location`() = runTest {
|
||||||
val sut = createNotifiableEventResolver(
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
notificationResult = Result.success(
|
notificationResult = Result.success(
|
||||||
createNotificationData(
|
createNotificationData(
|
||||||
content = NotificationContent.MessageLike.RoomMessage(
|
content = NotificationContent.MessageLike.RoomMessage(
|
||||||
|
|
@ -288,7 +288,7 @@ class NotifiableEventResolverTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `resolve event message notice`() = runTest {
|
fun `resolve event message notice`() = runTest {
|
||||||
val sut = createNotifiableEventResolver(
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
notificationResult = Result.success(
|
notificationResult = Result.success(
|
||||||
createNotificationData(
|
createNotificationData(
|
||||||
content = NotificationContent.MessageLike.RoomMessage(
|
content = NotificationContent.MessageLike.RoomMessage(
|
||||||
|
|
@ -305,7 +305,7 @@ class NotifiableEventResolverTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `resolve event message emote`() = runTest {
|
fun `resolve event message emote`() = runTest {
|
||||||
val sut = createNotifiableEventResolver(
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
notificationResult = Result.success(
|
notificationResult = Result.success(
|
||||||
createNotificationData(
|
createNotificationData(
|
||||||
content = NotificationContent.MessageLike.RoomMessage(
|
content = NotificationContent.MessageLike.RoomMessage(
|
||||||
|
|
@ -322,7 +322,7 @@ class NotifiableEventResolverTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `resolve poll`() = runTest {
|
fun `resolve poll`() = runTest {
|
||||||
val sut = createNotifiableEventResolver(
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
notificationResult = Result.success(
|
notificationResult = Result.success(
|
||||||
createNotificationData(
|
createNotificationData(
|
||||||
content = NotificationContent.MessageLike.Poll(
|
content = NotificationContent.MessageLike.Poll(
|
||||||
|
|
@ -339,7 +339,7 @@ class NotifiableEventResolverTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `resolve RoomMemberContent invite room`() = runTest {
|
fun `resolve RoomMemberContent invite room`() = runTest {
|
||||||
val sut = createNotifiableEventResolver(
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
notificationResult = Result.success(
|
notificationResult = Result.success(
|
||||||
createNotificationData(
|
createNotificationData(
|
||||||
content = NotificationContent.StateEvent.RoomMemberContent(
|
content = NotificationContent.StateEvent.RoomMemberContent(
|
||||||
|
|
@ -372,7 +372,7 @@ class NotifiableEventResolverTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `resolve RoomMemberContent invite direct`() = runTest {
|
fun `resolve RoomMemberContent invite direct`() = runTest {
|
||||||
val sut = createNotifiableEventResolver(
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
notificationResult = Result.success(
|
notificationResult = Result.success(
|
||||||
createNotificationData(
|
createNotificationData(
|
||||||
content = NotificationContent.StateEvent.RoomMemberContent(
|
content = NotificationContent.StateEvent.RoomMemberContent(
|
||||||
|
|
@ -405,7 +405,7 @@ class NotifiableEventResolverTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `resolve RoomMemberContent other`() = runTest {
|
fun `resolve RoomMemberContent other`() = runTest {
|
||||||
val sut = createNotifiableEventResolver(
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
notificationResult = Result.success(
|
notificationResult = Result.success(
|
||||||
createNotificationData(
|
createNotificationData(
|
||||||
content = NotificationContent.StateEvent.RoomMemberContent(
|
content = NotificationContent.StateEvent.RoomMemberContent(
|
||||||
|
|
@ -421,7 +421,7 @@ class NotifiableEventResolverTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `resolve RoomEncrypted`() = runTest {
|
fun `resolve RoomEncrypted`() = runTest {
|
||||||
val sut = createNotifiableEventResolver(
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
notificationResult = Result.success(
|
notificationResult = Result.success(
|
||||||
createNotificationData(
|
createNotificationData(
|
||||||
content = NotificationContent.MessageLike.RoomEncrypted
|
content = NotificationContent.MessageLike.RoomEncrypted
|
||||||
|
|
@ -445,7 +445,7 @@ class NotifiableEventResolverTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `resolve CallInvite`() = runTest {
|
fun `resolve CallInvite`() = runTest {
|
||||||
val sut = createNotifiableEventResolver(
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
notificationResult = Result.success(
|
notificationResult = Result.success(
|
||||||
createNotificationData(
|
createNotificationData(
|
||||||
content = NotificationContent.MessageLike.CallInvite(A_USER_ID_2)
|
content = NotificationContent.MessageLike.CallInvite(A_USER_ID_2)
|
||||||
|
|
@ -517,7 +517,7 @@ class NotifiableEventResolverTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun testNull(content: NotificationContent) = runTest {
|
private fun testNull(content: NotificationContent) = runTest {
|
||||||
val sut = createNotifiableEventResolver(
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
notificationResult = Result.success(
|
notificationResult = Result.success(
|
||||||
createNotificationData(
|
createNotificationData(
|
||||||
content = content
|
content = content
|
||||||
|
|
@ -528,10 +528,10 @@ class NotifiableEventResolverTest {
|
||||||
assertThat(result).isNull()
|
assertThat(result).isNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createNotifiableEventResolver(
|
private fun createDefaultNotifiableEventResolver(
|
||||||
notificationService: FakeNotificationService? = FakeNotificationService(),
|
notificationService: FakeNotificationService? = FakeNotificationService(),
|
||||||
notificationResult: Result<NotificationData?> = Result.success(null),
|
notificationResult: Result<NotificationData?> = Result.success(null),
|
||||||
): NotifiableEventResolver {
|
): DefaultNotifiableEventResolver {
|
||||||
val context = RuntimeEnvironment.getApplication() as Context
|
val context = RuntimeEnvironment.getApplication() as Context
|
||||||
notificationService?.givenGetNotificationResult(notificationResult)
|
notificationService?.givenGetNotificationResult(notificationResult)
|
||||||
val matrixClientProvider = FakeMatrixClientProvider(getClient = {
|
val matrixClientProvider = FakeMatrixClientProvider(getClient = {
|
||||||
|
|
@ -544,7 +544,7 @@ class NotifiableEventResolverTest {
|
||||||
val notificationMediaRepoFactory = NotificationMediaRepo.Factory {
|
val notificationMediaRepoFactory = NotificationMediaRepo.Factory {
|
||||||
FakeNotificationMediaRepo()
|
FakeNotificationMediaRepo()
|
||||||
}
|
}
|
||||||
return NotifiableEventResolver(
|
return DefaultNotifiableEventResolver(
|
||||||
stringProvider = AndroidStringProvider(context.resources),
|
stringProvider = AndroidStringProvider(context.resources),
|
||||||
clock = FakeSystemClock(),
|
clock = FakeSystemClock(),
|
||||||
matrixClientProvider = matrixClientProvider,
|
matrixClientProvider = matrixClientProvider,
|
||||||
|
|
@ -59,7 +59,6 @@ class DefaultNotificationDrawerManagerTest {
|
||||||
fun `cover all APIs`() = runTest {
|
fun `cover all APIs`() = runTest {
|
||||||
// For now just call all the API. Later, add more valuable tests.
|
// For now just call all the API. Later, add more valuable tests.
|
||||||
val defaultNotificationDrawerManager = createDefaultNotificationDrawerManager()
|
val defaultNotificationDrawerManager = createDefaultNotificationDrawerManager()
|
||||||
defaultNotificationDrawerManager.notificationStyleChanged()
|
|
||||||
defaultNotificationDrawerManager.clearAllMessagesEvents(A_SESSION_ID, doRender = true)
|
defaultNotificationDrawerManager.clearAllMessagesEvents(A_SESSION_ID, doRender = true)
|
||||||
defaultNotificationDrawerManager.clearAllMessagesEvents(A_SESSION_ID, doRender = false)
|
defaultNotificationDrawerManager.clearAllMessagesEvents(A_SESSION_ID, doRender = false)
|
||||||
defaultNotificationDrawerManager.clearEvent(A_SESSION_ID, AN_EVENT_ID, doRender = true)
|
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)
|
Result.success(Unit)
|
||||||
},
|
},
|
||||||
) : PushService {
|
) : PushService {
|
||||||
override fun notificationStyleChanged() {
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getCurrentPushProvider(): PushProvider? {
|
override suspend fun getCurrentPushProvider(): PushProvider? {
|
||||||
return availablePushProviders.firstOrNull()
|
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.junit)
|
||||||
testImplementation(libs.test.truth)
|
testImplementation(libs.test.truth)
|
||||||
testImplementation(libs.test.turbine)
|
testImplementation(libs.test.turbine)
|
||||||
|
testImplementation(libs.test.robolectric)
|
||||||
testImplementation(projects.libraries.matrix.test)
|
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.tests.testutils)
|
||||||
testImplementation(projects.services.toolbox.test)
|
testImplementation(projects.services.toolbox.test)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,10 @@
|
||||||
|
|
||||||
package io.element.android.libraries.pushproviders.firebase
|
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.extensions.flatMap
|
||||||
import io.element.android.libraries.core.log.logger.LoggerTag
|
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.auth.MatrixAuthenticationService
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
import io.element.android.libraries.pushproviders.api.PusherSubscriber
|
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.
|
* 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 pusherSubscriber: PusherSubscriber,
|
||||||
private val sessionStore: SessionStore,
|
private val sessionStore: SessionStore,
|
||||||
private val userPushStoreFactory: UserPushStoreFactory,
|
private val userPushStoreFactory: UserPushStoreFactory,
|
||||||
private val matrixAuthenticationService: MatrixAuthenticationService,
|
private val matrixAuthenticationService: MatrixAuthenticationService,
|
||||||
private val firebaseStore: FirebaseStore,
|
private val firebaseStore: FirebaseStore,
|
||||||
) {
|
) : FirebaseNewTokenHandler {
|
||||||
suspend fun handle(firebaseToken: String) {
|
override suspend fun handle(firebaseToken: String) {
|
||||||
firebaseStore.storeFcmToken(firebaseToken)
|
firebaseStore.storeFcmToken(firebaseToken)
|
||||||
// Register the pusher for all the sessions
|
// Register the pusher for all the sessions
|
||||||
sessionStore.getAllSessions().toUserList()
|
sessionStore.getAllSessions().toUserList()
|
||||||
|
|
@ -53,14 +60,15 @@ class FirebaseNewTokenHandler @Inject constructor(
|
||||||
Timber.tag(loggerTag.value).e(it, "Failed to restore session $sessionId")
|
Timber.tag(loggerTag.value).e(it, "Failed to restore session $sessionId")
|
||||||
}
|
}
|
||||||
.flatMap { client ->
|
.flatMap { client ->
|
||||||
pusherSubscriber.registerPusher(
|
pusherSubscriber
|
||||||
matrixClient = client,
|
.registerPusher(
|
||||||
pushKey = firebaseToken,
|
matrixClient = client,
|
||||||
gateway = FirebaseConfig.PUSHER_HTTP_URL,
|
pushKey = firebaseToken,
|
||||||
)
|
gateway = FirebaseConfig.PUSHER_HTTP_URL,
|
||||||
}
|
)
|
||||||
.onFailure {
|
.onFailure {
|
||||||
Timber.tag(loggerTag.value).e(it, "Failed to register pusher for session $sessionId")
|
Timber.tag(loggerTag.value).e(it, "Failed to register pusher for session $sessionId")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Timber.tag(loggerTag.value).d("This session is not using Firebase pusher")
|
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.core.log.logger.LoggerTag
|
||||||
import io.element.android.libraries.pushproviders.api.PushHandler
|
import io.element.android.libraries.pushproviders.api.PushHandler
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.SupervisorJob
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
@ -33,8 +32,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
|
||||||
@Inject lateinit var firebaseNewTokenHandler: FirebaseNewTokenHandler
|
@Inject lateinit var firebaseNewTokenHandler: FirebaseNewTokenHandler
|
||||||
@Inject lateinit var pushParser: FirebasePushParser
|
@Inject lateinit var pushParser: FirebasePushParser
|
||||||
@Inject lateinit var pushHandler: PushHandler
|
@Inject lateinit var pushHandler: PushHandler
|
||||||
|
@Inject lateinit var coroutineScope: CoroutineScope
|
||||||
private val coroutineScope = CoroutineScope(SupervisorJob())
|
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.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 app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
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.NotificationTroubleshootTestState
|
||||||
|
import io.element.android.libraries.troubleshoot.api.test.TestFilterData
|
||||||
import io.element.android.services.toolbox.test.strings.FakeStringProvider
|
import io.element.android.services.toolbox.test.strings.FakeStringProvider
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
|
|
@ -29,11 +31,7 @@ class FirebaseAvailabilityTestTest {
|
||||||
@Test
|
@Test
|
||||||
fun `test FirebaseAvailabilityTest success`() = runTest {
|
fun `test FirebaseAvailabilityTest success`() = runTest {
|
||||||
val sut = FirebaseAvailabilityTest(
|
val sut = FirebaseAvailabilityTest(
|
||||||
isPlayServiceAvailable = object : IsPlayServiceAvailable {
|
isPlayServiceAvailable = FakeIsPlayServiceAvailable(true),
|
||||||
override fun isAvailable(): Boolean {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
stringProvider = FakeStringProvider(),
|
stringProvider = FakeStringProvider(),
|
||||||
)
|
)
|
||||||
launch {
|
launch {
|
||||||
|
|
@ -50,11 +48,7 @@ class FirebaseAvailabilityTestTest {
|
||||||
@Test
|
@Test
|
||||||
fun `test FirebaseAvailabilityTest failure`() = runTest {
|
fun `test FirebaseAvailabilityTest failure`() = runTest {
|
||||||
val sut = FirebaseAvailabilityTest(
|
val sut = FirebaseAvailabilityTest(
|
||||||
isPlayServiceAvailable = object : IsPlayServiceAvailable {
|
isPlayServiceAvailable = FakeIsPlayServiceAvailable(false),
|
||||||
override fun isAvailable(): Boolean {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
stringProvider = FakeStringProvider(),
|
stringProvider = FakeStringProvider(),
|
||||||
)
|
)
|
||||||
launch {
|
launch {
|
||||||
|
|
@ -67,4 +61,14 @@ class FirebaseAvailabilityTestTest {
|
||||||
assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(false))
|
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 app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.libraries.pushproviders.firebase.FakeFirebaseTroubleshooter
|
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.pushproviders.firebase.InMemoryFirebaseStore
|
||||||
import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState
|
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 io.element.android.services.toolbox.test.strings.FakeStringProvider
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.test.runTest
|
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 {
|
companion object {
|
||||||
private const val FAKE_TOKEN = "abcdefghijk"
|
private const val FAKE_TOKEN = "abcdefghijk"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,4 +24,5 @@ android {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(projects.libraries.matrix.api)
|
implementation(projects.libraries.matrix.api)
|
||||||
implementation(projects.libraries.pushproviders.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.CurrentUserPushConfig
|
||||||
import io.element.android.libraries.pushproviders.api.Distributor
|
import io.element.android.libraries.pushproviders.api.Distributor
|
||||||
import io.element.android.libraries.pushproviders.api.PushProvider
|
import io.element.android.libraries.pushproviders.api.PushProvider
|
||||||
|
import io.element.android.tests.testutils.lambda.lambdaError
|
||||||
|
|
||||||
class FakePushProvider(
|
class FakePushProvider(
|
||||||
override val index: Int = 0,
|
override val index: Int = 0,
|
||||||
override val name: String = "aFakePushProvider",
|
override val name: String = "aFakePushProvider",
|
||||||
private val isAvailable: Boolean = true,
|
private val isAvailable: Boolean = true,
|
||||||
private val distributors: List<Distributor> = listOf(Distributor("aDistributorValue", "aDistributorName")),
|
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 {
|
) : PushProvider {
|
||||||
override fun isAvailable(): Boolean = isAvailable
|
override fun isAvailable(): Boolean = isAvailable
|
||||||
|
|
||||||
override fun getDistributors(): List<Distributor> = distributors
|
override fun getDistributors(): List<Distributor> = distributors
|
||||||
|
|
||||||
override suspend fun registerWith(matrixClient: MatrixClient, distributor: Distributor): Result<Unit> {
|
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? {
|
override suspend fun getCurrentDistributor(matrixClient: MatrixClient): Distributor? {
|
||||||
|
|
@ -40,10 +44,10 @@ class FakePushProvider(
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun unregister(matrixClient: MatrixClient): Result<Unit> {
|
override suspend fun unregister(matrixClient: MatrixClient): Result<Unit> {
|
||||||
return Result.success(Unit)
|
return unregisterWithResult(matrixClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getCurrentUserPushConfig(): CurrentUserPushConfig? {
|
override suspend fun getCurrentUserPushConfig(): CurrentUserPushConfig? {
|
||||||
return null
|
return currentUserPushConfig
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,9 +55,13 @@ dependencies {
|
||||||
|
|
||||||
testImplementation(libs.coroutines.test)
|
testImplementation(libs.coroutines.test)
|
||||||
testImplementation(libs.test.junit)
|
testImplementation(libs.test.junit)
|
||||||
|
testImplementation(libs.test.robolectric)
|
||||||
testImplementation(libs.test.truth)
|
testImplementation(libs.test.truth)
|
||||||
testImplementation(libs.test.turbine)
|
testImplementation(libs.test.turbine)
|
||||||
testImplementation(projects.libraries.matrix.test)
|
testImplementation(projects.libraries.matrix.test)
|
||||||
|
testImplementation(projects.libraries.push.test)
|
||||||
|
testImplementation(projects.libraries.pushstore.test)
|
||||||
testImplementation(projects.tests.testutils)
|
testImplementation(projects.tests.testutils)
|
||||||
testImplementation(projects.services.toolbox.test)
|
testImplementation(projects.services.toolbox.test)
|
||||||
|
testImplementation(projects.services.appnavstate.test)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,43 +17,41 @@
|
||||||
package io.element.android.libraries.pushproviders.unifiedpush
|
package io.element.android.libraries.pushproviders.unifiedpush
|
||||||
|
|
||||||
import android.content.Context
|
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.di.ApplicationContext
|
||||||
import io.element.android.libraries.pushproviders.api.Distributor
|
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.EndpointRegistrationHandler
|
||||||
import kotlinx.coroutines.CompletableDeferred
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.flow.filter
|
import kotlinx.coroutines.flow.filter
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.withTimeout
|
import kotlinx.coroutines.withTimeout
|
||||||
import org.unifiedpush.android.connector.UnifiedPush
|
import org.unifiedpush.android.connector.UnifiedPush
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.time.Duration.Companion.seconds
|
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,
|
@ApplicationContext private val context: Context,
|
||||||
private val endpointRegistrationHandler: EndpointRegistrationHandler,
|
private val endpointRegistrationHandler: EndpointRegistrationHandler,
|
||||||
private val coroutineScope: CoroutineScope,
|
) : RegisterUnifiedPushUseCase {
|
||||||
) {
|
override suspend fun execute(distributor: Distributor, clientSecret: String): Result<Unit> {
|
||||||
suspend fun execute(distributor: Distributor, clientSecret: String): Result<Unit> {
|
|
||||||
UnifiedPush.saveDistributor(context, distributor.value)
|
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
|
// This will trigger the callback
|
||||||
// VectorUnifiedPushMessagingReceiver.onNewEndpoint
|
// VectorUnifiedPushMessagingReceiver.onNewEndpoint
|
||||||
UnifiedPush.registerApp(context = context, instance = clientSecret)
|
UnifiedPush.registerApp(context = context, instance = clientSecret)
|
||||||
// Wait for VectorUnifiedPushMessagingReceiver.onNewEndpoint to proceed
|
// Wait for VectorUnifiedPushMessagingReceiver.onNewEndpoint to proceed
|
||||||
return withTimeout(30.seconds) {
|
return runCatching {
|
||||||
completable.await()
|
withTimeout(30.seconds) {
|
||||||
}
|
val result = endpointRegistrationHandler.state
|
||||||
.onFailure {
|
.filter { it.clientSecret == clientSecret }
|
||||||
job.cancel()
|
.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
|
package io.element.android.libraries.pushproviders.unifiedpush
|
||||||
|
|
||||||
|
import com.squareup.anvil.annotations.ContributesBinding
|
||||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||||
import io.element.android.libraries.network.RetrofitFactory
|
import io.element.android.libraries.di.AppScope
|
||||||
import io.element.android.libraries.pushproviders.unifiedpush.network.UnifiedPushApi
|
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class UnifiedPushGatewayResolver @Inject constructor(
|
interface UnifiedPushGatewayResolver {
|
||||||
private val retrofitFactory: RetrofitFactory,
|
suspend fun getGateway(endpoint: String): String
|
||||||
|
}
|
||||||
|
|
||||||
|
@ContributesBinding(AppScope::class)
|
||||||
|
class DefaultUnifiedPushGatewayResolver @Inject constructor(
|
||||||
|
private val unifiedPushApiFactory: UnifiedPushApiFactory,
|
||||||
private val coroutineDispatchers: CoroutineDispatchers,
|
private val coroutineDispatchers: CoroutineDispatchers,
|
||||||
) {
|
) : UnifiedPushGatewayResolver {
|
||||||
suspend fun getGateway(endpoint: String): String? {
|
override suspend fun getGateway(endpoint: String): String {
|
||||||
val gateway = UnifiedPushConfig.DEFAULT_PUSH_GATEWAY_HTTP_URL
|
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 {
|
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) {
|
return withContext(coroutineDispatchers.io) {
|
||||||
val api = retrofitFactory.create(customBase)
|
val api = unifiedPushApiFactory.create(customBase)
|
||||||
.create(UnifiedPushApi::class.java)
|
|
||||||
try {
|
try {
|
||||||
val discoveryResponse = api.discover()
|
val discoveryResponse = api.discover()
|
||||||
if (discoveryResponse.unifiedpush.gateway == "matrix") {
|
if (discoveryResponse.unifiedpush.gateway == "matrix") {
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,10 @@
|
||||||
|
|
||||||
package io.element.android.libraries.pushproviders.unifiedpush
|
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.extensions.flatMap
|
||||||
import io.element.android.libraries.core.log.logger.LoggerTag
|
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.auth.MatrixAuthenticationService
|
||||||
import io.element.android.libraries.pushproviders.api.PusherSubscriber
|
import io.element.android.libraries.pushproviders.api.PusherSubscriber
|
||||||
import io.element.android.libraries.pushstore.api.UserPushStoreFactory
|
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 timber.log.Timber
|
||||||
import javax.inject.Inject
|
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.
|
* 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 pusherSubscriber: PusherSubscriber,
|
||||||
private val userPushStoreFactory: UserPushStoreFactory,
|
private val userPushStoreFactory: UserPushStoreFactory,
|
||||||
private val pushClientSecret: PushClientSecret,
|
private val pushClientSecret: PushClientSecret,
|
||||||
private val matrixAuthenticationService: MatrixAuthenticationService,
|
private val matrixAuthenticationService: MatrixAuthenticationService,
|
||||||
) {
|
) : UnifiedPushNewGatewayHandler {
|
||||||
suspend fun handle(endpoint: String, pushGateway: String, clientSecret: String): Result<Unit> {
|
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.
|
// Register the pusher for the session with this client secret, if is it using UnifiedPush.
|
||||||
val userId = pushClientSecret.getUserIdFromSecret(clientSecret) ?: return Result.failure<Unit>(
|
val userId = pushClientSecret.getUserIdFromSecret(clientSecret) ?: return Result.failure<Unit>(
|
||||||
IllegalStateException("Unable to retrieve session")
|
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.core.data.tryOrNull
|
||||||
import io.element.android.libraries.pushproviders.api.PushData
|
import io.element.android.libraries.pushproviders.api.PushData
|
||||||
import kotlinx.serialization.decodeFromString
|
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,32 +19,44 @@ package io.element.android.libraries.pushproviders.unifiedpush
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import androidx.core.content.edit
|
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.ApplicationContext
|
||||||
import io.element.android.libraries.di.DefaultPreferences
|
import io.element.android.libraries.di.DefaultPreferences
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import javax.inject.Inject
|
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,
|
@ApplicationContext val context: Context,
|
||||||
@DefaultPreferences private val defaultPrefs: SharedPreferences,
|
@DefaultPreferences private val defaultPrefs: SharedPreferences,
|
||||||
) {
|
) : UnifiedPushStore {
|
||||||
/**
|
/**
|
||||||
* Retrieves the UnifiedPush Endpoint.
|
* Retrieves the UnifiedPush Endpoint.
|
||||||
*
|
*
|
||||||
* @param clientSecret the client secret, to identify the session
|
* @param clientSecret the client secret, to identify the session
|
||||||
* @return the UnifiedPush Endpoint or null if not received
|
* @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)
|
return defaultPrefs.getString(PREFS_ENDPOINT_OR_TOKEN + clientSecret, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Store UnifiedPush Endpoint to the SharedPrefs.
|
* Store UnifiedPush Endpoint to the SharedPrefs.
|
||||||
*
|
*
|
||||||
* @param endpoint the endpoint to store
|
|
||||||
* @param clientSecret the client secret, to identify the session
|
* @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 {
|
defaultPrefs.edit {
|
||||||
putString(PREFS_ENDPOINT_OR_TOKEN + clientSecret, endpoint)
|
putString(PREFS_ENDPOINT_OR_TOKEN + clientSecret, endpoint)
|
||||||
}
|
}
|
||||||
|
|
@ -56,27 +68,27 @@ class UnifiedPushStore @Inject constructor(
|
||||||
* @param clientSecret the client secret, to identify the session
|
* @param clientSecret the client secret, to identify the session
|
||||||
* @return the Push Gateway or null if not defined
|
* @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)
|
return defaultPrefs.getString(PREFS_PUSH_GATEWAY + clientSecret, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Store Push Gateway to the SharedPrefs.
|
* Store Push Gateway to the SharedPrefs.
|
||||||
*
|
*
|
||||||
* @param gateway the push gateway to store
|
|
||||||
* @param clientSecret the client secret, to identify the session
|
* @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 {
|
defaultPrefs.edit {
|
||||||
putString(PREFS_PUSH_GATEWAY + clientSecret, gateway)
|
putString(PREFS_PUSH_GATEWAY + clientSecret, gateway)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getDistributorValue(userId: UserId): String? {
|
override fun getDistributorValue(userId: UserId): String? {
|
||||||
return defaultPrefs.getString(PREFS_DISTRIBUTOR + userId, null)
|
return defaultPrefs.getString(PREFS_DISTRIBUTOR + userId, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setDistributorValue(userId: UserId, value: String) {
|
override fun setDistributorValue(userId: UserId, value: String) {
|
||||||
defaultPrefs.edit {
|
defaultPrefs.edit {
|
||||||
putString(PREFS_DISTRIBUTOR + userId, value)
|
putString(PREFS_DISTRIBUTOR + userId, value)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,18 +17,25 @@
|
||||||
package io.element.android.libraries.pushproviders.unifiedpush
|
package io.element.android.libraries.pushproviders.unifiedpush
|
||||||
|
|
||||||
import android.content.Context
|
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.di.ApplicationContext
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
import io.element.android.libraries.pushproviders.api.PusherSubscriber
|
import io.element.android.libraries.pushproviders.api.PusherSubscriber
|
||||||
import org.unifiedpush.android.connector.UnifiedPush
|
import org.unifiedpush.android.connector.UnifiedPush
|
||||||
import javax.inject.Inject
|
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,
|
@ApplicationContext private val context: Context,
|
||||||
private val unifiedPushStore: UnifiedPushStore,
|
private val unifiedPushStore: UnifiedPushStore,
|
||||||
private val pusherSubscriber: PusherSubscriber,
|
private val pusherSubscriber: PusherSubscriber,
|
||||||
) {
|
) : UnregisterUnifiedPushUseCase {
|
||||||
suspend fun execute(matrixClient: MatrixClient, clientSecret: String): Result<Unit> {
|
override suspend fun execute(matrixClient: MatrixClient, clientSecret: String): Result<Unit> {
|
||||||
val endpoint = unifiedPushStore.getEndpoint(clientSecret)
|
val endpoint = unifiedPushStore.getEndpoint(clientSecret)
|
||||||
val gateway = unifiedPushStore.getPushGateway(clientSecret)
|
val gateway = unifiedPushStore.getPushGateway(clientSecret)
|
||||||
if (endpoint == null || gateway == null) {
|
if (endpoint == null || gateway == null) {
|
||||||
|
|
@ -36,8 +43,8 @@ class UnregisterUnifiedPushUseCase @Inject constructor(
|
||||||
}
|
}
|
||||||
return pusherSubscriber.unregisterPusher(matrixClient, endpoint, gateway)
|
return pusherSubscriber.unregisterPusher(matrixClient, endpoint, gateway)
|
||||||
.onSuccess {
|
.onSuccess {
|
||||||
unifiedPushStore.storeUpEndpoint(null, clientSecret)
|
unifiedPushStore.storeUpEndpoint(clientSecret, null)
|
||||||
unifiedPushStore.storePushGateway(null, clientSecret)
|
unifiedPushStore.storePushGateway(clientSecret, null)
|
||||||
UnifiedPush.unregisterApp(context)
|
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.EndpointRegistrationHandler
|
||||||
import io.element.android.libraries.pushproviders.unifiedpush.registration.RegistrationResult
|
import io.element.android.libraries.pushproviders.unifiedpush.registration.RegistrationResult
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.SupervisorJob
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.unifiedpush.android.connector.MessagingReceiver
|
import org.unifiedpush.android.connector.MessagingReceiver
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
@ -40,8 +39,7 @@ class VectorUnifiedPushMessagingReceiver : MessagingReceiver() {
|
||||||
@Inject lateinit var unifiedPushGatewayResolver: UnifiedPushGatewayResolver
|
@Inject lateinit var unifiedPushGatewayResolver: UnifiedPushGatewayResolver
|
||||||
@Inject lateinit var newGatewayHandler: UnifiedPushNewGatewayHandler
|
@Inject lateinit var newGatewayHandler: UnifiedPushNewGatewayHandler
|
||||||
@Inject lateinit var endpointRegistrationHandler: EndpointRegistrationHandler
|
@Inject lateinit var endpointRegistrationHandler: EndpointRegistrationHandler
|
||||||
|
@Inject lateinit var coroutineScope: CoroutineScope
|
||||||
private val coroutineScope = CoroutineScope(SupervisorJob())
|
|
||||||
|
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
context.applicationContext.bindings<VectorUnifiedPushMessagingReceiverBindings>().inject(this)
|
context.applicationContext.bindings<VectorUnifiedPushMessagingReceiverBindings>().inject(this)
|
||||||
|
|
@ -75,30 +73,20 @@ class VectorUnifiedPushMessagingReceiver : MessagingReceiver() {
|
||||||
Timber.tag(loggerTag.value).i("onNewEndpoint: $endpoint")
|
Timber.tag(loggerTag.value).i("onNewEndpoint: $endpoint")
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
val gateway = unifiedPushGatewayResolver.getGateway(endpoint)
|
val gateway = unifiedPushGatewayResolver.getGateway(endpoint)
|
||||||
unifiedPushStore.storePushGateway(gateway, instance)
|
unifiedPushStore.storePushGateway(instance, gateway)
|
||||||
if (gateway == null) {
|
val result = newGatewayHandler.handle(endpoint, gateway, instance)
|
||||||
Timber.tag(loggerTag.value).w("No gateway found for endpoint $endpoint")
|
.onFailure {
|
||||||
endpointRegistrationHandler.registrationDone(
|
Timber.tag(loggerTag.value).e(it, "Failed to handle new gateway")
|
||||||
RegistrationResult(
|
}
|
||||||
clientSecret = instance,
|
.onSuccess {
|
||||||
result = Result.failure(IllegalStateException("No gateway found for endpoint $endpoint")),
|
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()
|
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 {
|
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\"}}"
|
"{\"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 app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.libraries.pushproviders.api.Distributor
|
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.NotificationTroubleshootTestState
|
||||||
|
import io.element.android.libraries.troubleshoot.api.test.TestFilterData
|
||||||
import io.element.android.services.toolbox.test.strings.FakeStringProvider
|
import io.element.android.services.toolbox.test.strings.FakeStringProvider
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
|
|
@ -81,4 +83,15 @@ class UnifiedPushTestTest {
|
||||||
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Success)
|
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(libs.coroutines.test)
|
||||||
testImplementation(projects.libraries.matrix.test)
|
testImplementation(projects.libraries.matrix.test)
|
||||||
testImplementation(projects.services.appnavstate.test)
|
testImplementation(projects.services.appnavstate.test)
|
||||||
|
testImplementation(projects.libraries.pushstore.test)
|
||||||
testImplementation(projects.libraries.sessionStorage.test)
|
testImplementation(projects.libraries.sessionStorage.test)
|
||||||
|
|
||||||
androidTestImplementation(libs.coroutines.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 com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
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 io.element.android.libraries.sessionstorage.test.observer.NoOpSessionObserver
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
|
||||||
|
|
@ -14,14 +14,15 @@
|
||||||
* limitations under the License.
|
* 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 io.element.android.libraries.pushstore.api.UserPushStore
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
|
||||||
class FakeUserPushStore : UserPushStore {
|
class FakeUserPushStore(
|
||||||
private var pushProviderName: String? = null
|
private var pushProviderName: String? = null
|
||||||
|
) : UserPushStore {
|
||||||
private var currentRegisteredPushKey: String? = null
|
private var currentRegisteredPushKey: String? = null
|
||||||
private val notificationEnabledForDevice = MutableStateFlow(true)
|
private val notificationEnabledForDevice = MutableStateFlow(true)
|
||||||
override suspend fun getPushProviderName(): String? {
|
override suspend fun getPushProviderName(): String? {
|
||||||
|
|
@ -14,14 +14,16 @@
|
||||||
* limitations under the License.
|
* 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.matrix.api.core.SessionId
|
||||||
import io.element.android.libraries.pushstore.api.UserPushStore
|
import io.element.android.libraries.pushstore.api.UserPushStore
|
||||||
import io.element.android.libraries.pushstore.api.UserPushStoreFactory
|
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 {
|
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.
|
* 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.matrix.api.core.SessionId
|
||||||
import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecretStore
|
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,
|
eventId = null,
|
||||||
viaParameters = persistentListOf(),
|
viaParameters = persistentListOf(),
|
||||||
)
|
)
|
||||||
else -> TODO()
|
else -> throw AssertionError("Unexpected value $uriString")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -16,26 +16,28 @@
|
||||||
|
|
||||||
package io.element.android.tests.testutils
|
package io.element.android.tests.testutils
|
||||||
|
|
||||||
|
import io.element.android.tests.testutils.lambda.lambdaError
|
||||||
|
|
||||||
class EnsureNeverCalled : () -> Unit {
|
class EnsureNeverCalled : () -> Unit {
|
||||||
override fun invoke() {
|
override fun invoke() {
|
||||||
throw AssertionError("Should not be called")
|
lambdaError()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class EnsureNeverCalledWithParam<T> : (T) -> Unit {
|
class EnsureNeverCalledWithParam<T> : (T) -> Unit {
|
||||||
override fun invoke(p1: T) {
|
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 {
|
class EnsureNeverCalledWithParamAndResult<T, R> : (T) -> R {
|
||||||
override fun invoke(p1: 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 {
|
class EnsureNeverCalledWithTwoParams<T, U> : (T, U) -> Unit {
|
||||||
override fun invoke(p1: T, p2: U) {
|
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?) {
|
internal fun onInvoke(vararg params: Any?) {
|
||||||
if (assertNoInvocation) {
|
if (assertNoInvocation) {
|
||||||
throw AssertionError("This lambda should never be called.")
|
lambdaError()
|
||||||
}
|
}
|
||||||
parametersSequence.add(params.toList())
|
parametersSequence.add(params.toList())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue