Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: migrate to Apollo4 #694

Merged
merged 6 commits into from
May 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ internal class KatanaKoverPlugin : Plugin<Project> {
"*.data.type",

// UI
"*.generated.resources",
"*.resources",
"*.shared.navigation",
"*.shared.resources",
"*.shared.strings",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,24 +60,32 @@ internal class KatanaMultiplatformDataRemotePlugin : Plugin<Project> {
context(Project)
private fun ApolloExtension.configureApollo() {
service("anilist") {
decapitalizeFields = true
generateAsInternal = true
generateDataBuilders = true
generateMethods = listOf("equalsHashCode")
packageName = fullPackageName
warnOnDeprecatedUsages = true

if (fullPackageName.contains(BASE_PACKAGE)) {
alwaysGenerateTypesMatching = listOf("Query", "User")
if (path == CORE_PROJECT) {
generateApolloMetadata = true
generateAsInternal = false
generateDataBuilders = true
schemaFiles.from(
file("src/commonMain/graphql/schema.graphqls"),
file("src/commonMain/graphql/extra.graphqls"),
)

introspection {
endpointUrl = "https://graphql.anilist.co"
schemaFile = project.file("src/commonMain/graphql/schema.graphqls")
schemaFile = 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"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ import org.koin.dsl.module
internal expect fun dataStoreModule(): Module

private val repositoriesModule = module {
singleOf(::SessionRepositoryImpl).bind<SessionRepository>()
singleOf(::SessionRepositoryImpl) bind SessionRepository::class
}

private val sourcesModule = module {
singleOf(::SessionLocalSourceImpl).bind<SessionLocalSource>()
singleOf(::SessionLocalSourceImpl) bind SessionLocalSource::class
}

val commonSessionDataModule = module {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import dev.alvr.katana.core.tests.koinExtension
import io.kotest.core.spec.style.FreeSpec
import io.kotest.core.test.TestCase
import io.kotest.matchers.equals.shouldBeEqual
import kotlin.time.Duration.Companion.milliseconds
import org.koin.test.KoinTest
import org.koin.test.inject

Expand All @@ -22,7 +21,7 @@ internal class SessionDataStoreTest : FreeSpec(), KoinTest {

init {
"initial session should equal to the Session class" {
dataStore.data.test(100.milliseconds) {
dataStore.data.test {
awaitItem() shouldBeEqual Session()
cancelAndConsumeRemainingEvents()
}
Expand All @@ -37,7 +36,7 @@ internal class SessionDataStoreTest : FreeSpec(), KoinTest {
)
}

data.test(100.milliseconds) {
data.test {
awaitItem() shouldBeEqual Session(
anilistToken = AnilistToken("token"),
isSessionActive = true,
Expand All @@ -48,7 +47,7 @@ internal class SessionDataStoreTest : FreeSpec(), KoinTest {
}

"corrupted dataStore should recreate again the file with initial values" {
corruptedDataStore.data.test(100.milliseconds) {
corruptedDataStore.data.test {
awaitItem() shouldBeEqual Session(anilistToken = AnilistToken("recreated"))
cancelAndConsumeRemainingEvents()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import dev.mokkery.mock
import dev.mokkery.verify
import dev.mokkery.verifySuspend
import io.kotest.core.spec.style.FreeSpec
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.flow.flowOf

internal class SessionRepositoryTest : FreeSpec() {
Expand All @@ -37,7 +36,7 @@ internal class SessionRepositoryTest : FreeSpec() {
false.right(),
)

repo.sessionActive.test(100.milliseconds) {
repo.sessionActive.test {
awaitItem().shouldBeRight(true)
awaitItem().shouldBeRight(true)
awaitItem().shouldBeRight(false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import dev.mokkery.mock
import dev.mokkery.verify
import dev.mokkery.verifySuspend
import io.kotest.core.spec.style.FreeSpec
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.flow.flowOf

internal class SessionLocalSourceTest : FreeSpec() {
Expand Down Expand Up @@ -80,7 +79,7 @@ internal class SessionLocalSourceTest : FreeSpec() {
"checking session active for ${session.anilistToken} and ${session.isSessionActive}" {
every { store.data } returns flowOf(session)

source.sessionActive.test(100.milliseconds) {
source.sessionActive.test {
awaitItem().shouldBeRight((session.anilistToken == null && session.isSessionActive).not())
cancelAndIgnoreRemainingEvents()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import dev.mokkery.mock
import dev.mokkery.verify
import io.kotest.core.spec.style.FreeSpec
import io.kotest.core.test.TestCase
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.flow.flowOf
import org.koin.test.KoinTest
import org.koin.test.inject
Expand All @@ -41,7 +40,7 @@ internal class ObserveActiveSessionUseCaseTest : FreeSpec(), KoinTest {

useCase()

useCase.flow.test(100.milliseconds) {
useCase.flow.test {
awaitItem().shouldBeRight(false)
awaitItem().shouldBeRight(true)
awaitItem().shouldBeRight(false)
Expand All @@ -58,7 +57,7 @@ internal class ObserveActiveSessionUseCaseTest : FreeSpec(), KoinTest {

useCase()

useCase.flow.test(100.milliseconds) {
useCase.flow.test {
awaitItem().shouldBeLeft(SessionFailure.CheckingActiveSession)
cancelAndConsumeRemainingEvents()
}
Expand Down
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)
}

kotlin {
sourceSets {
commonMain.dependencies {
Expand Down
4 changes: 2 additions & 2 deletions common/user/data/src/commonMain/graphql/QueryUserId.graphql
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
query UserIdQuery {
viewer: Viewer {
id
Viewer @nonnull {
id @nonnull
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
query UserInfoQuery {
user: Viewer {
name
avatar {
medium
Viewer @nonnull {
name @nonnull
avatar @nonnull {
large @nonnull
}
bannerImage
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,16 @@ import org.koin.dsl.bind
import org.koin.dsl.module

private val managersModule = module {
singleOf(::UserIdManagerImpl).bind<UserIdManager>()
singleOf(::UserIdManagerImpl) bind UserIdManager::class
}

private val repositoriesModule = module {
singleOf(::UserRepositoryImpl).bind<UserRepository>()
singleOf(::UserRepositoryImpl) bind UserRepository::class
}

private val sourcesModule = module {
singleOf(::UserIdRemoteSourceImpl).bind<UserIdRemoteSource>()
singleOf(::UserInfoRemoteSourceImpl).bind<UserInfoRemoteSource>()
singleOf(::UserIdRemoteSourceImpl) bind UserIdRemoteSource::class
singleOf(::UserInfoRemoteSourceImpl) bind UserInfoRemoteSource::class
}

val commonUserDataModule = module {
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 = viewer.id,
)
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 = viewer.name,
avatar = viewer.avatar.large,
banner = viewer.bannerImage,
)
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import dev.alvr.katana.common.user.data.UserIdQuery
import dev.alvr.katana.common.user.data.mappers.responses.invoke
import dev.alvr.katana.common.user.domain.failures.UserFailure
import dev.alvr.katana.core.common.catchUnit
import dev.alvr.katana.core.remote.executeOrThrow
import dev.alvr.katana.core.remote.toFailure

internal class UserIdRemoteSourceImpl(
Expand Down Expand Up @@ -36,6 +37,6 @@ internal class UserIdRemoteSourceImpl(
private suspend fun userIdHandler(policy: FetchPolicy) = client
.query(UserIdQuery())
.fetchPolicy(policy)
.execute()
.data()
.executeOrThrow()
.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 @@ -9,6 +9,7 @@ import com.apollographql.apollo3.cache.normalized.store
import com.apollographql.apollo3.testing.QueueTestNetworkTransport
import com.apollographql.apollo3.testing.enqueueTestResponse
import dev.alvr.katana.common.user.data.UserIdQuery
import dev.alvr.katana.core.remote.executeOrThrow
import dev.alvr.katana.core.remote.type.buildUser
import io.kotest.core.spec.style.FreeSpec
import io.kotest.matchers.booleans.shouldBeFalse
Expand All @@ -25,28 +26,28 @@ internal class ApolloUserIdManagerTest : FreeSpec() {
"retrieving the authenticated user" - {
"the first time should make a HTTP request" {
val query = UserIdQuery.Data {
this["viewer"] = buildUser {
this["id"] = 12345
viewer = buildUser {
id = 12345
}
}

client.enqueueTestResponse(UserIdQuery(), query)
client.query(UserIdQuery()).execute()
client.query(UserIdQuery()).executeOrThrow()
.also { res -> res.isFromCache.shouldBeFalse() }
.data.shouldNotBeNull()
.viewer.shouldNotBeNull() shouldBeEqual query.viewer.shouldNotBeNull()
}

"the second onwards it should be read from cache" {
val query = UserIdQuery.Data {
this["viewer"] = buildUser {
this["id"] = 12345
viewer = buildUser {
id = 12345
}
}

client.enqueueTestResponse(UserIdQuery(), query)
client.query(UserIdQuery()).execute() // Simulate HTTP request
client.query(UserIdQuery()).execute() // Next request is from cache
client.query(UserIdQuery()).executeOrThrow() // Simulate HTTP request
client.query(UserIdQuery()).executeOrThrow() // Next request is from cache
.also { res -> res.isFromCache.shouldBeTrue() }
.data.shouldNotBeNull()
.viewer.shouldNotBeNull() shouldBeEqual query.viewer.shouldNotBeNull()
Expand All @@ -55,21 +56,21 @@ internal class ApolloUserIdManagerTest : FreeSpec() {

"clearing the database" {
val query = UserIdQuery.Data {
this["viewer"] = buildUser {
this["id"] = 12345
viewer = buildUser {
id = 12345
}
}

client.enqueueTestResponse(UserIdQuery(), query)
client.query(UserIdQuery()).execute() // Simulate HTTP request
client.query(UserIdQuery()).execute() // Next request is from cache
client.query(UserIdQuery()).executeOrThrow() // Simulate HTTP request
client.query(UserIdQuery()).executeOrThrow() // Next request is from cache
.also { res -> res.isFromCache.shouldBeTrue() }
.data.shouldNotBeNull()
.viewer.shouldNotBeNull() shouldBeEqual query.viewer.shouldNotBeNull()

store.clearAll()

client.query(UserIdQuery()).execute() // No cache, HTTP request
client.query(UserIdQuery()).executeOrThrow() // No cache, HTTP request
.also { res -> res.isFromCache.shouldBeFalse() }
.data.shouldNotBeNull()
.viewer.shouldNotBeNull() shouldBeEqual query.viewer.shouldNotBeNull()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,10 @@ package dev.alvr.katana.common.user.data.mappers.responses

import dev.alvr.katana.common.user.data.UserIdQuery
import io.kotest.assertions.throwables.shouldNotThrowExactlyUnit
import io.kotest.assertions.throwables.shouldThrowExactlyUnit
import io.kotest.core.spec.style.FreeSpec
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)
userId()
} shouldHaveMessage "ViewerId is required."
}

"an UserIdQuery with a viewer should have same id" {
shouldNotThrowExactlyUnit<IllegalStateException> {
UserIdQuery.Data(viewer = UserIdQuery.Viewer(37_384))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import dev.mokkery.mock
import dev.mokkery.verify
import dev.mokkery.verifySuspend
import io.kotest.core.spec.style.FreeSpec
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.flow.emptyFlow

internal class UserRepositoryTest : FreeSpec() {
Expand Down Expand Up @@ -68,7 +67,7 @@ internal class UserRepositoryTest : FreeSpec() {
"observing userInfo" - {
"the server returns no data" {
every { userInfoSource.userInfo } returns emptyFlow()
repo.userInfo.test(100.milliseconds) { awaitComplete() }
repo.userInfo.test { awaitComplete() }
verify { userInfoSource.userInfo }
}
}
Expand Down
Loading
Loading