8000 [v2] Add Integration Tests (#326) · neo4j-graphql/neo4j-graphql-java@daf4b30 · GitHub
[go: up one dir, main page]

Skip to content

Commit daf4b30

Browse files
authored
[v2] Add Integration Tests (#326)
Following bugs where found and fixed: * handling paging correctly * handling duration and temporal scalars correctly * fixing union connection issues * using correct logical operator for `SOME` and `SINGLE` relation predicates The test-framework is extended by the following features: * validating query results via JsonPath in asciidoc * testing custom resolvers via asciidoc * using neo4j-driver connection for integration tests
1 parent 8aa1548 commit daf4b30

File tree

426 files changed

+57506
-33550
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

426 files changed

+57506
-33550
lines changed

core/pom.xml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@
2626
<version>2.0.0-SNAPSHOT</version>
2727
<scope>test</scope>
2828
</dependency>
29+
<dependency>
30+
<groupId>org.threeten</groupId>
31+
<artifactId>threeten-extra</artifactId>
32+
<version>1.7.0</version>
33+
</dependency>
2934
<dependency>
3035
<groupId>org.neo4j.driver</groupId>
3136
<artifactId>neo4j-java-driver</artifactId>
@@ -122,6 +127,13 @@
122127
<version>2.17.2</version>
123128
<scope>test</scope>
124129
</dependency>
130+
<dependency>
131+
<groupId>org.apache.commons</groupId>
132+
<artifactId>commons-csv</artifactId>
133+
<version>1.12.0</version>
134+
<scope>test</scope>
135+
</dependency>
136+
125137
</dependencies>
126138

127139
<dependencyManagement>
@@ -148,4 +160,27 @@
148160
</dependency>
149161
</dependencies>
150162
</dependencyManagement>
163+
164+
<profiles>
165+
<profile>
166+
<id>create-test-file-diff</id>
167+
<activation>
168+
<activeByDefault>true</activeByDefault>
169+
</activation>
170+
<build>
171+
<plugins>
172+
<plugin>
173+
<groupId>org.apache.maven.plugins</groupId>
174+
<artifactId>maven-surefire-plugin</artifactId>
175+
<version>3.5.0</version>
176+
<configuration>
177+
<systemPropertyVariables>
178+
<neo4j-graphql-java.generate-test-file-diff>true</neo4j-graphql-java.generate-test-file-diff>
179+
</systemPropertyVariables>
180+
</configuration>
181+
</plugin>
182+
</plugins>
183+
</build>
184+
</profile>
185+
</profiles>
151186
</project>

core/src/main/kotlin/org/neo4j/graphql/Constants.kt

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import graphql.language.TypeName
44
import org.neo4j.graphql.domain.directives.RelationshipDirective
55

66
object Constants {
7+
const val TYPE_NAME = "__typename"
78

89
const val JS_COMPATIBILITY: Boolean = true
910
const val ID_FIELD = "id"
@@ -26,18 +27,9 @@ object Constants {
2627
const val RELATIONSHIP_FIELD = "relationship"
2728
const val TYPENAME_IN = "typename_IN"
2829

29-
const val RESOLVE_TYPE = "__resolveType"
30+
const val RESOLVE_TYPE = TYPE_NAME
3031
const val RESOLVE_ID = "__id"
3132

32-
const val X = "x"
33-
const val Y = "y"
34-
const val Z = "z"
35-
const val LONGITUDE = "longitude"
36-
const val LATITUDE = "latitude"
37-
const val HEIGHT = "height"
38-
const val CRS = "crs"
39-
const val SRID = "srid"
40-
4133
const val POINT_TYPE = "Point"
4234
const val CARTESIAN_POINT_TYPE = "CartesianPoint"
4335
const val POINT_INPUT_TYPE = "PointInput"
@@ -68,8 +60,6 @@ object Constants {
6860
RelationshipDirective.NAME,
6961
)
7062

71-
const val TYPE_NAME = "__typename"
72-
7363
const val OPTIONS = "options"
7464
const val WHERE = "where"
7565

@@ -85,6 +75,9 @@ object Constants {
8575
val SortDirection = TypeName("SortDirection")
8676
val PointDistance = TypeName("PointDistance")
8777
val CartesianPointDistance = TypeName("CartesianPointDistance")
78+
79+
val POINT = TypeName(POINT_TYPE)
80+
val CARTESIAN_POINT = TypeName(CARTESIAN_POINT_TYPE)
8881
}
8982

9083

core/src/main/kotlin/org/neo4j/graphql/CypherDataFetcherResult.kt

Lines changed: 0 additions & 10 deletions
This file was deleted.

core/src/main/kotlin/org/neo4j/graphql/ExtensionFunctions.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ fun String.toLowerCase(): String = lowercase(Locale.getDefault())
3030

3131
infix fun Condition?.and(rhs: Condition) = this?.and(rhs) ?: rhs
3232
infix fun Condition?.or(rhs: Condition) = this?.or(rhs) ?: rhs
33+
infix fun Condition?.xor(rhs: Condition) = this?.xor(rhs) ?: rhs
34+
3335
fun Collection<Condition?>.foldWithAnd(): Condition? = this
3436
.filterNotNull()
3537
.takeIf { it.isNotEmpty() }
@@ -169,3 +171,4 @@ fun Iterable<Any?>.toDict(): List<Dict> = this.mapNotNull { Dict.create(it) }
169171

170172
fun String.toDeprecatedDirective() = Directive("deprecated", listOf(Argument("reason", StringValue(this))))
171173

174+
fun Collection<Statement>.union(): Statement = if (this.size == 1) this.first() else Cypher.union(this)

core/src/main/kotlin/org/neo4j/graphql/QueryContext.kt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,9 @@ package org.neo4j.graphql
33
import org.neo4j.cypherdsl.core.Cypher
44
import org.neo4j.cypherdsl.core.Parameter
55
import org.neo4j.graphql.domain.fields.RelationField
6-
import org.neo4j.graphql.driver.adapter.Neo4jAdapter.Dialect
76
import java.util.concurrent.atomic.AtomicInteger
87

98
data class QueryContext @JvmOverloads constructor(
10-
var neo4jDialect: Dialect = Dialect.NEO4J_5,
11-
129
val contextParams: Map<String, Any?>? = emptyMap(),
1310
) {
1411

core/src/main/kotlin/org/neo4j/graphql/SchemaBuilder.kt

Lines changed: 67 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@ import org.neo4j.graphql.domain.directives.Annotations.Companion.LIBRARY_DIRECTI
1313
import org.neo4j.graphql.domain.fields.RelationField
1414
import org.neo4j.graphql.driver.adapter.Neo4jAdapter
1515
import org.neo4j.graphql.handler.ConnectionResolver
16+
import org.neo4j.graphql.handler.ImplementingTypeConnectionFieldResolver
1617
import org.neo4j.graphql.handler.ReadResolver
1718
import org.neo4j.graphql.scalars.BigIntScalar
19+
import org.neo4j.graphql.scalars.DurationScalar
20+
import org.neo4j.graphql.scalars.TemporalScalar
1821
import org.neo4j.graphql.schema.AugmentationContext
1922
import org.neo4j.graphql.schema.AugmentationHandler
2023
import org.neo4j.graphql.schema.model.outputs.InterfaceSelection
@@ -35,69 +38,90 @@ import org.neo4j.graphql.schema.model.outputs.NodeSelection
3538
*/
3639
class SchemaBuilder @JvmOverloads constructor(
3740
val typeDefinitionRegistry: TypeDefinitionRegistry,
38-
val schemaConfig: SchemaConfig = SchemaConfig()
41+
val schemaConfig: SchemaConfig = SchemaConfig(),
3942
) {
4043

4144
companion object {
42-
/**
43-
* @param sdl the schema to augment
44-
* @param neo4jAdapter the adapter to run the generated cypher queries
45-
* @param config defines how the schema should get augmented
46-
*/
45+
4746
@JvmStatic
4847
@JvmOverloads
49-
fun buildSchema(
50-
sdl: String,
51-
config: SchemaConfig = SchemaConfig(),
52-
neo4jAdapter: Neo4jAdapter = Neo4jAdapter.NO_OP,
53-
addLibraryDirectivesToSchema: Boolean = true,
54-
): GraphQLSchema {
48+
fun fromSchema(sdl: String, config: SchemaConfig = SchemaConfig()): SchemaBuilder {
5549
val schemaParser = SchemaParser()
5650
val typeDefinitionRegistry = schemaParser.parse(sdl)
57-
return buildSchema(typeDefinitionRegistry, config, neo4jAdapter, addLibraryDirectivesToSchema)
51+
return SchemaBuilder(typeDefinitionRegistry, config)
5852
}
5953

6054
/**
61-
* @param typeDefinitionRegistry a registry containing all the types, that should be augmented
62-
* @param config defines how the schema should get augmented
55+
* @param sdl the schema to augment
6356
* @param neo4jAdapter the adapter to run the generated cypher queries
57+
* @param config defines how the schema should get augmented
6458
*/
6559
@JvmStatic
6660
@JvmOverloads
6761
fun buildSchema(
68-
typeDefinitionRegistry: TypeDefinitionRegistry,
62+
sdl: String,
6963
config: SchemaConfig = SchemaConfig(),
70-
neo4jAdapter: Neo4jAdapter,
64+
neo4jAdapter: Neo4jAdapter = Neo4jAdapter.NO_OP,
7165
addLibraryDirectivesToSchema: Boolean = true,
72-
): GraphQLSchema {
73-
74-
val builder = RuntimeWiring.newRuntimeWiring()
75-
val codeRegistryBuilder = GraphQLCodeRegistry.newCodeRegistry()
76-
val schemaBuilder = SchemaBuilder(typeDefinitionRegistry, config)
77-
schemaBuilder.augmentTypes(addLibraryDirectivesToSchema)
78-
schemaBuilder.registerScalars(builder)
79-
schemaBuilder.registerTypeNameResolver(builder)
80-
schemaBuilder.registerNeo4jAdapter(codeRegistryBuilder, neo4jAdapter)
81-
82-
return SchemaGenerator().makeExecutableSchema(
83-
typeDefinitionRegistry,
84-
builder.codeRegistry(codeRegistryBuilder).build()
85-
)
86-
}
66+
): GraphQLSchema = fromSchema(sdl, config)
67+
.withNeo4jAdapter(neo4jAdapter)
68+
.addLibraryDirectivesToSchema(addLibraryDirectivesToSchema)
69+
.build()
8770
}
8871

8972
private val handler: List<AugmentationHandler>
9073
private val neo4jTypeDefinitionRegistry: TypeDefinitionRegistry = getNeo4jEnhancements()
9174
private val augmentedFields = mutableListOf<AugmentationHandler.AugmentedField>()
9275
private val ctx = AugmentationContext(schemaConfig, typeDefinitionRegistry)
76+
private var addLibraryDirectivesToSchema: Boolean = false;
77+
private var codeRegistryBuilder: GraphQLCodeRegistry.Builder? = null
78+
private var runtimeWiringBuilder: RuntimeWiring.Builder? = null
79+
private var neo4jAdapter: Neo4jAdapter = Neo4jAdapter.NO_OP
9380

9481
init {
9582
handler = mutableListOf(
9683
ReadResolver.Factory(ctx),
9784
ConnectionResolver.Factory(ctx),
85+
ImplementingTypeConnectionFieldResolver.Factory(ctx)
9886
)
9987
}
10088

89+
fun addLibraryDirectivesToSchema(addLibraryDirectivesToSchema: Boolean): SchemaBuilder {
90+
this.addLibraryDirectivesToSchema = addLibraryDirectivesToSchema
91+
return this
92+
}
93+
94+
fun withCodeRegistryBuilder(codeRegistryBuilder: GraphQLCodeRegistry.Builder): SchemaBuilder {
95+
this.codeRegistryBuilder = codeRegistryBuilder
96+
return this
97+
}
98+
99+
fun withRuntimeWiringBuilder(runtimeWiring: RuntimeWiring.Builder): SchemaBuilder {
100+
this.runtimeWiringBuilder = runtimeWiring
101+
return this
102+
}
103+
104+
fun withNeo4jAdapter(neo4jAdapter: Neo4jAdapter): SchemaBuilder {
105+
this.neo4jAdapter = neo4jAdapter
106+
return this
107+
}
108+
109+
fun build(): GraphQLSchema {
110+
augmentTypes(addLibraryDirectivesToSchema)
111+
val runtimeWiringBuilder = this.runtimeWiringBuilder ?: RuntimeWiring.newRuntimeWiring()
112+
registerScalars(runtimeWiringBuilder)
113+
registerTypeNameResolver(runtimeWiringBuilder)
114+
115+
val codeRegistryBuilder = this.codeRegistryBuilder ?: GraphQLCodeRegistry.newCodeRegistry()
116+
registerNeo4jAdapter(codeRegistryBuilder, neo4jAdapter)
117+
118+
return SchemaGenerator().makeExecutableSchema(
119+
typeDefinitionRegistry,
120+
runtimeWiringBuilder.codeRegistry(codeRegistryBuilder).build()
121+
)
122+
123+
}
124+
101125

102126
/**
103127
* Generated additionally query and mutation fields according to the types present in the [typeDefinitionRegistry].
@@ -266,6 +290,12 @@ class SchemaBuilder @JvmOverloads constructor(
266290
.forEach { (name, definition) ->
267291
val scalar = when (name) {
268292
Constants.BIG_INT -> BigIntScalar.INSTANCE
293+
Constants.DATE -> TemporalScalar.DATE
294+
Constants.TIME -> TemporalScalar.TIME
295+
Constants.LOCAL_TIME -> TemporalScalar.LOCAL_TIME
296+
Constants.DATE_TIME -> TemporalScalar.DATE_TIME
297+
Constants.LOCAL_DATE_TIME -> TemporalScalar.LOCAL_DATE_TIME
298+
Constants.DURATION -> DurationScalar.INSTANCE
269299
else -> GraphQLScalarType.newScalar()
270300
.name(name)
271301
.description(
@@ -310,20 +340,11 @@ class SchemaBuilder @JvmOverloads constructor(
310340
neo4jAdapter: Neo4jAdapter,
311341
) {
312342
codeRegistryBuilder.defaultDataFetcher { AliasPropertyDataFetcher() }
313-
augmentedFields.forEach { augmentedField ->
314-
val interceptedDataFetcher: DataFetcher<*> = DataFetcher { env ->
315-
val neo4jDialect = neo4jAdapter.getDialect()
316-
env.graphQlContext.setQueryContext(QueryContext(neo4jDialect = neo4jDialect))
317-
val (cypher, params, type, variable) = augmentedField.dataFetcher.get(env)
318-
val result = neo4jAdapter.executeQuery(cypher, params)
319-
return@DataFetcher if (type?.isList() == true) {
320-
result.map { it[variable] }
321-
} else {
322-
result.map { it[variable] }
323-
.firstOrNull() ?: emptyMap<String, Any>()
324-
}
325-
}
326-
codeRegistryBuilder.dataFetcher(augmentedField.coordinates, interceptedDataFetcher)
343+
augmentedFields.forEach { (coordinates, dataFetcher) ->
344+
codeRegistryBuilder.dataFetcher(coordinates, DataFetcher { env ->
345+
env.graphQlContext.put(Neo4jAdapter.CONTEXT_KEY, neo4jAdapter)
346+
dataFetcher.get(env)
347+
})
327348
}
328349
}
329350

core/src/main/kotlin/org/neo4j/graphql/domain/fields/PointField.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,9 @@ class PointField(
103103

104104
enum class CoordinateType(
105105
internal val inputType: TypeName,
106-
internal val selectionFactory: (IResolveTree) -> BasePointSelection
106+
internal val selectionFactory: (IResolveTree) -> BasePointSelection<*>
107107
) {
108-
GEOGRAPHIC(Constants.Types.PointDistance, ::PointSelection),
109-
CARTESIAN(Constants.Types.CartesianPointDistance, ::CartesianPointSelection)
108+
GEOGRAPHIC(Constants.Types.PointDistance, PointSelection::parse),
109+
CARTESIAN(Constants.Types.CartesianPointDistance, CartesianPointSelection::parse)
110110
}
111111
}

core/src/main/kotlin/org/neo4j/graphql/domain/naming/RelationshipBaseNames.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ sealed class RelationshipBaseNames<T : RelationBaseField>(
2828

2929
val relationshipFieldTypename get() = "${prefixForTypenameWithInheritance}Relationship"
3030

31-
val connectionFieldName get() = "${prefixForTypenameWithInheritance}Connection"
31+
val connectionFieldName get() = "${relationship.fieldName}Connection"
3232

3333
fun getConnectionWhereTypename(target: ImplementingType) =
3434
"$prefixForTypenameWithInheritance${target.useNameIfFieldIsUnion()}ConnectionWhere"

core/src/main/kotlin/org/neo4j/graphql/handler/BaseDataFetcher.kt

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,20 @@ import org.neo4j.cypherdsl.core.Statement
77
import org.neo4j.cypherdsl.core.renderer.Configuration
88
import org.neo4j.cypherdsl.core.renderer.Dialect
99
import org.neo4j.cypherdsl.core.renderer.Renderer
10-
import org.neo4j.graphql.CypherDataFetcherResult
1110
import org.neo4j.graphql.SchemaConfig
1211
import org.neo4j.graphql.driver.adapter.Neo4jAdapter
13-
import org.neo4j.graphql.queryContext
12+
import org.neo4j.graphql.isList
1413

1514
/**
1615
* This is a base class for the implementation of graphql data fetcher used in this project
1716
*/
1817
internal abstract class BaseDataFetcher(protected val schemaConfig: SchemaConfig) :
19-
DataFetcher<CypherDataFetcherResult> {
18+
DataFetcher<Any> {
2019

21-
final override fun get(env: DataFetchingEnvironment): CypherDataFetcherResult {
22-
val variable = "this"
23-
val statement = generateCypher(variable, env)
24-
val dialect = when (env.queryContext().neo4jDialect) {
20+
final override fun get(env: DataFetchingEnvironment): Any {
21+
val statement = generateCypher(env)
22+
val neo4jAdapter = env.graphQlContext.get<Neo4jAdapter?>(Neo4jAdapter.CONTEXT_KEY)
23+
val dialect = when (neo4jAdapter.getDialect()) {
2524
Neo4jAdapter.Dialect.NEO4J_4 -> Dialect.NEO4J_4
2625
Neo4jAdapter.Dialect.NEO4J_5 -> Dialect.NEO4J_5
2726
Neo4jAdapter.Dialect.NEO4J_5_23 -> Dialect.NEO4J_5_23
@@ -38,8 +37,19 @@ internal abstract class BaseDataFetcher(protected val schemaConfig: SchemaConfig
3837
val params = statement.catalog.parameters.mapValues { (_, value) ->
3938
(value as? VariableReference)?.let { env.variables[it.name] } ?: value
4039
}
41-
return CypherDataFetcherResult(query, params, env.fieldDefinition.type, variable = variable)
40+
41+
val result = neo4jAdapter.executeQuery(query, params)
42+
return if (env.fieldDefinition.type?.isList() == true) {
43+
result.map { it[RESULT_VARIABLE] }
44+
} else {
45+
result.map { it[RESULT_VARIABLE] }
46+
.firstOrNull() ?: emptyMap<String, Any>()
47+
}
4248
}
4349

44-
protected abstract fun generateCypher(variable: String, env: DataFetchingEnvironment): Statement
50+
protected abstract fun generateCypher(env: DataFetchingEnvironment): Statement
51+
52+
companion object {
53+
const val RESULT_VARIABLE = "this"
54+
}
4555
}

0 commit comments

Comments
 (0)
0