extracting me fetching logic to its own usecase and testing
This commit is contained in:
parent
1d1aff0ca9
commit
6532478fc8
|
@ -4,6 +4,9 @@
|
||||||
<inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
|
<inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
<option name="composableFile" value="true" />
|
<option name="composableFile" value="true" />
|
||||||
</inspection_tool>
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PreviewApiLevelMustBeValid" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
<inspection_tool class="PreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
|
<inspection_tool class="PreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
<option name="composableFile" value="true" />
|
<option name="composableFile" value="true" />
|
||||||
</inspection_tool>
|
</inspection_tool>
|
||||||
|
@ -25,5 +28,6 @@
|
||||||
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
<option name="composableFile" value="true" />
|
<option name="composableFile" value="true" />
|
||||||
</inspection_tool>
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PrivatePropertyName" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||||
</profile>
|
</profile>
|
||||||
</component>
|
</component>
|
|
@ -0,0 +1,16 @@
|
||||||
|
package fake
|
||||||
|
|
||||||
|
import io.ktor.client.statement.*
|
||||||
|
import io.ktor.http.*
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
|
||||||
|
class FakeHttpResponse {
|
||||||
|
|
||||||
|
val instance = mockk<HttpResponse>(relaxed = true)
|
||||||
|
|
||||||
|
fun givenStatus(code: Int) {
|
||||||
|
every { instance.status } returns HttpStatusCode(code, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
package fake
|
package fake
|
||||||
|
|
||||||
import app.dapk.st.matrix.http.MatrixHttpClient
|
import app.dapk.st.matrix.http.MatrixHttpClient
|
||||||
|
import io.ktor.client.plugins.*
|
||||||
import io.mockk.coEvery
|
import io.mockk.coEvery
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
|
|
||||||
|
@ -8,4 +9,8 @@ class FakeMatrixHttpClient : MatrixHttpClient by mockk() {
|
||||||
fun <T : Any> given(request: MatrixHttpClient.HttpRequest<T>, response: T) {
|
fun <T : Any> given(request: MatrixHttpClient.HttpRequest<T>, response: T) {
|
||||||
coEvery { execute(request) } returns response
|
coEvery { execute(request) } returns response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun <T : Any> errors(request: MatrixHttpClient.HttpRequest<T>, cause: Throwable) {
|
||||||
|
coEvery { execute(request) } throws cause
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package fixture
|
||||||
|
|
||||||
|
import fake.FakeHttpResponse
|
||||||
|
import io.ktor.client.plugins.*
|
||||||
|
|
||||||
|
fun a404HttpError() = ClientRequestException(
|
||||||
|
FakeHttpResponse().apply { givenStatus(404) }.instance,
|
||||||
|
cachedResponseText = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
fun a403HttpError() = ClientRequestException(
|
||||||
|
FakeHttpResponse().apply { givenStatus(403) }.instance,
|
||||||
|
cachedResponseText = ""
|
||||||
|
)
|
|
@ -1,5 +1,14 @@
|
||||||
|
plugins { id 'java-test-fixtures' }
|
||||||
applyMatrixServiceModule(project)
|
applyMatrixServiceModule(project)
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation project(":core")
|
implementation project(":core")
|
||||||
|
|
||||||
|
kotlinTest(it)
|
||||||
|
kotlinFixtures(it)
|
||||||
|
testImplementation(testFixtures(project(":matrix:common")))
|
||||||
|
testImplementation(testFixtures(project(":matrix:matrix-http")))
|
||||||
|
testImplementation(testFixtures(project(":core")))
|
||||||
|
testFixturesImplementation(testFixtures(project(":matrix:common")))
|
||||||
|
testFixturesImplementation(testFixtures(project(":core")))
|
||||||
}
|
}
|
|
@ -10,6 +10,7 @@ import app.dapk.st.matrix.common.CredentialsStore
|
||||||
import app.dapk.st.matrix.common.HomeServerUrl
|
import app.dapk.st.matrix.common.HomeServerUrl
|
||||||
import app.dapk.st.matrix.common.UserId
|
import app.dapk.st.matrix.common.UserId
|
||||||
import app.dapk.st.matrix.room.internal.DefaultProfileService
|
import app.dapk.st.matrix.room.internal.DefaultProfileService
|
||||||
|
import app.dapk.st.matrix.room.internal.FetchMeUseCase
|
||||||
|
|
||||||
private val SERVICE_KEY = ProfileService::class
|
private val SERVICE_KEY = ProfileService::class
|
||||||
|
|
||||||
|
@ -31,8 +32,9 @@ fun MatrixServiceInstaller.installProfileService(
|
||||||
singletonFlows: SingletonFlows,
|
singletonFlows: SingletonFlows,
|
||||||
credentialsStore: CredentialsStore,
|
credentialsStore: CredentialsStore,
|
||||||
): InstallExtender<ProfileService> {
|
): InstallExtender<ProfileService> {
|
||||||
return this.install { (httpClient, _, _, logger) ->
|
return this.install { (httpClient, _, _, _) ->
|
||||||
SERVICE_KEY to DefaultProfileService(httpClient, logger, profileStore, singletonFlows, credentialsStore)
|
val fetchMeUseCase = FetchMeUseCase(httpClient, credentialsStore)
|
||||||
|
SERVICE_KEY to DefaultProfileService(profileStore, singletonFlows, fetchMeUseCase)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,22 +1,14 @@
|
||||||
package app.dapk.st.matrix.room.internal
|
package app.dapk.st.matrix.room.internal
|
||||||
|
|
||||||
import app.dapk.st.core.SingletonFlows
|
import app.dapk.st.core.SingletonFlows
|
||||||
import app.dapk.st.matrix.common.*
|
|
||||||
import app.dapk.st.matrix.http.MatrixHttpClient
|
|
||||||
import app.dapk.st.matrix.room.ProfileService
|
import app.dapk.st.matrix.room.ProfileService
|
||||||
import app.dapk.st.matrix.room.ProfileStore
|
import app.dapk.st.matrix.room.ProfileStore
|
||||||
import io.ktor.client.call.*
|
|
||||||
import io.ktor.client.plugins.*
|
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.serialization.SerialName
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
internal class DefaultProfileService(
|
internal class DefaultProfileService(
|
||||||
private val httpClient: MatrixHttpClient,
|
|
||||||
private val logger: MatrixLogger,
|
|
||||||
private val profileStore: ProfileStore,
|
private val profileStore: ProfileStore,
|
||||||
private val singletonFlows: SingletonFlows,
|
private val singletonFlows: SingletonFlows,
|
||||||
private val credentialsStore: CredentialsStore,
|
private val fetchMeUseCase: FetchMeUseCase,
|
||||||
) : ProfileService {
|
) : ProfileService {
|
||||||
|
|
||||||
override suspend fun me(forceRefresh: Boolean): ProfileService.Me {
|
override suspend fun me(forceRefresh: Boolean): ProfileService.Me {
|
||||||
|
@ -29,42 +21,7 @@ internal class DefaultProfileService(
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun fetchMe(): ProfileService.Me {
|
private suspend fun fetchMe(): ProfileService.Me {
|
||||||
val credentials = credentialsStore.credentials()!!
|
return fetchMeUseCase.fetchMe()
|
||||||
val userId = credentials.userId
|
|
||||||
return runCatching { httpClient.execute(profileRequest(userId)) }.fold(
|
|
||||||
onSuccess = {
|
|
||||||
ProfileService.Me(
|
|
||||||
userId,
|
|
||||||
it.displayName,
|
|
||||||
it.avatarUrl?.convertMxUrToUrl(credentials.homeServer)?.let { AvatarUrl(it) },
|
|
||||||
homeServerUrl = credentials.homeServer,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
onFailure = {
|
|
||||||
when {
|
|
||||||
it is ClientRequestException && it.response.status.value == 404 -> {
|
|
||||||
ProfileService.Me(
|
|
||||||
userId,
|
|
||||||
displayName = null,
|
|
||||||
avatarUrl = null,
|
|
||||||
homeServerUrl = credentials.homeServer,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> throw it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun profileRequest(userId: UserId) = MatrixHttpClient.HttpRequest.httpRequest<ApiMe>(
|
|
||||||
path = "_matrix/client/r0/profile/${userId.value}/",
|
|
||||||
method = MatrixHttpClient.Method.GET,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
internal data class ApiMe(
|
|
||||||
@SerialName("displayname") val displayName: String? = null,
|
|
||||||
@SerialName("avatar_url") val avatarUrl: MxUrl? = null,
|
|
||||||
)
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
package app.dapk.st.matrix.room.internal
|
||||||
|
|
||||||
|
import app.dapk.st.matrix.common.*
|
||||||
|
import app.dapk.st.matrix.http.MatrixHttpClient
|
||||||
|
import app.dapk.st.matrix.room.ProfileService
|
||||||
|
import io.ktor.client.plugins.*
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
internal class FetchMeUseCase(
|
||||||
|
private val httpClient: MatrixHttpClient,
|
||||||
|
private val credentialsStore: CredentialsStore,
|
||||||
|
) {
|
||||||
|
suspend fun fetchMe(): ProfileService.Me {
|
||||||
|
val credentials = credentialsStore.credentials()!!
|
||||||
|
val userId = credentials.userId
|
||||||
|
return runCatching { httpClient.execute(profileRequest(userId)) }.fold(
|
||||||
|
onSuccess = {
|
||||||
|
ProfileService.Me(
|
||||||
|
userId,
|
||||||
|
it.displayName,
|
||||||
|
it.avatarUrl?.convertMxUrToUrl(credentials.homeServer)?.let { AvatarUrl(it) },
|
||||||
|
homeServerUrl = credentials.homeServer,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
when {
|
||||||
|
it is ClientRequestException && it.response.status.value == 404 -> {
|
||||||
|
ProfileService.Me(
|
||||||
|
userId,
|
||||||
|
displayName = null,
|
||||||
|
avatarUrl = null,
|
||||||
|
homeServerUrl = credentials.homeServer,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> throw it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun profileRequest(userId: UserId) = MatrixHttpClient.HttpRequest.httpRequest<ApiMe>(
|
||||||
|
path = "_matrix/client/r0/profile/${userId.value}/",
|
||||||
|
method = MatrixHttpClient.Method.GET,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
internal data class ApiMe(
|
||||||
|
@SerialName("displayname") val displayName: String? = null,
|
||||||
|
@SerialName("avatar_url") val avatarUrl: MxUrl? = null,
|
||||||
|
)
|
|
@ -0,0 +1,78 @@
|
||||||
|
package app.dapk.st.matrix.room.internal
|
||||||
|
|
||||||
|
import app.dapk.st.matrix.room.ProfileService
|
||||||
|
import fake.FakeCredentialsStore
|
||||||
|
import fake.FakeMatrixHttpClient
|
||||||
|
import fixture.a403HttpError
|
||||||
|
import fixture.a404HttpError
|
||||||
|
import fixture.aUserCredentials
|
||||||
|
import fixture.aUserId
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.amshove.kluent.coInvoking
|
||||||
|
import org.amshove.kluent.shouldBeEqualTo
|
||||||
|
import org.amshove.kluent.shouldThrow
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
private val A_USER_CREDENTIALS = aUserCredentials()
|
||||||
|
private val A_USER_ID = aUserId()
|
||||||
|
private val AN_API_ME_RESPONSE = ApiMe(
|
||||||
|
displayName = "a display name",
|
||||||
|
avatarUrl = null,
|
||||||
|
)
|
||||||
|
private val AN_UNHANDLED_ERROR = RuntimeException()
|
||||||
|
|
||||||
|
class FetchMeUseCaseTest {
|
||||||
|
|
||||||
|
private val fakeHttpClient = FakeMatrixHttpClient()
|
||||||
|
private val fakeCredentialsStore = FakeCredentialsStore()
|
||||||
|
|
||||||
|
private val useCase = FetchMeUseCase(fakeHttpClient, fakeCredentialsStore)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when fetching me, then returns Me instance`() = runTest {
|
||||||
|
fakeCredentialsStore.givenCredentials().returns(A_USER_CREDENTIALS)
|
||||||
|
fakeHttpClient.given(profileRequest(aUserId()), AN_API_ME_RESPONSE)
|
||||||
|
|
||||||
|
val result = useCase.fetchMe()
|
||||||
|
|
||||||
|
result shouldBeEqualTo ProfileService.Me(
|
||||||
|
userId = A_USER_ID,
|
||||||
|
displayName = AN_API_ME_RESPONSE.displayName,
|
||||||
|
avatarUrl = null,
|
||||||
|
homeServerUrl = fakeCredentialsStore.credentials()!!.homeServer
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given unhandled error, when fetching me, then throws`() = runTest {
|
||||||
|
fakeCredentialsStore.givenCredentials().returns(A_USER_CREDENTIALS)
|
||||||
|
fakeHttpClient.errors(profileRequest(aUserId()), AN_UNHANDLED_ERROR)
|
||||||
|
|
||||||
|
coInvoking { useCase.fetchMe() } shouldThrow AN_UNHANDLED_ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given 403, when fetching me, then throws`() = runTest {
|
||||||
|
val error = a403HttpError()
|
||||||
|
fakeCredentialsStore.givenCredentials().returns(A_USER_CREDENTIALS)
|
||||||
|
fakeHttpClient.errors(profileRequest(aUserId()), error)
|
||||||
|
|
||||||
|
coInvoking { useCase.fetchMe() } shouldThrow error
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given 404, when fetching me, then returns Me instance with empty profile fields`() = runTest {
|
||||||
|
fakeCredentialsStore.givenCredentials().returns(A_USER_CREDENTIALS)
|
||||||
|
fakeHttpClient.errors(profileRequest(aUserId()), a404HttpError())
|
||||||
|
|
||||||
|
val result = useCase.fetchMe()
|
||||||
|
|
||||||
|
result shouldBeEqualTo ProfileService.Me(
|
||||||
|
userId = A_USER_ID,
|
||||||
|
displayName = null,
|
||||||
|
avatarUrl = null,
|
||||||
|
homeServerUrl = fakeCredentialsStore.credentials()!!.homeServer
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue