adding unified push delegate tests

This commit is contained in:
Adam Brown 2022-11-03 13:44:01 +00:00
parent 093e5b64bb
commit 1e8d868348
4 changed files with 118 additions and 3 deletions

View File

@ -6,7 +6,11 @@ import kotlinx.coroutines.test.runTest
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
fun runExpectTest(testBody: suspend ExpectTestScope.() -> Unit) { fun runExpectTest(testBody: suspend ExpectTestScope.() -> Unit) {
runTest { testBody(ExpectTest(coroutineContext)) } runTest {
val expectTest = ExpectTest(coroutineContext)
testBody(expectTest)
}
} }
class ExpectTest(override val coroutineContext: CoroutineContext) : ExpectTestScope { 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) }) expects.add(times to { block(this@expectUnit) })
} }
override fun <T> T.expect(times: Int, block: suspend MockKMatcherScope.(T) -> Unit) {
coJustRun { block(this@expect) }.ignore()
expects.add(times to { block(this@expect) })
}
override fun <T> T.captureExpects(block: suspend MockKMatcherScope.(T) -> Unit) { override fun <T> T.captureExpects(block: suspend MockKMatcherScope.(T) -> Unit) {
groups.add { block(this@captureExpects) } groups.add { block(this@captureExpects) }
} }
@ -34,5 +43,6 @@ private fun Any.ignore() = Unit
interface ExpectTestScope : CoroutineScope { interface ExpectTestScope : CoroutineScope {
fun verifyExpects() fun verifyExpects()
fun <T> T.expectUnit(times: Int = 1, block: suspend MockKMatcherScope.(T) -> Unit) 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) fun <T> T.captureExpects(block: suspend MockKMatcherScope.(T) -> Unit)
} }

View File

@ -10,4 +10,8 @@ dependencies {
implementation Dependencies.mavenCentral.kotlinSerializationJson implementation Dependencies.mavenCentral.kotlinSerializationJson
implementation Dependencies.jitPack.unifiedPush implementation Dependencies.jitPack.unifiedPush
kotlinTest(it)
androidImportFixturesWorkaround(project, project(":core"))
androidImportFixturesWorkaround(project, project(":domains:android:stub"))
} }

View File

@ -22,7 +22,10 @@ private val json = Json { ignoreUnknownKeys = true }
class UnifiedPushMessageDelegate( class UnifiedPushMessageDelegate(
private val scope: CoroutineScope = CoroutineScope(SupervisorJob()), private val scope: CoroutineScope = CoroutineScope(SupervisorJob()),
private val pushModuleProvider: (Context) -> PushModule = { it.module() } 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) { fun onMessage(context: Context, message: ByteArray) {
@ -44,7 +47,7 @@ class UnifiedPushMessageDelegate(
scope.launch { scope.launch {
withContext(module.dispatcher().io) { withContext(module.dispatcher().io) {
val matrixEndpoint = URL(endpoint).let { URL("${it.protocol}://${it.host}/_matrix/push/v1/notify") } 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 content = endpointReader(matrixEndpoint)
val gatewayUrl = when { val gatewayUrl = when {
content.contains("\"gateway\":\"matrix\"") -> matrixEndpoint.toString() content.contains("\"gateway\":\"matrix\"") -> matrixEndpoint.toString()
else -> FALLBACK_UNIFIED_PUSH_GATEWAY else -> FALLBACK_UNIFIED_PUSH_GATEWAY

View File

@ -0,0 +1,98 @@
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.PushHandler
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 FakePushHandler : PushHandler by mockk()
class FakeEndpointReader : suspend (URL) -> String by mockk() {
fun given(url: String) = coEvery { this@FakeEndpointReader.invoke(URL(url)) }.delegateReturn()
}