Merge pull request #245 from ouchadam/tech/push-tests
Tech/Push registration tests
This commit is contained in:
commit
ab330c94d9
|
@ -6,7 +6,11 @@ import kotlinx.coroutines.test.runTest
|
|||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
fun runExpectTest(testBody: suspend ExpectTestScope.() -> Unit) {
|
||||
runTest { testBody(ExpectTest(coroutineContext)) }
|
||||
runTest {
|
||||
val expectTest = ExpectTest(coroutineContext)
|
||||
testBody(expectTest)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ExpectTest(override val coroutineContext: CoroutineContext) : ExpectTestScope {
|
||||
|
@ -24,6 +28,11 @@ class ExpectTest(override val coroutineContext: CoroutineContext) : ExpectTestSc
|
|||
expects.add(times to { block(this@expectUnit) })
|
||||
}
|
||||
|
||||
override fun <T> T.expect(times: Int, block: suspend MockKMatcherScope.(T) -> Unit) {
|
||||
coJustRun { block(this@expect) }
|
||||
expects.add(times to { block(this@expect) })
|
||||
}
|
||||
|
||||
override fun <T> T.captureExpects(block: suspend MockKMatcherScope.(T) -> Unit) {
|
||||
groups.add { block(this@captureExpects) }
|
||||
}
|
||||
|
@ -34,5 +43,6 @@ private fun Any.ignore() = Unit
|
|||
interface ExpectTestScope : CoroutineScope {
|
||||
fun verifyExpects()
|
||||
fun <T> T.expectUnit(times: Int = 1, block: suspend MockKMatcherScope.(T) -> Unit)
|
||||
fun <T> T.expect(times: Int = 1, block: suspend MockKMatcherScope.(T) -> Unit)
|
||||
fun <T> T.captureExpects(block: suspend MockKMatcherScope.(T) -> Unit)
|
||||
}
|
|
@ -10,4 +10,9 @@ dependencies {
|
|||
|
||||
implementation Dependencies.mavenCentral.kotlinSerializationJson
|
||||
implementation Dependencies.jitPack.unifiedPush
|
||||
|
||||
kotlinTest(it)
|
||||
androidImportFixturesWorkaround(project, project(":core"))
|
||||
androidImportFixturesWorkaround(project, project(":matrix:common"))
|
||||
androidImportFixturesWorkaround(project, project(":domains:android:stub"))
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import app.dapk.st.core.extensions.unsafeLazy
|
|||
import app.dapk.st.domain.push.PushTokenRegistrarPreferences
|
||||
import app.dapk.st.firebase.messaging.Messaging
|
||||
import app.dapk.st.push.messaging.MessagingPushTokenRegistrar
|
||||
import app.dapk.st.push.unifiedpush.UnifiedPushImpl
|
||||
import app.dapk.st.push.unifiedpush.UnifiedPushRegistrar
|
||||
|
||||
class PushModule(
|
||||
|
@ -21,15 +22,15 @@ class PushModule(
|
|||
) : ProvidableModule {
|
||||
|
||||
private val registrars by unsafeLazy {
|
||||
val unifiedPush = UnifiedPushImpl(context)
|
||||
PushTokenRegistrars(
|
||||
context,
|
||||
MessagingPushTokenRegistrar(
|
||||
errorTracker,
|
||||
pushHandler,
|
||||
messaging,
|
||||
),
|
||||
UnifiedPushRegistrar(context),
|
||||
PushTokenRegistrarPreferences(preferences)
|
||||
UnifiedPushRegistrar(context, unifiedPush),
|
||||
PushTokenRegistrarPreferences(preferences),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,32 +1,30 @@
|
|||
package app.dapk.st.push
|
||||
|
||||
import android.content.Context
|
||||
import app.dapk.st.domain.push.PushTokenRegistrarPreferences
|
||||
import app.dapk.st.push.messaging.MessagingPushTokenRegistrar
|
||||
import app.dapk.st.push.unifiedpush.UnifiedPushRegistrar
|
||||
import org.unifiedpush.android.connector.UnifiedPush
|
||||
|
||||
private val FIREBASE_OPTION = Registrar("Google - Firebase (FCM)")
|
||||
private val NONE = Registrar("None")
|
||||
|
||||
class PushTokenRegistrars(
|
||||
private val context: Context,
|
||||
private val messagingPushTokenRegistrar: MessagingPushTokenRegistrar,
|
||||
private val unifiedPushRegistrar: UnifiedPushRegistrar,
|
||||
private val pushTokenStore: PushTokenRegistrarPreferences,
|
||||
private val state: SelectionState = SelectionState(selection = null),
|
||||
) : PushTokenRegistrar {
|
||||
|
||||
private var selection: Registrar? = null
|
||||
|
||||
fun options(): List<Registrar> {
|
||||
val messagingOption = when (messagingPushTokenRegistrar.isAvailable()) {
|
||||
true -> FIREBASE_OPTION
|
||||
else -> null
|
||||
}
|
||||
return listOfNotNull(NONE, messagingOption) + UnifiedPush.getDistributors(context).map { Registrar(it) }
|
||||
return listOfNotNull(NONE, messagingOption) + unifiedPushRegistrar.getDistributors()
|
||||
}
|
||||
|
||||
suspend fun currentSelection() = selection ?: (pushTokenStore.currentSelection()?.let { Registrar(it) } ?: defaultSelection()).also { selection = it }
|
||||
suspend fun currentSelection() = state.selection ?: (readStoredSelection() ?: defaultSelection()).also { state.selection = it }
|
||||
|
||||
private suspend fun readStoredSelection() = pushTokenStore.currentSelection()?.let { Registrar(it) }?.takeIf { options().contains(it) }
|
||||
|
||||
private fun defaultSelection() = when (messagingPushTokenRegistrar.isAvailable()) {
|
||||
true -> FIREBASE_OPTION
|
||||
|
@ -34,7 +32,7 @@ class PushTokenRegistrars(
|
|||
}
|
||||
|
||||
suspend fun makeSelection(option: Registrar) {
|
||||
selection = option
|
||||
state.selection = option
|
||||
pushTokenStore.store(option.id)
|
||||
when (option) {
|
||||
NONE -> {
|
||||
|
@ -66,7 +64,7 @@ class PushTokenRegistrars(
|
|||
}
|
||||
|
||||
override fun unregister() {
|
||||
when (selection) {
|
||||
when (state.selection) {
|
||||
FIREBASE_OPTION -> messagingPushTokenRegistrar.unregister()
|
||||
NONE -> {
|
||||
runCatching {
|
||||
|
@ -86,4 +84,6 @@ class PushTokenRegistrars(
|
|||
}
|
||||
|
||||
@JvmInline
|
||||
value class Registrar(val id: String)
|
||||
value class Registrar(val id: String)
|
||||
|
||||
data class SelectionState(var selection: Registrar?)
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
package app.dapk.st.push.unifiedpush
|
||||
|
||||
import android.content.Context
|
||||
import org.unifiedpush.android.connector.UnifiedPush
|
||||
|
||||
interface UnifiedPush {
|
||||
fun saveDistributor(distributor: String)
|
||||
fun getDistributor(): String
|
||||
fun getDistributors(): List<String>
|
||||
fun registerApp()
|
||||
fun unregisterApp()
|
||||
}
|
||||
|
||||
internal class UnifiedPushImpl(private val context: Context) : app.dapk.st.push.unifiedpush.UnifiedPush {
|
||||
override fun saveDistributor(distributor: String) = UnifiedPush.saveDistributor(context, distributor)
|
||||
override fun getDistributor(): String = UnifiedPush.getDistributor(context)
|
||||
override fun getDistributors(): List<String> = UnifiedPush.getDistributors(context)
|
||||
override fun registerApp() = UnifiedPush.registerApp(context)
|
||||
override fun unregisterApp() = UnifiedPush.unregisterApp(context)
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
package app.dapk.st.push.unifiedpush
|
||||
|
||||
import android.content.Context
|
||||
import app.dapk.st.core.AppLogTag
|
||||
import app.dapk.st.core.log
|
||||
import app.dapk.st.core.module
|
||||
import app.dapk.st.matrix.common.EventId
|
||||
import app.dapk.st.matrix.common.RoomId
|
||||
import app.dapk.st.push.PushModule
|
||||
import app.dapk.st.push.PushTokenPayload
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.net.URL
|
||||
|
||||
private const val FALLBACK_UNIFIED_PUSH_GATEWAY = "https://matrix.gateway.unifiedpush.org/_matrix/push/v1/notify"
|
||||
private val json = Json { ignoreUnknownKeys = true }
|
||||
|
||||
class UnifiedPushMessageDelegate(
|
||||
private val scope: CoroutineScope = CoroutineScope(SupervisorJob()),
|
||||
private val pushModuleProvider: (Context) -> PushModule = { it.module() },
|
||||
private val endpointReader: suspend (URL) -> String = {
|
||||
runCatching { it.openStream().use { String(it.readBytes()) } }.getOrNull() ?: ""
|
||||
}
|
||||
) {
|
||||
|
||||
fun onMessage(context: Context, message: ByteArray) {
|
||||
log(AppLogTag.PUSH, "UnifiedPush onMessage, $message")
|
||||
val module = pushModuleProvider(context)
|
||||
val handler = module.pushHandler()
|
||||
scope.launch {
|
||||
withContext(module.dispatcher().io) {
|
||||
val payload = json.decodeFromString(UnifiedPushMessagePayload.serializer(), String(message))
|
||||
handler.onMessageReceived(payload.notification.eventId?.let { EventId(it) }, payload.notification.roomId?.let { RoomId(it) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onNewEndpoint(context: Context, endpoint: String) {
|
||||
log(AppLogTag.PUSH, "UnifiedPush onNewEndpoint $endpoint")
|
||||
val module = pushModuleProvider(context)
|
||||
val handler = module.pushHandler()
|
||||
scope.launch {
|
||||
withContext(module.dispatcher().io) {
|
||||
val matrixEndpoint = URL(endpoint).let { URL("${it.protocol}://${it.host}/_matrix/push/v1/notify") }
|
||||
val content = endpointReader(matrixEndpoint)
|
||||
val gatewayUrl = when {
|
||||
content.contains("\"gateway\":\"matrix\"") -> matrixEndpoint.toString()
|
||||
else -> FALLBACK_UNIFIED_PUSH_GATEWAY
|
||||
}
|
||||
handler.onNewToken(PushTokenPayload(token = endpoint, gatewayUrl = gatewayUrl))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
private data class UnifiedPushMessagePayload(
|
||||
@SerialName("notification") val notification: Notification,
|
||||
) {
|
||||
|
||||
@Serializable
|
||||
data class Notification(
|
||||
@SerialName("event_id") val eventId: String? = null,
|
||||
@SerialName("room_id") val roomId: String? = null,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
|
@ -3,56 +3,19 @@ package app.dapk.st.push.unifiedpush
|
|||
import android.content.Context
|
||||
import app.dapk.st.core.AppLogTag
|
||||
import app.dapk.st.core.log
|
||||
import app.dapk.st.core.module
|
||||
import app.dapk.st.matrix.common.EventId
|
||||
import app.dapk.st.matrix.common.RoomId
|
||||
import app.dapk.st.push.PushModule
|
||||
import app.dapk.st.push.PushTokenPayload
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.unifiedpush.android.connector.MessagingReceiver
|
||||
import java.net.URL
|
||||
|
||||
private val json = Json { ignoreUnknownKeys = true }
|
||||
|
||||
private const val FALLBACK_UNIFIED_PUSH_GATEWAY = "https://matrix.gateway.unifiedpush.org/_matrix/push/v1/notify"
|
||||
|
||||
class UnifiedPushMessageReceiver : MessagingReceiver() {
|
||||
|
||||
private val scope = CoroutineScope(SupervisorJob())
|
||||
private val delegate = UnifiedPushMessageDelegate()
|
||||
|
||||
override fun onMessage(context: Context, message: ByteArray, instance: String) {
|
||||
log(AppLogTag.PUSH, "UnifiedPush onMessage, $message")
|
||||
val module = context.module<PushModule>()
|
||||
val handler = module.pushHandler()
|
||||
scope.launch {
|
||||
withContext(module.dispatcher().io) {
|
||||
val payload = json.decodeFromString(UnifiedPushMessagePayload.serializer(), String(message))
|
||||
handler.onMessageReceived(payload.notification.eventId?.let { EventId(it) }, payload.notification.roomId?.let { RoomId(it) })
|
||||
}
|
||||
}
|
||||
delegate.onMessage(context, message)
|
||||
}
|
||||
|
||||
override fun onNewEndpoint(context: Context, endpoint: String, instance: String) {
|
||||
log(AppLogTag.PUSH, "UnifiedPush onNewEndpoint $endpoint")
|
||||
val module = context.module<PushModule>()
|
||||
val handler = module.pushHandler()
|
||||
scope.launch {
|
||||
withContext(module.dispatcher().io) {
|
||||
val matrixEndpoint = URL(endpoint).let { URL("${it.protocol}://${it.host}/_matrix/push/v1/notify") }
|
||||
val content = runCatching { matrixEndpoint.openStream().use { String(it.readBytes()) } }.getOrNull() ?: ""
|
||||
val gatewayUrl = when {
|
||||
content.contains("\"gateway\":\"matrix\"") -> matrixEndpoint.toString()
|
||||
else -> FALLBACK_UNIFIED_PUSH_GATEWAY
|
||||
}
|
||||
handler.onNewToken(PushTokenPayload(token = endpoint, gatewayUrl = gatewayUrl))
|
||||
}
|
||||
}
|
||||
delegate.onNewEndpoint(context, endpoint)
|
||||
}
|
||||
|
||||
override fun onRegistrationFailed(context: Context, instance: String) {
|
||||
|
@ -63,15 +26,4 @@ class UnifiedPushMessageReceiver : MessagingReceiver() {
|
|||
log(AppLogTag.PUSH, "UnifiedPush onUnregistered")
|
||||
}
|
||||
|
||||
@Serializable
|
||||
private data class UnifiedPushMessagePayload(
|
||||
@SerialName("notification") val notification: Notification,
|
||||
) {
|
||||
|
||||
@Serializable
|
||||
data class Notification(
|
||||
@SerialName("event_id") val eventId: String? = null,
|
||||
@SerialName("room_id") val roomId: String? = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,38 +7,41 @@ import app.dapk.st.core.AppLogTag
|
|||
import app.dapk.st.core.log
|
||||
import app.dapk.st.push.PushTokenRegistrar
|
||||
import app.dapk.st.push.Registrar
|
||||
import org.unifiedpush.android.connector.UnifiedPush
|
||||
|
||||
class UnifiedPushRegistrar(
|
||||
private val context: Context,
|
||||
private val unifiedPush: UnifiedPush,
|
||||
private val componentFactory: (Context) -> ComponentName = { ComponentName(it, UnifiedPushMessageReceiver::class.java) }
|
||||
) : PushTokenRegistrar {
|
||||
|
||||
fun getDistributors() = unifiedPush.getDistributors().map { Registrar(it) }
|
||||
|
||||
fun registerSelection(registrar: Registrar) {
|
||||
log(AppLogTag.PUSH, "UnifiedPush - register: $registrar")
|
||||
UnifiedPush.saveDistributor(context, registrar.id)
|
||||
unifiedPush.saveDistributor(registrar.id)
|
||||
registerApp()
|
||||
}
|
||||
|
||||
override suspend fun registerCurrentToken() {
|
||||
log(AppLogTag.PUSH, "UnifiedPush - register current token")
|
||||
if (UnifiedPush.getDistributor(context).isNotEmpty()) {
|
||||
if (unifiedPush.getDistributor().isNotEmpty()) {
|
||||
registerApp()
|
||||
}
|
||||
}
|
||||
|
||||
private fun registerApp() {
|
||||
context.packageManager.setComponentEnabledSetting(
|
||||
ComponentName(context, UnifiedPushMessageReceiver::class.java),
|
||||
componentFactory(context),
|
||||
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
|
||||
PackageManager.DONT_KILL_APP,
|
||||
)
|
||||
UnifiedPush.registerApp(context)
|
||||
unifiedPush.registerApp()
|
||||
}
|
||||
|
||||
override fun unregister() {
|
||||
UnifiedPush.unregisterApp(context)
|
||||
unifiedPush.unregisterApp()
|
||||
context.packageManager.setComponentEnabledSetting(
|
||||
ComponentName(context, UnifiedPushMessageReceiver::class.java),
|
||||
componentFactory(context),
|
||||
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
|
||||
PackageManager.DONT_KILL_APP,
|
||||
)
|
||||
|
|
|
@ -0,0 +1,205 @@
|
|||
package app.dapk.st.push
|
||||
|
||||
import app.dapk.st.domain.push.PushTokenRegistrarPreferences
|
||||
import app.dapk.st.push.messaging.MessagingPushTokenRegistrar
|
||||
import app.dapk.st.push.unifiedpush.UnifiedPushRegistrar
|
||||
import io.mockk.*
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.amshove.kluent.shouldBeEqualTo
|
||||
import org.junit.Test
|
||||
import test.delegateReturn
|
||||
import test.runExpectTest
|
||||
|
||||
private val UNIFIED_PUSH = Registrar("unified-push option")
|
||||
private val NONE = Registrar("None")
|
||||
private val FIREBASE = Registrar("Google - Firebase (FCM)")
|
||||
private val UNIFIED_PUSH_DISTRIBUTORS = listOf(UNIFIED_PUSH)
|
||||
|
||||
class PushTokenRegistrarsTest {
|
||||
|
||||
private val fakeMessagingPushRegistrar = FakeMessagingPushRegistrar()
|
||||
private val fakeUnifiedPushRegistrar = FakeUnifiedPushRegistrar()
|
||||
private val fakePushTokenRegistrarPreferences = FakePushTokenRegistrarPreferences()
|
||||
private val selectionState = SelectionState(selection = null)
|
||||
|
||||
private val registrars = PushTokenRegistrars(
|
||||
fakeMessagingPushRegistrar.instance,
|
||||
fakeUnifiedPushRegistrar.instance,
|
||||
fakePushTokenRegistrarPreferences.instance,
|
||||
selectionState,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `given messaging is available, when reading options, then returns firebase and unified push`() {
|
||||
fakeMessagingPushRegistrar.givenIsAvailable().returns(true)
|
||||
fakeUnifiedPushRegistrar.givenDistributors().returns(UNIFIED_PUSH_DISTRIBUTORS)
|
||||
|
||||
val result = registrars.options()
|
||||
|
||||
result shouldBeEqualTo listOf(Registrar("None"), FIREBASE) + UNIFIED_PUSH_DISTRIBUTORS
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given messaging is not available, when reading options, then returns unified push`() {
|
||||
fakeMessagingPushRegistrar.givenIsAvailable().returns(false)
|
||||
fakeUnifiedPushRegistrar.givenDistributors().returns(UNIFIED_PUSH_DISTRIBUTORS)
|
||||
|
||||
val result = registrars.options()
|
||||
|
||||
result shouldBeEqualTo listOf(Registrar("None")) + UNIFIED_PUSH_DISTRIBUTORS
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given no saved selection and messaging is not available, when reading default selection, then returns none`() = runTest {
|
||||
fakePushTokenRegistrarPreferences.givenCurrentSelection().returns(null)
|
||||
fakeMessagingPushRegistrar.givenIsAvailable().returns(false)
|
||||
|
||||
val result = registrars.currentSelection()
|
||||
|
||||
result shouldBeEqualTo NONE
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given no saved selection and messaging is available, when reading default selection, then returns firebase`() = runTest {
|
||||
fakePushTokenRegistrarPreferences.givenCurrentSelection().returns(null)
|
||||
fakeMessagingPushRegistrar.givenIsAvailable().returns(true)
|
||||
|
||||
val result = registrars.currentSelection()
|
||||
|
||||
result shouldBeEqualTo FIREBASE
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given saved selection and is a option, when reading default selection, then returns selection`() = runTest {
|
||||
fakeUnifiedPushRegistrar.givenDistributors().returns(UNIFIED_PUSH_DISTRIBUTORS)
|
||||
fakePushTokenRegistrarPreferences.givenCurrentSelection().returns(FIREBASE.id)
|
||||
fakeMessagingPushRegistrar.givenIsAvailable().returns(true)
|
||||
|
||||
val result = registrars.currentSelection()
|
||||
|
||||
result shouldBeEqualTo FIREBASE
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given saved selection and is not an option, when reading default selection, then returns next default`() = runTest {
|
||||
fakeUnifiedPushRegistrar.givenDistributors().returns(UNIFIED_PUSH_DISTRIBUTORS)
|
||||
fakePushTokenRegistrarPreferences.givenCurrentSelection().returns(FIREBASE.id)
|
||||
fakeMessagingPushRegistrar.givenIsAvailable().returns(false)
|
||||
|
||||
val result = registrars.currentSelection()
|
||||
|
||||
result shouldBeEqualTo NONE
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when selecting none, then stores and unregisters`() = runExpectTest {
|
||||
fakePushTokenRegistrarPreferences.instance.expect { it.store(NONE.id) }
|
||||
fakeMessagingPushRegistrar.instance.expect { it.unregister() }
|
||||
fakeUnifiedPushRegistrar.instance.expect { it.unregister() }
|
||||
|
||||
registrars.makeSelection(NONE)
|
||||
|
||||
verifyExpects()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when selecting firebase, then stores and unregisters unifiedpush`() = runExpectTest {
|
||||
fakePushTokenRegistrarPreferences.instance.expect { it.store(FIREBASE.id) }
|
||||
fakeMessagingPushRegistrar.instance.expect { it.registerCurrentToken() }
|
||||
fakeUnifiedPushRegistrar.instance.expect { it.unregister() }
|
||||
|
||||
registrars.makeSelection(FIREBASE)
|
||||
|
||||
verifyExpects()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when selecting unified push, then stores and unregisters firebase`() = runExpectTest {
|
||||
fakePushTokenRegistrarPreferences.instance.expect { it.store(UNIFIED_PUSH.id) }
|
||||
fakeMessagingPushRegistrar.instance.expect { it.unregister() }
|
||||
fakeUnifiedPushRegistrar.instance.expect { it.registerSelection(UNIFIED_PUSH) }
|
||||
|
||||
registrars.makeSelection(UNIFIED_PUSH)
|
||||
|
||||
verifyExpects()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given unified push selected, when registering current token, then delegates`() = runExpectTest {
|
||||
selectionState.selection = UNIFIED_PUSH
|
||||
fakeUnifiedPushRegistrar.instance.expect { it.registerCurrentToken() }
|
||||
|
||||
registrars.registerCurrentToken()
|
||||
|
||||
verifyExpects()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given firebase selected, when registering current token, then delegates`() = runExpectTest {
|
||||
selectionState.selection = FIREBASE
|
||||
fakeMessagingPushRegistrar.instance.expect { it.registerCurrentToken() }
|
||||
|
||||
registrars.registerCurrentToken()
|
||||
|
||||
verifyExpects()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given none selected, when registering current token, then does nothing`() = runExpectTest {
|
||||
selectionState.selection = NONE
|
||||
|
||||
registrars.registerCurrentToken()
|
||||
|
||||
verify { fakeMessagingPushRegistrar.instance wasNot Called }
|
||||
verify { fakeUnifiedPushRegistrar.instance wasNot Called }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given unified push selected, when unregistering, then delegates`() = runExpectTest {
|
||||
selectionState.selection = UNIFIED_PUSH
|
||||
fakeUnifiedPushRegistrar.instance.expect { it.unregister() }
|
||||
|
||||
registrars.unregister()
|
||||
|
||||
verifyExpects()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given firebase selected, when unregistering, then delegates`() = runExpectTest {
|
||||
selectionState.selection = FIREBASE
|
||||
fakeMessagingPushRegistrar.instance.expect { it.unregister() }
|
||||
|
||||
registrars.unregister()
|
||||
|
||||
verifyExpects()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given none selected, when unregistering, then unregisters all`() = runExpectTest {
|
||||
selectionState.selection = NONE
|
||||
fakeUnifiedPushRegistrar.instance.expect { it.unregister() }
|
||||
fakeMessagingPushRegistrar.instance.expect { it.unregister() }
|
||||
|
||||
registrars.unregister()
|
||||
|
||||
verifyExpects()
|
||||
}
|
||||
}
|
||||
|
||||
class FakeMessagingPushRegistrar {
|
||||
val instance = mockk<MessagingPushTokenRegistrar>()
|
||||
|
||||
fun givenIsAvailable() = every { instance.isAvailable() }.delegateReturn()
|
||||
}
|
||||
|
||||
class FakeUnifiedPushRegistrar {
|
||||
val instance = mockk<UnifiedPushRegistrar>()
|
||||
|
||||
fun givenDistributors() = every { instance.getDistributors() }.delegateReturn()
|
||||
}
|
||||
|
||||
class FakePushTokenRegistrarPreferences {
|
||||
val instance = mockk<PushTokenRegistrarPreferences>()
|
||||
|
||||
fun givenCurrentSelection() = coEvery { instance.currentSelection() }.delegateReturn()
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
package app.dapk.st.push.messaging
|
||||
|
||||
import app.dapk.st.firebase.messaging.Messaging
|
||||
import app.dapk.st.push.PushTokenPayload
|
||||
import app.dapk.st.push.unifiedpush.FakePushHandler
|
||||
import fake.FakeErrorTracker
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import org.amshove.kluent.shouldBeEqualTo
|
||||
import org.junit.Test
|
||||
import test.delegateReturn
|
||||
import test.runExpectTest
|
||||
|
||||
private const val A_TOKEN = "a-token"
|
||||
private const val SYGNAL_GATEWAY = "https://sygnal.dapk.app/_matrix/push/v1/notify"
|
||||
private val AN_ERROR = RuntimeException()
|
||||
|
||||
class MessagingPushTokenRegistrarTest {
|
||||
|
||||
private val fakePushHandler = FakePushHandler()
|
||||
private val fakeErrorTracker = FakeErrorTracker()
|
||||
private val fakeMessaging = FakeMessaging()
|
||||
|
||||
private val registrar = MessagingPushTokenRegistrar(
|
||||
fakeErrorTracker,
|
||||
fakePushHandler,
|
||||
fakeMessaging.instance,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `when checking isAvailable, then delegates`() = runExpectTest {
|
||||
fakeMessaging.givenIsAvailable().returns(true)
|
||||
|
||||
val result = registrar.isAvailable()
|
||||
|
||||
result shouldBeEqualTo true
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when registering current token, then enables and forwards current token to handler`() = runExpectTest {
|
||||
fakeMessaging.instance.expect { it.enable() }
|
||||
fakePushHandler.expect { it.onNewToken(PushTokenPayload(A_TOKEN, SYGNAL_GATEWAY)) }
|
||||
fakeMessaging.givenToken().returns(A_TOKEN)
|
||||
|
||||
registrar.registerCurrentToken()
|
||||
|
||||
verifyExpects()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given fails to register, when registering current token, then tracks error`() = runExpectTest {
|
||||
fakeMessaging.instance.expect { it.enable() }
|
||||
fakeMessaging.givenToken().throws(AN_ERROR)
|
||||
fakeErrorTracker.expect { it.track(AN_ERROR) }
|
||||
|
||||
registrar.registerCurrentToken()
|
||||
|
||||
verifyExpects()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when unregistering, then deletes token and disables`() = runExpectTest {
|
||||
fakeMessaging.instance.expect { it.deleteToken() }
|
||||
fakeMessaging.instance.expect { it.disable() }
|
||||
|
||||
registrar.unregister()
|
||||
|
||||
verifyExpects()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class FakeMessaging {
|
||||
val instance = mockk<Messaging>()
|
||||
|
||||
fun givenIsAvailable() = every { instance.isAvailable() }.delegateReturn()
|
||||
fun givenToken() = coEvery { instance.token() }.delegateReturn()
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package app.dapk.st.push.messaging
|
||||
|
||||
import app.dapk.st.push.PushTokenPayload
|
||||
import app.dapk.st.push.unifiedpush.FakePushHandler
|
||||
import fixture.aRoomId
|
||||
import fixture.anEventId
|
||||
import org.junit.Test
|
||||
import test.runExpectTest
|
||||
|
||||
private const val A_TOKEN = "a-push-token"
|
||||
private const val SYGNAL_GATEWAY = "https://sygnal.dapk.app/_matrix/push/v1/notify"
|
||||
private val A_ROOM_ID = aRoomId()
|
||||
private val AN_EVENT_ID = anEventId()
|
||||
|
||||
class MessagingServiceAdapterTest {
|
||||
|
||||
private val fakePushHandler = FakePushHandler()
|
||||
|
||||
private val messagingServiceAdapter = MessagingServiceAdapter(fakePushHandler)
|
||||
|
||||
@Test
|
||||
fun `onNewToken, then delegates to push handler`() = runExpectTest {
|
||||
fakePushHandler.expect {
|
||||
it.onNewToken(PushTokenPayload(token = A_TOKEN, gatewayUrl = SYGNAL_GATEWAY))
|
||||
}
|
||||
messagingServiceAdapter.onNewToken(A_TOKEN)
|
||||
|
||||
verifyExpects()
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun `onMessageReceived, then delegates to push handler`() = runExpectTest {
|
||||
fakePushHandler.expect {
|
||||
it.onMessageReceived(AN_EVENT_ID, A_ROOM_ID)
|
||||
}
|
||||
messagingServiceAdapter.onMessageReceived(AN_EVENT_ID, A_ROOM_ID)
|
||||
|
||||
verifyExpects()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package app.dapk.st.push.unifiedpush
|
||||
|
||||
import app.dapk.st.push.PushHandler
|
||||
import io.mockk.mockk
|
||||
|
||||
class FakePushHandler : PushHandler by mockk()
|
|
@ -0,0 +1,95 @@
|
|||
package app.dapk.st.push.unifiedpush
|
||||
|
||||
import app.dapk.st.matrix.common.EventId
|
||||
import app.dapk.st.matrix.common.RoomId
|
||||
import app.dapk.st.push.PushModule
|
||||
import app.dapk.st.push.PushTokenPayload
|
||||
import fake.FakeContext
|
||||
import fixture.CoroutineDispatchersFixture.aCoroutineDispatchers
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||
import org.junit.Test
|
||||
import test.delegateReturn
|
||||
import test.runExpectTest
|
||||
import java.net.URL
|
||||
|
||||
private val A_CONTEXT = FakeContext()
|
||||
private const val A_ROOM_ID = "a room id"
|
||||
private const val AN_EVENT_ID = "an event id"
|
||||
private const val AN_ENDPOINT_HOST = "https://aendpointurl.com"
|
||||
private const val AN_ENDPOINT = "$AN_ENDPOINT_HOST/with/path"
|
||||
private const val A_GATEWAY_URL = "$AN_ENDPOINT_HOST/_matrix/push/v1/notify"
|
||||
private const val FALLBACK_GATEWAY_URL = "https://matrix.gateway.unifiedpush.org/_matrix/push/v1/notify"
|
||||
|
||||
class UnifiedPushMessageDelegateTest {
|
||||
|
||||
private val fakePushHandler = FakePushHandler()
|
||||
private val fakeEndpointReader = FakeEndpointReader()
|
||||
private val fakePushModule = FakePushModule().also {
|
||||
it.givenPushHandler().returns(fakePushHandler)
|
||||
}
|
||||
|
||||
private val unifiedPushReceiver = UnifiedPushMessageDelegate(
|
||||
CoroutineScope(UnconfinedTestDispatcher()),
|
||||
pushModuleProvider = { _ -> fakePushModule.instance },
|
||||
endpointReader = fakeEndpointReader,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `parses incoming message payloads`() = runExpectTest {
|
||||
fakePushHandler.expect { it.onMessageReceived(EventId(AN_EVENT_ID), RoomId(A_ROOM_ID)) }
|
||||
val messageBytes = createMessage(A_ROOM_ID, AN_EVENT_ID)
|
||||
|
||||
unifiedPushReceiver.onMessage(A_CONTEXT.instance, messageBytes)
|
||||
|
||||
verifyExpects()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given endpoint is a gateway, then uses original endpoint url`() = runExpectTest {
|
||||
fakeEndpointReader.given(A_GATEWAY_URL).returns("""{"unifiedpush":{"gateway":"matrix"}}""")
|
||||
fakePushHandler.expect { it.onNewToken(PushTokenPayload(token = AN_ENDPOINT, gatewayUrl = A_GATEWAY_URL)) }
|
||||
|
||||
unifiedPushReceiver.onNewEndpoint(A_CONTEXT.instance, AN_ENDPOINT)
|
||||
|
||||
verifyExpects()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given endpoint is not a gateway, then uses fallback endpoint url`() = runExpectTest {
|
||||
fakeEndpointReader.given(A_GATEWAY_URL).returns("")
|
||||
fakePushHandler.expect { it.onNewToken(PushTokenPayload(token = AN_ENDPOINT, gatewayUrl = FALLBACK_GATEWAY_URL)) }
|
||||
|
||||
unifiedPushReceiver.onNewEndpoint(A_CONTEXT.instance, AN_ENDPOINT)
|
||||
|
||||
verifyExpects()
|
||||
}
|
||||
|
||||
private fun createMessage(roomId: String, eventId: String) = """
|
||||
{
|
||||
"notification": {
|
||||
"room_id": "$roomId",
|
||||
"event_id": "$eventId"
|
||||
}
|
||||
}
|
||||
""".trimIndent().toByteArray()
|
||||
}
|
||||
|
||||
class FakePushModule {
|
||||
val instance = mockk<PushModule>()
|
||||
|
||||
init {
|
||||
every { instance.dispatcher() }.returns(aCoroutineDispatchers())
|
||||
}
|
||||
|
||||
fun givenPushHandler() = every { instance.pushHandler() }.delegateReturn()
|
||||
}
|
||||
|
||||
class FakeEndpointReader : suspend (URL) -> String by mockk() {
|
||||
|
||||
fun given(url: String) = coEvery { this@FakeEndpointReader.invoke(URL(url)) }.delegateReturn()
|
||||
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
package app.dapk.st.push.unifiedpush
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import app.dapk.st.push.Registrar
|
||||
import fake.FakeContext
|
||||
import fake.FakePackageManager
|
||||
import io.mockk.Called
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import org.amshove.kluent.shouldBeEqualTo
|
||||
import org.junit.Test
|
||||
import test.delegateReturn
|
||||
import test.runExpectTest
|
||||
|
||||
private val A_COMPONENT_NAME = FakeComponentName()
|
||||
private val A_REGISTRAR_SELECTION = Registrar("a-registrar")
|
||||
private const val A_SAVED_DISTRIBUTOR = "a distributor"
|
||||
|
||||
class UnifiedPushRegistrarTest {
|
||||
|
||||
private val fakePackageManager = FakePackageManager()
|
||||
private val fakeContext = FakeContext().also {
|
||||
it.givenPackageManager().returns(fakePackageManager.instance)
|
||||
}
|
||||
private val fakeUnifiedPush = FakeUnifiedPush()
|
||||
private val fakeComponentFactory = { _: Context -> A_COMPONENT_NAME.instance }
|
||||
|
||||
private val registrar = UnifiedPushRegistrar(fakeContext.instance, fakeUnifiedPush, fakeComponentFactory)
|
||||
|
||||
@Test
|
||||
fun `when unregistering, then updates unified push and disables component`() = runExpectTest {
|
||||
fakeUnifiedPush.expect { it.unregisterApp() }
|
||||
fakePackageManager.instance.expect {
|
||||
it.setComponentEnabledSetting(A_COMPONENT_NAME.instance, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP)
|
||||
}
|
||||
|
||||
registrar.unregister()
|
||||
|
||||
verifyExpects()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when registering selection, then updates unified push and enables component`() = runExpectTest {
|
||||
fakeUnifiedPush.expect { it.registerApp() }
|
||||
fakeUnifiedPush.expect { it.saveDistributor(A_REGISTRAR_SELECTION.id) }
|
||||
fakePackageManager.instance.expect {
|
||||
it.setComponentEnabledSetting(A_COMPONENT_NAME.instance, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP)
|
||||
}
|
||||
|
||||
registrar.registerSelection(A_REGISTRAR_SELECTION)
|
||||
|
||||
verifyExpects()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given saved distributor, when registering current token, then updates unified push and enables component`() = runExpectTest {
|
||||
fakeUnifiedPush.givenDistributor().returns(A_SAVED_DISTRIBUTOR)
|
||||
fakeUnifiedPush.expect { it.registerApp() }
|
||||
fakePackageManager.instance.expect {
|
||||
it.setComponentEnabledSetting(A_COMPONENT_NAME.instance, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP)
|
||||
}
|
||||
|
||||
registrar.registerCurrentToken()
|
||||
|
||||
verifyExpects()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given no distributor, when registering current token, then does nothing`() = runExpectTest {
|
||||
fakeUnifiedPush.givenDistributor().returns("")
|
||||
|
||||
registrar.registerCurrentToken()
|
||||
|
||||
verify(exactly = 0) { fakeUnifiedPush.registerApp() }
|
||||
verify { fakePackageManager.instance wasNot Called }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given distributors, then returns them as Registrars`() {
|
||||
fakeUnifiedPush.givenDistributors().returns(listOf("a", "b"))
|
||||
|
||||
val result = registrar.getDistributors()
|
||||
|
||||
result shouldBeEqualTo listOf(Registrar("a"), Registrar("b"))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class FakeUnifiedPush : UnifiedPush by mockk() {
|
||||
fun givenDistributor() = every { getDistributor() }.delegateReturn()
|
||||
fun givenDistributors() = every { getDistributors() }.delegateReturn()
|
||||
}
|
||||
|
||||
class FakeComponentName {
|
||||
val instance = mockk<ComponentName>()
|
||||
}
|
|
@ -1,8 +1,16 @@
|
|||
package fake
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import test.delegateReturn
|
||||
|
||||
class FakeContext {
|
||||
val instance = mockk<Context>()
|
||||
fun givenPackageManager() = every { instance.packageManager }.delegateReturn()
|
||||
}
|
||||
|
||||
class FakePackageManager {
|
||||
val instance = mockk<PackageManager>()
|
||||
}
|
|
@ -15,7 +15,6 @@ import io.mockk.every
|
|||
import io.mockk.mockk
|
||||
import org.junit.Test
|
||||
import test.delegateReturn
|
||||
import test.expect
|
||||
import test.runExpectTest
|
||||
|
||||
private const val SUMMARY_ID = 101
|
||||
|
@ -45,12 +44,13 @@ class NotificationRendererTest {
|
|||
@Test
|
||||
fun `given removed rooms when rendering then cancels notifications with cancelled room ids`() = runExpectTest {
|
||||
val removedRooms = setOf(aRoomId("id-1"), aRoomId("id-2"))
|
||||
fakeNotificationFactory.instance.expect { it.mapToNotifications(NotificationState(emptyMap(), removedRooms, emptySet(), emptySet())) }
|
||||
fakeNotificationManager.instance.expectUnit {
|
||||
removedRooms.forEach { removedRoom -> it.cancel(removedRoom.value, ROOM_MESSAGE_ID) }
|
||||
}
|
||||
val state = aNotificationState(removedRooms = removedRooms)
|
||||
fakeNotificationFactory.givenNotifications(state).returns(aNotifications())
|
||||
fakeNotificationManager.instance.expectUnit { removedRooms.forEach { removedRoom -> it.cancel(removedRoom.value, ROOM_MESSAGE_ID) } }
|
||||
fakeNotificationManager.instance.expectUnit { it.cancel(SUMMARY_ID) }
|
||||
|
||||
notificationRenderer.render(state)
|
||||
|
||||
notificationRenderer.render(NotificationState(emptyMap(), removedRooms, emptySet(), emptySet()))
|
||||
verifyExpects()
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ def excludes = [
|
|||
'**/*Activity*',
|
||||
'**/*AndroidService*',
|
||||
'**/*Application*',
|
||||
'**/*Receiver*',
|
||||
|
||||
// Generated
|
||||
'**/*serializer*',
|
||||
|
|
Loading…
Reference in New Issue