extracting me fetching logic to its own usecase and testing
This commit is contained in:
parent
c05fe5ac25
commit
7ca0224d7a
|
@ -4,6 +4,9 @@
|
|||
<inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
</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">
|
||||
<option name="composableFile" value="true" />
|
||||
</inspection_tool>
|
||||
|
@ -25,5 +28,6 @@
|
|||
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PrivatePropertyName" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
</profile>
|
||||
</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
|
||||
|
||||
import app.dapk.st.matrix.http.MatrixHttpClient
|
||||
import io.ktor.client.plugins.*
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.mockk
|
||||
|
||||
|
@ -8,4 +9,8 @@ class FakeMatrixHttpClient : MatrixHttpClient by mockk() {
|
|||
fun <T : Any> given(request: MatrixHttpClient.HttpRequest<T>, response: T) {
|
||||
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)
|
||||
|
||||
dependencies {
|
||||
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.UserId
|
||||
import app.dapk.st.matrix.room.internal.DefaultProfileService
|
||||
import app.dapk.st.matrix.room.internal.FetchMeUseCase
|
||||
|
||||
private val SERVICE_KEY = ProfileService::class
|
||||
|
||||
|
@ -31,8 +32,9 @@ fun MatrixServiceInstaller.installProfileService(
|
|||
singletonFlows: SingletonFlows,
|
||||
credentialsStore: CredentialsStore,
|
||||
): InstallExtender<ProfileService> {
|
||||
return this.install { (httpClient, _, _, logger) ->
|
||||
SERVICE_KEY to DefaultProfileService(httpClient, logger, profileStore, singletonFlows, credentialsStore)
|
||||
return this.install { (httpClient, _, _, _) ->
|
||||
val fetchMeUseCase = FetchMeUseCase(httpClient, credentialsStore)
|
||||
SERVICE_KEY to DefaultProfileService(profileStore, singletonFlows, fetchMeUseCase)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,22 +1,14 @@
|
|||
package app.dapk.st.matrix.room.internal
|
||||
|
||||
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.ProfileStore
|
||||
import io.ktor.client.call.*
|
||||
import io.ktor.client.plugins.*
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
internal class DefaultProfileService(
|
||||
private val httpClient: MatrixHttpClient,
|
||||
private val logger: MatrixLogger,
|
||||
private val profileStore: ProfileStore,
|
||||
private val singletonFlows: SingletonFlows,
|
||||
private val credentialsStore: CredentialsStore,
|
||||
private val fetchMeUseCase: FetchMeUseCase,
|
||||
) : ProfileService {
|
||||
|
||||
override suspend fun me(forceRefresh: Boolean): ProfileService.Me {
|
||||
|
@ -29,42 +21,7 @@ internal class DefaultProfileService(
|
|||
}
|
||||
|
||||
private 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
|
||||
}
|
||||
}
|
||||
)
|
||||
return fetchMeUseCase.fetchMe()
|
||||
}
|
||||
}
|
||||
|
||||
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