Add test on DefaultFirebaseNewTokenHandler

This commit is contained in:
Benoit Marty
2024-05-22 18:35:02 +02:00
parent 310c309e1e
commit 9762962586
5 changed files with 247 additions and 10 deletions

View File

@@ -31,7 +31,9 @@ import kotlinx.coroutines.flow.flowOf
val A_OIDC_DATA = OidcDetails(url = "a-url")
class FakeAuthenticationService : MatrixAuthenticationService {
class FakeAuthenticationService(
private val matrixClientResult: ((SessionId) -> Result<MatrixClient>)? = null
) : MatrixAuthenticationService {
private val homeserver = MutableStateFlow<MatrixHomeServerDetails?>(null)
private var oidcError: Throwable? = null
private var oidcCancelError: Throwable? = null
@@ -48,6 +50,9 @@ class FakeAuthenticationService : MatrixAuthenticationService {
override suspend fun getLatestSessionId(): SessionId? = getLatestSessionIdLambda()
override suspend fun restoreSession(sessionId: SessionId): Result<MatrixClient> {
if (matrixClientResult != null) {
return matrixClientResult.invoke(sessionId)
}
return if (matrixClient != null) {
Result.success(matrixClient!!)
} else {

View File

@@ -60,14 +60,15 @@ class DefaultFirebaseNewTokenHandler @Inject constructor(
Timber.tag(loggerTag.value).e(it, "Failed to restore session $sessionId")
}
.flatMap { client ->
pusherSubscriber.registerPusher(
matrixClient = client,
pushKey = firebaseToken,
gateway = FirebaseConfig.PUSHER_HTTP_URL,
)
}
.onFailure {
Timber.tag(loggerTag.value).e(it, "Failed to register pusher for session $sessionId")
pusherSubscriber
.registerPusher(
matrixClient = client,
pushKey = firebaseToken,
gateway = FirebaseConfig.PUSHER_HTTP_URL,
)
.onFailure {
Timber.tag(loggerTag.value).e(it, "Failed to register pusher for session $sessionId")
}
}
} else {
Timber.tag(loggerTag.value).d("This session is not using Firebase pusher")

View File

@@ -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 -> throw IllegalStateException()
}
}
),
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,
)
}
}

View File

@@ -20,8 +20,9 @@ import io.element.android.libraries.pushstore.api.UserPushStore
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
class FakeUserPushStore : UserPushStore {
class FakeUserPushStore(
private var pushProviderName: String? = null
) : UserPushStore {
private var currentRegisteredPushKey: String? = null
private val notificationEnabledForDevice = MutableStateFlow(true)
override suspend fun getPushProviderName(): String? {

View File

@@ -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")
}