Skip to content

Commit 2b80a6e

Browse files
smyrickmykevinjung
authored andcommitted
add more test coverage for federation (ExpediaGroup#327)
1 parent a85ba6f commit 2b80a6e

File tree

8 files changed

+300
-34
lines changed

8 files changed

+300
-34
lines changed

graphql-kotlin-federation/src/main/kotlin/com/expediagroup/graphql/federation/execution/EntityResolver.kt

Lines changed: 1 addition & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package com.expediagroup.graphql.federation.execution
22

3-
import com.expediagroup.graphql.federation.exception.FederatedRequestFailure
4-
import com.expediagroup.graphql.federation.exception.InvalidFederatedRequest
53
import graphql.GraphQLError
64
import graphql.execution.DataFetcherResult
75
import graphql.schema.DataFetcher
@@ -36,7 +34,7 @@ open class EntityResolver(private val federatedTypeRegistry: FederatedTypeRegist
3634
val errors = mutableListOf<GraphQLError>()
3735
indexedBatchRequestsByType.map { (typeName, indexedRequests) ->
3836
async {
39-
resolveType(typeName, indexedRequests)
37+
resolveType(typeName, indexedRequests, federatedTypeRegistry)
4038
}
4139
}.awaitAll()
4240
.flatten()
@@ -56,35 +54,4 @@ open class EntityResolver(private val federatedTypeRegistry: FederatedTypeRegist
5654
.build()
5755
}.asCompletableFuture()
5856
}
59-
60-
private suspend fun resolveType(typeName: String, indexedRequests: List<IndexedValue<Map<String, Any>>>): List<Pair<Int, Any?>> {
61-
val indices = indexedRequests.map { it.index }
62-
val batch = indexedRequests.map { it.value }
63-
val results = resolveBatch(typeName, batch)
64-
return if (results.size != indices.size) {
65-
indices.map {
66-
it to FederatedRequestFailure("Federation batch request for $typeName generated different number of results than requested, representations=${indices.size}, results=${results.size}")
67-
}
68-
} else {
69-
indices.zip(results)
70-
}
71-
}
72-
73-
@Suppress("TooGenericExceptionCaught")
74-
private suspend fun resolveBatch(typeName: String, batch: List<Map<String, Any>>): List<Any?> {
75-
val resolver = federatedTypeRegistry.getFederatedResolver(typeName)
76-
return if (resolver != null) {
77-
try {
78-
resolver.resolve(batch)
79-
} catch (e: Exception) {
80-
batch.map {
81-
FederatedRequestFailure("Exception was thrown while trying to resolve federated type, representation=$it", e)
82-
}
83-
}
84-
} else {
85-
batch.map {
86-
InvalidFederatedRequest("Unable to resolve federated type, representation=$it")
87-
}
88-
}
89-
}
9057
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.expediagroup.graphql.federation.execution
2+
3+
import com.expediagroup.graphql.federation.exception.FederatedRequestFailure
4+
import com.expediagroup.graphql.federation.exception.InvalidFederatedRequest
5+
6+
internal suspend fun resolveType(typeName: String, indexedRequests: List<IndexedValue<Map<String, Any>>>, federatedTypeRegistry: FederatedTypeRegistry): List<Pair<Int, Any?>> {
7+
val indices = indexedRequests.map { it.index }
8+
val batch = indexedRequests.map { it.value }
9+
val results = resolveBatch(typeName, batch, federatedTypeRegistry)
10+
return if (results.size != indices.size) {
11+
indices.map {
12+
it to FederatedRequestFailure("Federation batch request for $typeName generated different number of results than requested, representations=${indices.size}, results=${results.size}")
13+
}
14+
} else {
15+
indices.zip(results)
16+
}
17+
}
18+
19+
@Suppress("TooGenericExceptionCaught")
20+
private suspend fun resolveBatch(typeName: String, batch: List<Map<String, Any>>, federatedTypeRegistry: FederatedTypeRegistry): List<Any?> {
21+
val resolver = federatedTypeRegistry.getFederatedResolver(typeName)
22+
return if (resolver != null) {
23+
try {
24+
resolver.resolve(batch)
25+
} catch (e: Exception) {
26+
batch.map {
27+
FederatedRequestFailure("Exception was thrown while trying to resolve federated type, representation=$it", e)
28+
}
29+
}
30+
} else {
31+
batch.map {
32+
InvalidFederatedRequest("Unable to resolve federated type, representation=$it")
33+
}
34+
}
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.expediagroup.graphql.federation.exception
2+
3+
import graphql.ErrorType
4+
import graphql.language.SourceLocation
5+
import org.junit.jupiter.api.Test
6+
import kotlin.test.assertEquals
7+
import kotlin.test.assertNull
8+
9+
internal class FederatedRequestFailureTest {
10+
11+
private val simpleFailure = FederatedRequestFailure("myErrorMessage")
12+
13+
@Test
14+
fun getMessage() {
15+
assertEquals(expected = "myErrorMessage", actual = simpleFailure.message)
16+
}
17+
18+
@Test
19+
fun getErrorType() {
20+
assertEquals(expected = ErrorType.DataFetchingException, actual = simpleFailure.errorType)
21+
}
22+
23+
@Test
24+
fun getLocations() {
25+
assertEquals(expected = listOf(SourceLocation(-1, -1)), actual = simpleFailure.locations)
26+
}
27+
28+
@Test
29+
fun getExtensions() {
30+
assertNull(simpleFailure.extensions)
31+
32+
val exception = Exception("custom error")
33+
val failure = FederatedRequestFailure("newErrorMessage", exception)
34+
assertEquals(expected = mapOf("error" to exception), actual = failure.extensions)
35+
}
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.expediagroup.graphql.federation.execution
2+
3+
import io.mockk.coEvery
4+
import io.mockk.coVerify
5+
import io.mockk.mockk
6+
import kotlinx.coroutines.runBlocking
7+
import org.junit.jupiter.api.Test
8+
import kotlin.test.assertEquals
9+
import kotlin.test.assertTrue
10+
11+
internal class ResolversKtTest {
12+
13+
@Test
14+
fun `resolveBatch should call the resolver from the registry`() {
15+
val indexedValue = mapOf<String, Any>()
16+
val indexedRequests: List<IndexedValue<Map<String, Any>>> = listOf(IndexedValue(7, indexedValue))
17+
val mockResolver: FederatedTypeResolver<*> = mockk()
18+
coEvery { mockResolver.resolve(any()) } returns listOf("foo")
19+
val registry = FederatedTypeRegistry(mapOf("MyType" to mockResolver))
20+
21+
runBlocking {
22+
val result = resolveType("MyType", indexedRequests, registry)
23+
assertTrue(result.isNotEmpty())
24+
assertEquals(expected = 7 to "foo", actual = result.first())
25+
coVerify(exactly = 1) { mockResolver.resolve(any()) }
26+
}
27+
}
28+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package com.expediagroup.graphql.federation.types
2+
3+
import graphql.AssertException
4+
import graphql.language.ArrayValue
5+
import graphql.language.BooleanValue
6+
import graphql.language.EnumValue
7+
import graphql.language.FloatValue
8+
import graphql.language.IntValue
9+
import graphql.language.NullValue
10+
import graphql.language.ObjectField
11+
import graphql.language.ObjectValue
12+
import graphql.language.StringValue
13+
import graphql.language.Value
14+
import org.junit.jupiter.api.Test
15+
import java.math.BigDecimal
16+
import java.math.BigInteger
17+
import kotlin.test.assertEquals
18+
import kotlin.test.assertFailsWith
19+
import kotlin.test.assertNull
20+
21+
internal class AnyTest {
22+
23+
@Test
24+
fun `_Any scalar should allow all types`() {
25+
val coercing = ANY_SCALAR_TYPE.coercing
26+
27+
assertNull(coercing.parseLiteral(NullValue.Null))
28+
assertEquals(expected = BigDecimal.ONE, actual = coercing.parseLiteral(FloatValue(BigDecimal.ONE)))
29+
assertEquals(expected = "hello", actual = coercing.parseLiteral(StringValue("hello")))
30+
assertEquals(expected = BigInteger.ONE, actual = coercing.parseLiteral(IntValue(BigInteger.ONE)))
31+
assertEquals(expected = true, actual = coercing.parseLiteral(BooleanValue(true)))
32+
assertEquals(expected = "MyEnum", actual = coercing.parseLiteral(EnumValue("MyEnum")))
33+
34+
val listValues = listOf<Value<IntValue>>(IntValue(BigInteger.TEN))
35+
assertEquals(expected = listOf(BigInteger.TEN), actual = coercing.parseLiteral(ArrayValue(listValues)))
36+
37+
val objectValue = ObjectValue(listOf(ObjectField("myName", IntValue(BigInteger.ONE))))
38+
assertEquals(expected = mapOf("myName" to BigInteger.ONE), actual = coercing.parseLiteral(objectValue))
39+
}
40+
41+
@Test
42+
fun `_Any scalar should throw exception on invalid graphql value`() {
43+
val coercing = ANY_SCALAR_TYPE.coercing
44+
45+
assertFailsWith(AssertException::class) {
46+
coercing.parseLiteral(1)
47+
}
48+
}
49+
50+
@Test
51+
fun `_Any scalar serialize should just return`() {
52+
val coercing = ANY_SCALAR_TYPE.coercing
53+
54+
assertEquals(expected = 1, actual = coercing.serialize(1))
55+
}
56+
57+
@Test
58+
fun `_Any scalar parseValue should just return`() {
59+
val coercing = ANY_SCALAR_TYPE.coercing
60+
61+
assertEquals(expected = 1, actual = coercing.parseValue(1))
62+
}
63+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.expediagroup.graphql.federation.types
2+
3+
import graphql.schema.GraphQLTypeUtil
4+
import graphql.schema.GraphQLUnionType
5+
import org.junit.jupiter.api.Test
6+
import kotlin.test.assertEquals
7+
import kotlin.test.assertFailsWith
8+
import kotlin.test.assertFalse
9+
import kotlin.test.assertNotNull
10+
11+
internal class EntityTest {
12+
13+
@Test
14+
fun `generateEntityFieldDefinition should fail on empty set`() {
15+
assertFailsWith(graphql.AssertException::class) {
16+
generateEntityFieldDefinition(emptySet())
17+
}
18+
}
19+
20+
@Test
21+
fun `generateEntityFieldDefinition should return a valid type on a single set`() {
22+
val result = generateEntityFieldDefinition(setOf("MyType"))
23+
assertNotNull(result)
24+
assertEquals(expected = "_entities", actual = result.name)
25+
assertFalse(result.description.isNullOrEmpty())
26+
assertEquals(expected = 1, actual = result.arguments.size)
27+
28+
val graphQLUnionType = GraphQLTypeUtil.unwrapType(result.type).last() as? GraphQLUnionType
29+
30+
assertNotNull(graphQLUnionType)
31+
assertEquals(expected = "_Entity", actual = graphQLUnionType.name)
32+
assertEquals(expected = 1, actual = graphQLUnionType.types.size)
33+
assertEquals(expected = "MyType", actual = graphQLUnionType.types.first().name)
34+
}
35+
36+
@Test
37+
fun `generateEntityFieldDefinition should return a valid type on a multiple values`() {
38+
val result = generateEntityFieldDefinition(setOf("MyType", "MySecondType"))
39+
val graphQLUnionType = GraphQLTypeUtil.unwrapType(result.type).last() as? GraphQLUnionType
40+
41+
assertNotNull(graphQLUnionType)
42+
assertEquals(expected = 2, actual = graphQLUnionType.types.size)
43+
assertEquals(expected = "MyType", actual = graphQLUnionType.types.first().name)
44+
assertEquals(expected = "MySecondType", actual = graphQLUnionType.types[1].name)
45+
}
46+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package com.expediagroup.graphql.federation.types
2+
3+
import com.expediagroup.graphql.federation.directives.FieldSet
4+
import graphql.language.IntValue
5+
import graphql.language.StringValue
6+
import graphql.schema.Coercing
7+
import graphql.schema.CoercingParseLiteralException
8+
import graphql.schema.CoercingSerializeException
9+
import org.junit.jupiter.api.Test
10+
import java.math.BigInteger
11+
import kotlin.test.assertEquals
12+
import kotlin.test.assertFailsWith
13+
import kotlin.test.assertTrue
14+
15+
internal class FieldSetTest {
16+
private val coercing: Coercing<Any, Any> = FIELD_SET_SCALAR_TYPE.coercing
17+
18+
@Test
19+
fun `serialize should throw exception when not a FieldSet`() {
20+
assertFailsWith(CoercingSerializeException::class) {
21+
coercing.serialize(StringValue("hello"))
22+
}
23+
}
24+
25+
@Test
26+
fun `serialize should return the value when a FieldSet`() {
27+
28+
@FieldSet("1")
29+
class MyClass
30+
31+
val result = coercing.serialize(MyClass::class.annotations.first())
32+
assertEquals(expected = "1", actual = result)
33+
}
34+
35+
@Test
36+
fun `parseValue should run to parseLiteral`() {
37+
val result = coercing.parseValue(StringValue("hello"))
38+
39+
assertTrue(result is FieldSet)
40+
}
41+
42+
@Test
43+
fun `parseValue should throw exception on non-StringValue`() {
44+
assertFailsWith(CoercingParseLiteralException::class) {
45+
coercing.parseValue(IntValue(BigInteger.ONE))
46+
}
47+
}
48+
49+
@Test
50+
fun `parseLiteral should map StringValue to a FieldSet`() {
51+
val result = coercing.parseLiteral(StringValue("hello"))
52+
53+
assertTrue(result is FieldSet)
54+
}
55+
56+
@Test
57+
fun `parseLiteral should throw exception on non-StringValue`() {
58+
assertFailsWith(CoercingParseLiteralException::class) {
59+
coercing.parseLiteral(IntValue(BigInteger.ONE))
60+
}
61+
}
62+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.expediagroup.graphql.federation.types
2+
3+
import graphql.schema.GraphQLNonNull
4+
import graphql.schema.GraphQLObjectType
5+
import org.junit.jupiter.api.Test
6+
import kotlin.test.assertEquals
7+
import kotlin.test.assertNotNull
8+
9+
internal class ServiceTest {
10+
11+
@Test
12+
fun `service object should have the correct naming`() {
13+
val service = _Service("mySdl")
14+
assertNotNull(service.sdl)
15+
16+
val serviceField = SERVICE_FIELD_DEFINITION
17+
assertEquals(expected = "_service", actual = serviceField.name)
18+
19+
val serviceObject = serviceField.type as? GraphQLObjectType
20+
assertNotNull(serviceObject)
21+
assertEquals(expected = "_Service", actual = serviceObject.name)
22+
assertEquals(expected = 1, actual = serviceObject.fieldDefinitions.size)
23+
assertEquals(expected = "sdl", actual = serviceObject.fieldDefinitions.first().name)
24+
25+
val fieldType = serviceObject.fieldDefinitions.first().type as? GraphQLNonNull
26+
assertNotNull(fieldType)
27+
assertEquals(expected = "String", actual = fieldType.wrappedType.name)
28+
}
29+
}

0 commit comments

Comments
 (0)