Skip to content

Commit 349d56a

Browse files
oryan-blockclaude
andcommitted
Add regression tests for fixed issues
Cover nested generic object types (#415, #314), generic input classes with several parameterizations or nested in other inputs (#333), enum list argument defaults (#444), input field defaults (#298) and anonymous Java class resolvers (#714). Also make SchemaParserBuilderTest fail when no syntax error is thrown. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent f5b5fcd commit 349d56a

6 files changed

Lines changed: 399 additions & 0 deletions

File tree

‎src/test/kotlin/graphql/kickstart/tools/EnumDefaultValueTest.kt‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package graphql.kickstart.tools
22

33
import graphql.GraphQL
44
import graphql.execution.AsyncExecutionStrategy
5+
import graphql.schema.DataFetchingEnvironment
56
import org.junit.Test
67

78
class EnumDefaultValueTest {
@@ -44,6 +45,39 @@ class EnumDefaultValueTest {
4445
assertEquals(data["test"], "createdOn")
4546
}
4647

48+
@Test
49+
fun `enum list argument default value is passed to resolvers as enums`() {
50+
val schema = SchemaParser.newParser()
51+
.schemaString(
52+
"""
53+
type Query {
54+
test(sortBy: [SortBy!] = [createdOn, updatedOn]): [String!]!
55+
}
56+
enum SortBy {
57+
createdOn
58+
updatedOn
59+
}
60+
""")
61+
.resolvers(object : GraphQLQueryResolver {
62+
fun test(sortBy: List<SortBy>, env: DataFetchingEnvironment): List<String> =
63+
env.getArgument<List<Any>>("sortBy")!!.map { "${it.javaClass.simpleName}:$it" }
64+
})
65+
.build()
66+
.makeExecutableSchema()
67+
68+
val ggl = GraphQL.newGraphQL(schema).build()
69+
70+
val data = assertNoGraphQlErrors(ggl) {
71+
"""
72+
query {
73+
test
74+
}
75+
"""
76+
}
77+
78+
assertEquals(data["test"], listOf("SortBy:createdOn", "SortBy:updatedOn"))
79+
}
80+
4781
class MySortSpecifier {
4882
var sortBy: SortBy? = null
4983
var value: Int? = null

‎src/test/kotlin/graphql/kickstart/tools/GenericInputTypesTest.kt‎

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,91 @@ class GenericInputTypesTest {
8181
assertEquals(data["audit"], "op:1")
8282
}
8383

84+
@Test
85+
fun `one generic input class can back several input types`() {
86+
val schema = SchemaParser.newParser()
87+
.schemaString(
88+
"""
89+
type Query {
90+
languageAudit(input: LanguageAudit!): String!
91+
countryAudit(input: CountryAudit!): String!
92+
}
93+
94+
input LanguageAudit {
95+
content: LanguageInput!
96+
operator: String!
97+
}
98+
99+
input CountryAudit {
100+
content: CountryInput!
101+
operator: String!
102+
}
103+
104+
input LanguageInput {
105+
id: ID!
106+
}
107+
108+
input CountryInput {
109+
code: String!
110+
}
111+
""")
112+
.resolvers(MultipleParameterizationsQueryResolver())
113+
.build()
114+
.makeExecutableSchema()
115+
val gql = GraphQL.newGraphQL(schema).build()
116+
117+
val data = assertNoGraphQlErrors(gql) {
118+
"""
119+
query {
120+
languageAudit(input: { content: { id: "1" }, operator: "op" })
121+
countryAudit(input: { content: { code: "CA" }, operator: "op" })
122+
}
123+
"""
124+
}
125+
126+
assertEquals(data["languageAudit"], "op:1")
127+
assertEquals(data["countryAudit"], "op:CA")
128+
}
129+
130+
@Test
131+
fun `generic input types nested in a non-generic input type are parsed`() {
132+
val schema = SchemaParser.newParser()
133+
.schemaString(
134+
"""
135+
type Query {
136+
audit(input: AuditRequest!): String!
137+
}
138+
139+
input AuditRequest {
140+
audit: LanguageAudit!
141+
reason: String!
142+
}
143+
144+
input LanguageAudit {
145+
content: LanguageInput!
146+
operator: String!
147+
}
148+
149+
input LanguageInput {
150+
id: ID!
151+
}
152+
""")
153+
.resolvers(NestedQueryResolver())
154+
.build()
155+
.makeExecutableSchema()
156+
val gql = GraphQL.newGraphQL(schema).build()
157+
158+
val data = assertNoGraphQlErrors(gql) {
159+
"""
160+
query {
161+
audit(input: { audit: { content: { id: "1" }, operator: "op" }, reason: "why" })
162+
}
163+
"""
164+
}
165+
166+
assertEquals(data["audit"], "why:op:1")
167+
}
168+
84169
class QueryResolver : GraphQLQueryResolver {
85170
fun audit(input: AuditWrapper<LanguageInput>): String = "${input.operator}:${input.content?.id}"
86171
fun audits(input: AuditWrapper<List<LanguageInput>>): String = "${input.operator}:${input.content?.joinToString(",") { it.id.orEmpty() }}"
@@ -90,14 +175,32 @@ class GenericInputTypesTest {
90175
fun audit(input: LanguageAuditWrapper): String = "${input.operator}:${input.content?.id}"
91176
}
92177

178+
class MultipleParameterizationsQueryResolver : GraphQLQueryResolver {
179+
fun languageAudit(input: AuditWrapper<LanguageInput>): String = "${input.operator}:${input.content?.id}"
180+
fun countryAudit(input: AuditWrapper<CountryInput>): String = "${input.operator}:${input.content?.code}"
181+
}
182+
183+
class NestedQueryResolver : GraphQLQueryResolver {
184+
fun audit(input: AuditRequest): String = "${input.reason}:${input.audit?.operator}:${input.audit?.content?.id}"
185+
}
186+
93187
open class AuditWrapper<T> {
94188
var content: T? = null
95189
var operator: String? = null
96190
}
97191

98192
class LanguageAuditWrapper : AuditWrapper<LanguageInput>()
99193

194+
class AuditRequest {
195+
var audit: AuditWrapper<LanguageInput>? = null
196+
var reason: String? = null
197+
}
198+
100199
class LanguageInput {
101200
var id: String? = null
102201
}
202+
203+
class CountryInput {
204+
var code: String? = null
205+
}
103206
}
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package graphql.kickstart.tools
2+
3+
import graphql.GraphQL
4+
import org.junit.Test
5+
6+
class GenericObjectTypesTest {
7+
8+
@Test
9+
fun `generic type arguments that are lists are not erased`() {
10+
val schema = SchemaParser.newParser()
11+
.schemaString(
12+
"""
13+
type Query {
14+
test: TestClass!
15+
}
16+
17+
type TestClass {
18+
match: StringListEntityMatch!
19+
single: StringEntityMatch!
20+
}
21+
22+
type StringListEntityMatch {
23+
text: String!
24+
value: [String!]
25+
}
26+
27+
type StringEntityMatch {
28+
text: String!
29+
value: String
30+
}
31+
""")
32+
.resolvers(EntityMatchQueryResolver())
33+
.build()
34+
.makeExecutableSchema()
35+
val gql = GraphQL.newGraphQL(schema).build()
36+
37+
val data = assertNoGraphQlErrors(gql) {
38+
"""
39+
query {
40+
test {
41+
match { text value }
42+
single { text value }
43+
}
44+
}
45+
"""
46+
}
47+
48+
assertEquals(data["test"], mapOf(
49+
"match" to mapOf("text" to "x y", "value" to listOf("x", "y")),
50+
"single" to mapOf("text" to "z", "value" to "z")
51+
))
52+
}
53+
54+
@Test
55+
fun `nested generic type arguments are resolved`() {
56+
val schema = SchemaParser.newParser()
57+
.schemaString(
58+
"""
59+
type Query {
60+
characterHit: CharacterHit!
61+
characterHitPage: CharacterHitPage!
62+
}
63+
64+
type CharacterHitPage {
65+
content: [CharacterHit!]!
66+
totalElements: Int!
67+
}
68+
69+
type CharacterHit {
70+
score: Float!
71+
source: Character!
72+
}
73+
74+
interface Character {
75+
name: String!
76+
}
77+
78+
type Human implements Character {
79+
name: String!
80+
homePlanet: String!
81+
}
82+
83+
type Droid implements Character {
84+
name: String!
85+
primaryFunction: String!
86+
}
87+
""")
88+
.resolvers(CharacterHitQueryResolver())
89+
.dictionary(Human::class, Droid::class)
90+
.build()
91+
.makeExecutableSchema()
92+
val gql = GraphQL.newGraphQL(schema).build()
93+
94+
val data = assertNoGraphQlErrors(gql) {
95+
"""
96+
query {
97+
characterHit {
98+
score
99+
source { name }
100+
}
101+
characterHitPage {
102+
totalElements
103+
content {
104+
source {
105+
name
106+
... on Human { homePlanet }
107+
... on Droid { primaryFunction }
108+
}
109+
}
110+
}
111+
}
112+
"""
113+
}
114+
115+
assertEquals(data["characterHit"], mapOf("score" to 0.5, "source" to mapOf("name" to "Luke")))
116+
assertEquals(data["characterHitPage"], mapOf(
117+
"totalElements" to 2,
118+
"content" to listOf(
119+
mapOf("source" to mapOf("name" to "Luke", "homePlanet" to "Tatooine")),
120+
mapOf("source" to mapOf("name" to "R2-D2", "primaryFunction" to "Astromech"))
121+
)
122+
))
123+
}
124+
125+
class EntityMatchQueryResolver : GraphQLQueryResolver {
126+
fun test(): TestClass = TestClass(
127+
match = EntityMatch("x y", listOf("x", "y")),
128+
single = EntityMatch("z", "z")
129+
)
130+
}
131+
132+
data class TestClass(val match: EntityMatch<List<String>>, val single: EntityMatch<String>)
133+
134+
data class EntityMatch<T>(val text: String, val value: T?)
135+
136+
class CharacterHitQueryResolver : GraphQLQueryResolver {
137+
fun characterHit(): Hit<Character> = Hit(Human("Luke", "Tatooine"), 0.5)
138+
fun characterHitPage(): Page<Hit<Character>> = Page(
139+
listOf(Hit(Human("Luke", "Tatooine"), 1.0), Hit(Droid("R2-D2", "Astromech"), 1.0)),
140+
2
141+
)
142+
}
143+
144+
class Page<T>(val content: List<T>, val totalElements: Int)
145+
146+
class Hit<T>(val source: T, val score: Double)
147+
148+
interface Character {
149+
val name: String
150+
}
151+
152+
class Human(override val name: String, val homePlanet: String) : Character
153+
154+
class Droid(override val name: String, val primaryFunction: String) : Character
155+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package graphql.kickstart.tools
2+
3+
import graphql.GraphQL
4+
import org.junit.Test
5+
6+
class InputDefaultValueTest {
7+
8+
private val schema = SchemaParser.newParser()
9+
.schemaString(
10+
"""
11+
type Query {
12+
list(listing: Listing! = { size: 10 }): String!
13+
listWithEmptyDefault(listing: Listing! = {}): String!
14+
}
15+
16+
input Listing {
17+
size: Int! = 10
18+
after: String
19+
}
20+
""")
21+
.resolvers(QueryResolver())
22+
.build()
23+
.makeExecutableSchema()
24+
private val gql = GraphQL.newGraphQL(schema).build()
25+
26+
@Test
27+
fun `input field default value is applied when the field is omitted`() {
28+
val data = assertNoGraphQlErrors(gql) {
29+
"""
30+
query {
31+
list(listing: { after: "test" })
32+
}
33+
"""
34+
}
35+
36+
assertEquals(data["list"], "size: 10, after: test")
37+
}
38+
39+
@Test
40+
fun `input field default value is applied to an argument default value`() {
41+
val data = assertNoGraphQlErrors(gql) {
42+
"""
43+
query {
44+
listWithEmptyDefault
45+
}
46+
"""
47+
}
48+
49+
assertEquals(data["listWithEmptyDefault"], "size: 10, after: null")
50+
}
51+
52+
class QueryResolver : GraphQLQueryResolver {
53+
fun list(listing: Listing): String = describe(listing)
54+
fun listWithEmptyDefault(listing: Listing): String = describe(listing)
55+
56+
private fun describe(listing: Listing) = "size: ${listing.size}, after: ${listing.after}"
57+
}
58+
59+
class Listing {
60+
var size: Int? = null
61+
var after: String? = null
62+
}
63+
}

0 commit comments

Comments
 (0)