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

Fix #2818 #4358

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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 6 additions & 1 deletion apollo-integration/build.gradle.kts
Expand Up @@ -57,6 +57,11 @@ configure<ApolloExtension> {
rootPackageName.set("com.apollographql.apollo.integration.directives")
generateKotlinModels.set(true)
}
service("fragmentoverwrites") {
sourceFolder.set("com/apollographql/apollo/integration/fragmentoverwrites")
rootPackageName.set("com.apollographql.apollo.integration.fragmentoverwrites")
generateKotlinModels.set(true)
}
service("sealedclasses") {
sealedClassesForEnumsMatching.set(listOf(".*"))
generateKotlinModels.set(true)
Expand Down Expand Up @@ -86,4 +91,4 @@ tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
}
}
@@ -0,0 +1,15 @@
query Home {
home {
...sectionFragment
sectionA {
name
}
}
}

fragment sectionFragment on Home {
sectionA {
id
imageUrl
}
}
@@ -0,0 +1,13 @@
type Query {
home: Home!
}

type Home {
sectionA: SectionA
}

type SectionA {
id: String!
name: String!
imageUrl: String
}
@@ -0,0 +1,96 @@
package com.apollographql.apollo

import com.apollographql.apollo.Utils.immediateExecutor
import com.apollographql.apollo.Utils.immediateExecutorService
import com.apollographql.apollo.Utils.mockResponse
import com.apollographql.apollo.api.Operation
import com.apollographql.apollo.api.ResponseField
import com.apollographql.apollo.cache.normalized.CacheKey
import com.apollographql.apollo.cache.normalized.CacheKeyResolver
import com.apollographql.apollo.cache.normalized.lru.EvictionPolicy
import com.apollographql.apollo.cache.normalized.lru.LruNormalizedCacheFactory
import com.apollographql.apollo.coroutines.await
import com.apollographql.apollo.fetcher.ApolloResponseFetchers
import com.apollographql.apollo.integration.fragmentoverwrites.HomeQuery
import com.apollographql.apollo.integration.fragmentoverwrites.fragment.SectionFragment
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.runBlocking
import okhttp3.Dispatcher
import okhttp3.OkHttpClient
import okhttp3.mockwebserver.MockWebServer
import org.junit.Before
import org.junit.Rule
import org.junit.Test

class FragmentOverwritesTest {

private lateinit var apolloClient: ApolloClient

@get:Rule
val server = MockWebServer()

private val cacheKeyResolver = object : CacheKeyResolver() {
override fun fromFieldArguments(field: ResponseField, variables: Operation.Variables): CacheKey {
return CacheKey.NO_KEY
}

override fun fromFieldRecordSet(field: ResponseField, recordSet: Map<String, Any>): CacheKey {
return (recordSet["id"] as? String)?.let { CacheKey.from(it) } ?: CacheKey.NO_KEY
}
}

@Before
fun setup() {
val okHttpClient = OkHttpClient.Builder()
.dispatcher(Dispatcher(immediateExecutorService()))
.build()

apolloClient = ApolloClient.builder()
.normalizedCache(LruNormalizedCacheFactory(EvictionPolicy.NO_EVICTION), cacheKeyResolver)
.okHttpClient(okHttpClient)
.dispatcher(immediateExecutor())
.serverUrl(server.url("/"))
.build()
}

@Test
fun `doesn't overwrite cache entries when using fragments`() {

server.enqueue(mockResponse("FragmentOverwritesTestHomeQueryResponse.json"))

runBlocking {
val networkResponse = apolloClient.query(HomeQuery()).await()

assertThat(networkResponse.data?.home?.sectionA?.name).isEqualTo("initialSectionName")
assertThat(networkResponse.data?.home?.fragments?.sectionFragment?.sectionA?.imageUrl).isEqualTo("initialUrl")

apolloClient.apolloStore.writeAndPublish(
HomeQuery(),
HomeQuery.Data(
HomeQuery.Home(
sectionA = HomeQuery.SectionA(
name = "modifiedSectionName"
),
fragments = HomeQuery.Home.Fragments(
sectionFragment = SectionFragment(
sectionA = SectionFragment.SectionA(
id = "section-id",
imageUrl = "modifiedUrl",
),
)
)
)
)
).execute()

val cacheResponse = apolloClient.query(HomeQuery())
.toBuilder()
.responseFetcher(ApolloResponseFetchers.CACHE_ONLY)
.build()
.await()

assertThat(cacheResponse.data?.home?.sectionA?.name).isEqualTo("modifiedSectionName")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you also add a check that the fragment is correctly written?

Suggested change
assertThat(cacheResponse.data?.home?.sectionA?.name).isEqualTo("modifiedSectionName")
assertThat(cacheResponse.data?.home?.sectionA?.name).isEqualTo("modifiedSectionName")
assertThat(cacheResponse.data?.home?.fragments?.sectionFragment?.sectionA?.imageUrl).isEqualTo("modifiedUrl")

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 0c04a41. Thanks!

assertThat(cacheResponse.data?.home?.fragments?.sectionFragment?.sectionA?.imageUrl).isEqualTo("modifiedUrl")
}
}
}
@@ -0,0 +1,13 @@
{
"data": {
"home": {
"__typename": "Home",
"sectionA": {
"__typename": "SectionA",
"name": "initialSectionName",
"id": "section-id",
"imageUrl": "initialUrl"
}
}
}
}
@@ -1,6 +1,5 @@
package com.apollographql.apollo.internal.response

import com.apollographql.apollo.api.CustomTypeAdapter
import com.apollographql.apollo.api.Operation
import com.apollographql.apollo.api.ResponseField
import com.apollographql.apollo.api.ScalarType
Expand Down Expand Up @@ -47,7 +46,22 @@ class RealResponseWriter(private val operationVariables: Operation.Variables, pr
}
val nestedResponseWriter = RealResponseWriter(operationVariables, scalarTypeAdapters)
marshaller.marshal(nestedResponseWriter)
buffer[field.responseName] = FieldDescriptor(field, nestedResponseWriter.buffer)
buffer[field.responseName] = deepMergeObjects(field, buffer[field.responseName]?.value, nestedResponseWriter.buffer)
}

private fun deepMergeObjects(field: ResponseField, oldValue: Any?, newValue: Map<String, FieldDescriptor>): FieldDescriptor {
return if (oldValue == null || oldValue !is Map<*, *>) {
FieldDescriptor(field, newValue)
} else {
val oldMap = oldValue as Map<String, FieldDescriptor>

val mergedCommonValues = oldMap.keys.intersect(newValue.keys)
.filter { newValue[it]?.value is Map<*, *> }
.map { deepMergeObjects(oldMap[it]!!.field, oldMap[it]?.value, newValue[it]!!.value as Map<String, FieldDescriptor>) }
.associateBy { it.field.responseName }

FieldDescriptor(field, oldMap + newValue + mergedCommonValues)
}
}

override fun writeFragment(marshaller: ResponseFieldMarshaller?) {
Expand Down Expand Up @@ -226,4 +240,4 @@ class RealResponseWriter(private val operationVariables: Operation.Variables, pr
}
}

}
}