Skip to content

Commit

Permalink
feat: migrate to Apollo4
Browse files Browse the repository at this point in the history
  • Loading branch information
alvr committed Apr 17, 2024
1 parent d9c0db8 commit 71792d9
Show file tree
Hide file tree
Showing 21 changed files with 58 additions and 92 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -61,23 +61,24 @@ internal class KatanaMultiplatformDataRemotePlugin : Plugin<Project> {
private fun ApolloExtension.configureApollo() {
service("anilist") {
generateAsInternal = true
generateDataBuilders = true
packageName = fullPackageName

if (fullPackageName.contains(BASE_PACKAGE)) {
alwaysGenerateTypesMatching = listOf("Query", "User")
if (path == CORE_PROJECT) {
generateApolloMetadata = true
generateAsInternal = false
generateDataBuilders = true

introspection {
endpointUrl = "https://graphql.anilist.co"
schemaFile = project.file("src/commonMain/graphql/schema.graphqls")
}
} else {
dependsOn(project(CORE_PROJECT))
}
}
}

private companion object {
const val BASE_PACKAGE = ".remote"
const val CORE_PROJECT = ":core:remote"
}
}
4 changes: 0 additions & 4 deletions common/user/data/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@ plugins {
id("katana.multiplatform.data.remote")
}

dependencies {
apolloMetadata(projects.core.remote)
}

katanaMultiplatform {
commonMainDependencies {
implementation(projects.core.common)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ package dev.alvr.katana.common.user.data.mappers.responses
import dev.alvr.katana.common.user.data.UserIdQuery
import dev.alvr.katana.common.user.domain.models.UserId

internal operator fun UserIdQuery.Data?.invoke(): UserId = UserId(
id = checkNotNull(this?.viewer?.id) { "ViewerId is required." },
internal operator fun UserIdQuery.Data.invoke(): UserId = UserId(
id = checkNotNull(viewer?.id) { "ViewerId is required." },
)
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package dev.alvr.katana.common.user.data.mappers.responses
import dev.alvr.katana.common.user.data.UserInfoQuery
import dev.alvr.katana.common.user.domain.models.UserInfo

internal operator fun UserInfoQuery.Data?.invoke() = UserInfo(
username = this?.user?.name.orEmpty(),
avatar = this?.user?.avatar?.medium.orEmpty(),
banner = this?.user?.bannerImage.orEmpty(),
internal operator fun UserInfoQuery.Data.invoke() = UserInfo(
username = user?.name.orEmpty(),
avatar = user?.avatar?.medium.orEmpty(),
banner = user?.bannerImage.orEmpty(),
)
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,5 @@ internal class UserIdRemoteSourceImpl(
.query(UserIdQuery())
.fetchPolicy(policy)
.execute()
.data()
.dataAssertNoErrors()
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ internal class UserInfoRemoteSourceImpl(
client.query(UserInfoQuery())
.fetchPolicy(FetchPolicy.CacheAndNetwork)
.watch()
.map { res -> res.data().right() as Either<Failure, UserInfo> }
.map { res -> res.dataAssertNoErrors().right() as Either<Failure, UserInfo> }
.catch { error ->
Logger.e(error) { "Was not possible to get the user info" }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,6 @@ import io.kotest.matchers.ints.shouldBeExactly
import io.kotest.matchers.throwable.shouldHaveMessage

internal class UserIdMapperTest : FreeSpec({
"null UserIdQuery Data" {
shouldThrowExactlyUnit<IllegalStateException> {
val userId: UserIdQuery.Data? = null
userId()
} shouldHaveMessage "ViewerId is required."
}

"UserIdQuery with null viewer" {
shouldThrowExactlyUnit<IllegalStateException> {
val userId = UserIdQuery.Data(viewer = null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,20 @@ import kotlin.time.Duration.Companion.milliseconds

@OptIn(ApolloExperimental::class)
internal class UserInfoRemoteSourceTest : FreeSpec() {
private val client = ApolloClient.Builder().networkTransport(MapTestNetworkTransport()).build()
private val source: UserInfoRemoteSource = UserInfoRemoteSourceImpl(client)
private lateinit var client: ApolloClient
private lateinit var source: UserInfoRemoteSource

init {
beforeTest {
client = ApolloClient.Builder().networkTransport(MapTestNetworkTransport()).build()
source = UserInfoRemoteSourceImpl(client)
}

"observing the user info" - {
"the server returns no data" {
client.registerTestResponse(UserInfoQuery())
source.userInfo.test(100.milliseconds) {
awaitItem().shouldBeRight(userInfoNoData)
awaitItem().shouldBeLeft(UserFailure.GettingUserInfo)
cancelAndIgnoreRemainingEvents()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import com.apollographql.apollo3.exception.ApolloHttpException
import com.apollographql.apollo3.exception.ApolloNetworkException
import com.apollographql.apollo3.exception.ApolloParseException
import com.apollographql.apollo3.exception.CacheMissException
import com.apollographql.apollo3.exception.DefaultApolloException
import com.apollographql.apollo3.exception.HttpCacheMissException
import com.apollographql.apollo3.exception.JsonDataException
import com.apollographql.apollo3.exception.JsonEncodingException
import com.apollographql.apollo3.exception.NoDataException
import dev.alvr.katana.core.domain.failures.Failure

fun <A, B> Either<A, B>.optional() = Optional.presentIfNotNull(getOrNull())
Expand All @@ -20,11 +22,13 @@ fun Throwable.toFailure(
unknown: Failure = Failure.Unknown,
): Failure = when (this) {
is ApolloHttpException,
is ApolloNetworkException -> network
is ApolloNetworkException,
is DefaultApolloException -> network
is CacheMissException,
is HttpCacheMissException -> cache
is ApolloParseException,
is JsonDataException,
is JsonEncodingException -> response
is JsonEncodingException,
is NoDataException -> response
else -> unknown
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import io.kotest.core.spec.IsolationMode
import io.kotest.core.test.AssertionMode

object KotestProjectConfig : AbstractProjectConfig() {
private const val NUM_THREADS = 1
private const val NUM_THREADS = 2

override val assertionMode = AssertionMode.Warn
override val coroutineDebugProbes = true
Expand Down
4 changes: 0 additions & 4 deletions features/account/data/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@ plugins {
id("katana.multiplatform.data.remote")
}

dependencies {
apolloMetadata(projects.core.remote)
}

katanaMultiplatform {
commonMainDependencies {
implementation(projects.core.remote)
Expand Down
4 changes: 0 additions & 4 deletions features/explore/data/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@ plugins {
id("katana.multiplatform.data.remote")
}

dependencies {
apolloMetadata(projects.core.remote)
}

katanaMultiplatform {
commonMainDependencies {
implementation(projects.core.remote)
Expand Down
4 changes: 0 additions & 4 deletions features/lists/data/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@ plugins {
id("katana.multiplatform.data.remote")
}

dependencies {
apolloMetadata(projects.core.remote)
}

katanaMultiplatform {
commonMainDependencies {
implementation(projects.common.user.domain)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach

internal class CommonListsRemoteSourceImpl(
private val client: ApolloClient,
Expand All @@ -47,8 +48,9 @@ internal class CommonListsRemoteSourceImpl(
.query(MediaListCollectionQuery(userId.getId().optional(), type))
.fetchPolicyInterceptor(reloadInterceptor)
.watch()
.onEach { println(it) }
.distinctUntilChanged()
.map { res -> MediaCollection(res.data?.mediaList<T>(type).orEmpty()).right() }
.map { res -> MediaCollection(res.dataAssertNoErrors.mediaList<T>(type)).right() }
.distinctUntilChanged()
.catch { error ->
Logger.e(error) { "There was an error collecting the lists" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import io.kotest.matchers.shouldNotBe
import dev.alvr.katana.features.lists.data.fragment.MediaEntry as MediaEntryFragment

internal class MediaEntryMapperTest : FreeSpec({
MediaFormat.knownValues()
MediaFormat.knownEntries
.forEach { format ->
"MediaFormat $format should not be ${CommonMediaEntry.Format.UNKNOWN}" {
MediaEntryFragment(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,7 @@ internal val mediaListMock = MediaList(
updatedAt = Arb.dateTimeTz().orNull().next(),
)

internal val apolloErrorMock = Error(
message = Arb.string().next(),
locations = emptyList(),
path = emptyList(),
extensions = emptyMap(),
nonStandardFields = emptyMap(),
)
internal val apolloErrorMock = Error.Builder(Arb.string().next()).build()

private fun Arb.Companion.date() = arbitrary {
Date(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ internal class ApolloListsRemoteSourceTest : FreeSpec() {
)

animeSource.animeCollection.test(100.milliseconds) {
awaitItem().shouldBeRight().lists.shouldBeEmpty()
awaitItem().shouldBeLeft(ListsFailure.GetMediaCollection)
awaitComplete()
}

Expand Down Expand Up @@ -580,7 +580,7 @@ internal class ApolloListsRemoteSourceTest : FreeSpec() {
)

mangaSource.mangaCollection.test(100.milliseconds) {
awaitItem().shouldBeRight().lists.shouldBeEmpty()
awaitItem().shouldBeLeft(ListsFailure.GetMediaCollection)
awaitComplete()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,20 @@ import com.apollographql.apollo3.ApolloClient
import com.apollographql.apollo3.annotations.ApolloExperimental
import com.apollographql.apollo3.api.ApolloResponse
import com.apollographql.apollo3.interceptor.ApolloInterceptor
import com.apollographql.apollo3.mockserver.MockResponse
import com.apollographql.apollo3.mockserver.MockServer
import com.apollographql.apollo3.mockserver.enqueueError
import com.apollographql.apollo3.mockserver.enqueueString
import com.apollographql.apollo3.testing.QueueTestNetworkTransport
import com.apollographql.apollo3.testing.enqueueTestResponse
import com.benasher44.uuid.uuid4
import dev.alvr.katana.common.user.domain.managers.UserIdManager
import dev.alvr.katana.core.domain.failures.Failure
import dev.alvr.katana.core.remote.type.MediaType
import dev.alvr.katana.core.remote.type.buildMediaListCollection
import dev.alvr.katana.core.tests.shouldBeLeft
import dev.alvr.katana.core.tests.shouldBeRight
import dev.alvr.katana.features.lists.data.MediaListCollectionQuery
import dev.alvr.katana.features.lists.data.apolloErrorMock
import dev.alvr.katana.features.lists.data.enqueueResponse
import dev.alvr.katana.features.lists.data.mediaListCollectionQueryMock
import dev.alvr.katana.features.lists.data.mediaListEntriesMutationMock
import dev.alvr.katana.features.lists.data.mediaListMock
Expand All @@ -31,7 +32,6 @@ import dev.mokkery.mock
import dev.mokkery.verifySuspend
import io.kotest.core.spec.style.FreeSpec
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds

@OptIn(ApolloExperimental::class)
internal class CommonListsRemoteSourceTest : FreeSpec() {
Expand All @@ -50,12 +50,16 @@ internal class CommonListsRemoteSourceTest : FreeSpec() {
val response = ApolloResponse.Builder(
operation = mediaListCollectionQueryMock,
requestUuid = uuid4(),
data = data,
).build()
).data(data).build()
client.enqueueTestResponse(response)

source.getMediaCollection<MediaEntry>(type).test(100.milliseconds) {
awaitItem().shouldBeRight(MediaCollection(emptyList()))
if (data == null) {
awaitItem().shouldBeLeft(ListsFailure.GetMediaCollection)
} else {
awaitItem().shouldBeRight(MediaCollection(emptyList()))
}

cancelAndIgnoreRemainingEvents()
}
verifySuspend { userIdManager.getId() }
Expand All @@ -66,12 +70,11 @@ internal class CommonListsRemoteSourceTest : FreeSpec() {
val response = ApolloResponse.Builder(
operation = mediaListCollectionQueryMock,
requestUuid = uuid4(),
data = data,
).errors(listOf(apolloErrorMock)).build()
).data(data).errors(listOf(apolloErrorMock)).build()
client.enqueueTestResponse(response)

source.getMediaCollection<MediaEntry>(type).test(100.milliseconds) {
awaitItem().shouldBeRight(MediaCollection(emptyList()))
awaitItem().shouldBeLeft(Failure.Unknown)
cancelAndIgnoreRemainingEvents()
}
verifySuspend { userIdManager.getId() }
Expand All @@ -95,14 +98,14 @@ internal class CommonListsRemoteSourceTest : FreeSpec() {
reloadInterceptor,
)

afterSpec { mockServer.stop() }
afterSpec { mockServer.close() }

badClient().forEach { (type, action) ->
mockServer.badClient().forEach { (type, enqueueAction) ->
"a HTTP error occurs" {
everySuspend { userIdManager.getId() } returns 37_384.right()
mockServer.enqueueResponse(action)
enqueueAction()

source.getMediaCollection<MediaEntry>(type).test(5.seconds) {
source.getMediaCollection<MediaEntry>(type).test(250.milliseconds) {
awaitItem().shouldBeLeft(ListsFailure.GetMediaCollection)
cancelAndIgnoreRemainingEvents()
}
Expand All @@ -124,24 +127,20 @@ internal class CommonListsRemoteSourceTest : FreeSpec() {
add(null)
add(empty)
}
val types = MediaType.knownValues()
val types = MediaType.knownEntries

return buildList {
values.forEach { v -> types.forEach { t -> add(v to t) } }
}
}

private fun badClient(): List<Pair<MediaType, (MockResponse.Builder.() -> Unit)>> {
fun apolloCommand(
block: MockResponse.Builder.() -> Unit,
): MockResponse.Builder.() -> Unit = { MockResponse.Builder().apply(block) }

private fun MockServer.badClient(): List<Pair<MediaType, (() -> Unit)>> {
val commands = buildList {
add(apolloCommand { statusCode(500) })
add(apolloCommand { body("Malformed body") })
add(apolloCommand { body("""{"data": {"random": 42}}""") })
add { enqueueError(500) }
add { enqueueString("Malformed body") }
add { enqueueString("""{"data": {"random": 42}}""") }
}
val types = MediaType.knownValues()
val types = MediaType.knownEntries

return buildList {
commands.forEach { c -> types.forEach { t -> add(t to c) } }
Expand Down

This file was deleted.

4 changes: 0 additions & 4 deletions features/social/data/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@ plugins {
id("katana.multiplatform.data.remote")
}

dependencies {
apolloMetadata(projects.core.remote)
}

katanaMultiplatform {
commonMainDependencies {
implementation(projects.core.remote)
Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ android = "8.3.2"
androidx-activity = "1.8.2"
androidx-lifecycle = "2.7.0"
androidx-splashscreen = "1.0.1"
apollo = "3.8.3"
apollo = "4.0.0-beta.5"
arrow = "1.2.4"
buildconfig = "5.3.5"
complete-kotlin = "1.1.0"
Expand Down

0 comments on commit 71792d9

Please sign in to comment.