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

[v2] Add Integration Tests #326

Merged
merged 7 commits into from
Dec 13, 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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
35 changes: 35 additions & 0 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@
<version>2.0.0-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.threeten</groupId>
<artifactId>threeten-extra</artifactId>
<version>1.7.0</version>
</dependency>
<dependency>
<groupId>org.neo4j.driver</groupId>
<artifactId>neo4j-java-driver</artifactId>
Expand Down Expand Up @@ -122,6 +127,13 @@
<version>2.17.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-csv</artifactId>
<version>1.12.0</version>
<scope>test</scope>
</dependency>

</dependencies>

<dependencyManagement>
Expand All @@ -148,4 +160,27 @@
</dependency>
</dependencies>
</dependencyManagement>

<profiles>
<profile>
<id>create-test-file-diff</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.0</version>
<configuration>
<systemPropertyVariables>
<neo4j-graphql-java.generate-test-file-diff>true</neo4j-graphql-java.generate-test-file-diff>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
17 changes: 5 additions & 12 deletions core/src/main/kotlin/org/neo4j/graphql/Constants.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import graphql.language.TypeName
import org.neo4j.graphql.domain.directives.RelationshipDirective

object Constants {
const val TYPE_NAME = "__typename"

const val JS_COMPATIBILITY: Boolean = true
const val ID_FIELD = "id"
Expand All @@ -26,18 +27,9 @@ object Constants {
const val RELATIONSHIP_FIELD = "relationship"
const val TYPENAME_IN = "typename_IN"

const val RESOLVE_TYPE = "__resolveType"
const val RESOLVE_TYPE = TYPE_NAME
const val RESOLVE_ID = "__id"

const val X = "x"
const val Y = "y"
const val Z = "z"
const val LONGITUDE = "longitude"
const val LATITUDE = "latitude"
const val HEIGHT = "height"
const val CRS = "crs"
const val SRID = "srid"

const val POINT_TYPE = "Point"
const val CARTESIAN_POINT_TYPE = "CartesianPoint"
const val POINT_INPUT_TYPE = "PointInput"
Expand Down Expand Up @@ -68,8 +60,6 @@ object Constants {
RelationshipDirective.NAME,
)

const val TYPE_NAME = "__typename"

const val OPTIONS = "options"
const val WHERE = "where"

Expand All @@ -85,6 +75,9 @@ object Constants {
val SortDirection = TypeName("SortDirection")
val PointDistance = TypeName("PointDistance")
val CartesianPointDistance = TypeName("CartesianPointDistance")

val POINT = TypeName(POINT_TYPE)
val CARTESIAN_POINT = TypeName(CARTESIAN_POINT_TYPE)
}


Expand Down
10 changes: 0 additions & 10 deletions core/src/main/kotlin/org/neo4j/graphql/CypherDataFetcherResult.kt

This file was deleted.

3 changes: 3 additions & 0 deletions core/src/main/kotlin/org/neo4j/graphql/ExtensionFunctions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ fun String.toLowerCase(): String = lowercase(Locale.getDefault())

infix fun Condition?.and(rhs: Condition) = this?.and(rhs) ?: rhs
infix fun Condition?.or(rhs: Condition) = this?.or(rhs) ?: rhs
infix fun Condition?.xor(rhs: Condition) = this?.xor(rhs) ?: rhs

fun Collection<Condition?>.foldWithAnd(): Condition? = this
.filterNotNull()
.takeIf { it.isNotEmpty() }
Expand Down Expand Up @@ -169,3 +171,4 @@ fun Iterable<Any?>.toDict(): List<Dict> = this.mapNotNull { Dict.create(it) }

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

fun Collection<Statement>.union(): Statement = if (this.size == 1) this.first() else Cypher.union(this)
3 changes: 0 additions & 3 deletions core/src/main/kotlin/org/neo4j/graphql/QueryContext.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,9 @@ package org.neo4j.graphql
import org.neo4j.cypherdsl.core.Cypher
import org.neo4j.cypherdsl.core.Parameter
import org.neo4j.graphql.domain.fields.RelationField
import org.neo4j.graphql.driver.adapter.Neo4jAdapter.Dialect
import java.util.concurrent.atomic.AtomicInteger

data class QueryContext @JvmOverloads constructor(
var neo4jDialect: Dialect = Dialect.NEO4J_5,

val contextParams: Map<String, Any?>? = emptyMap(),
) {

Expand Down
113 changes: 67 additions & 46 deletions core/src/main/kotlin/org/neo4j/graphql/SchemaBuilder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ import org.neo4j.graphql.domain.directives.Annotations.Companion.LIBRARY_DIRECTI
import org.neo4j.graphql.domain.fields.RelationField
import org.neo4j.graphql.driver.adapter.Neo4jAdapter
import org.neo4j.graphql.handler.ConnectionResolver
import org.neo4j.graphql.handler.ImplementingTypeConnectionFieldResolver
import org.neo4j.graphql.handler.ReadResolver
import org.neo4j.graphql.scalars.BigIntScalar
import org.neo4j.graphql.scalars.DurationScalar
import org.neo4j.graphql.scalars.TemporalScalar
import org.neo4j.graphql.schema.AugmentationContext
import org.neo4j.graphql.schema.AugmentationHandler
import org.neo4j.graphql.schema.model.outputs.InterfaceSelection
Expand All @@ -35,69 +38,90 @@ import org.neo4j.graphql.schema.model.outputs.NodeSelection
*/
class SchemaBuilder @JvmOverloads constructor(
val typeDefinitionRegistry: TypeDefinitionRegistry,
val schemaConfig: SchemaConfig = SchemaConfig()
val schemaConfig: SchemaConfig = SchemaConfig(),
) {

companion object {
/**
* @param sdl the schema to augment
* @param neo4jAdapter the adapter to run the generated cypher queries
* @param config defines how the schema should get augmented
*/

@JvmStatic
@JvmOverloads
fun buildSchema(
sdl: String,
config: SchemaConfig = SchemaConfig(),
neo4jAdapter: Neo4jAdapter = Neo4jAdapter.NO_OP,
addLibraryDirectivesToSchema: Boolean = true,
): GraphQLSchema {
fun fromSchema(sdl: String, config: SchemaConfig = SchemaConfig()): SchemaBuilder {
val schemaParser = SchemaParser()
val typeDefinitionRegistry = schemaParser.parse(sdl)
return buildSchema(typeDefinitionRegistry, config, neo4jAdapter, addLibraryDirectivesToSchema)
return SchemaBuilder(typeDefinitionRegistry, config)
}

/**
* @param typeDefinitionRegistry a registry containing all the types, that should be augmented
* @param config defines how the schema should get augmented
* @param sdl the schema to augment
* @param neo4jAdapter the adapter to run the generated cypher queries
* @param config defines how the schema should get augmented
*/
@JvmStatic
@JvmOverloads
fun buildSchema(
typeDefinitionRegistry: TypeDefinitionRegistry,
sdl: String,
config: SchemaConfig = SchemaConfig(),
neo4jAdapter: Neo4jAdapter,
neo4jAdapter: Neo4jAdapter = Neo4jAdapter.NO_OP,
addLibraryDirectivesToSchema: Boolean = true,
): GraphQLSchema {

val builder = RuntimeWiring.newRuntimeWiring()
val codeRegistryBuilder = GraphQLCodeRegistry.newCodeRegistry()
val schemaBuilder = SchemaBuilder(typeDefinitionRegistry, config)
schemaBuilder.augmentTypes(addLibraryDirectivesToSchema)
schemaBuilder.registerScalars(builder)
schemaBuilder.registerTypeNameResolver(builder)
schemaBuilder.registerNeo4jAdapter(codeRegistryBuilder, neo4jAdapter)

return SchemaGenerator().makeExecutableSchema(
typeDefinitionRegistry,
builder.codeRegistry(codeRegistryBuilder).build()
)
}
): GraphQLSchema = fromSchema(sdl, config)
.withNeo4jAdapter(neo4jAdapter)
.addLibraryDirectivesToSchema(addLibraryDirectivesToSchema)
.build()
}

private val handler: List<AugmentationHandler>
private val neo4jTypeDefinitionRegistry: TypeDefinitionRegistry = getNeo4jEnhancements()
private val augmentedFields = mutableListOf<AugmentationHandler.AugmentedField>()
private val ctx = AugmentationContext(schemaConfig, typeDefinitionRegistry)
private var addLibraryDirectivesToSchema: Boolean = false;
private var codeRegistryBuilder: GraphQLCodeRegistry.Builder? = null
private var runtimeWiringBuilder: RuntimeWiring.Builder? = null
private var neo4jAdapter: Neo4jAdapter = Neo4jAdapter.NO_OP

init {
handler = mutableListOf(
ReadResolver.Factory(ctx),
ConnectionResolver.Factory(ctx),
ImplementingTypeConnectionFieldResolver.Factory(ctx)
)
}

fun addLibraryDirectivesToSchema(addLibraryDirectivesToSchema: Boolean): SchemaBuilder {
this.addLibraryDirectivesToSchema = addLibraryDirectivesToSchema
return this
}

fun withCodeRegistryBuilder(codeRegistryBuilder: GraphQLCodeRegistry.Builder): SchemaBuilder {
this.codeRegistryBuilder = codeRegistryBuilder
return this
}

fun withRuntimeWiringBuilder(runtimeWiring: RuntimeWiring.Builder): SchemaBuilder {
this.runtimeWiringBuilder = runtimeWiring
return this
}

fun withNeo4jAdapter(neo4jAdapter: Neo4jAdapter): SchemaBuilder {
this.neo4jAdapter = neo4jAdapter
return this
}

fun build(): GraphQLSchema {
augmentTypes(addLibraryDirectivesToSchema)
val runtimeWiringBuilder = this.runtimeWiringBuilder ?: RuntimeWiring.newRuntimeWiring()
registerScalars(runtimeWiringBuilder)
registerTypeNameResolver(runtimeWiringBuilder)

val codeRegistryBuilder = this.codeRegistryBuilder ?: GraphQLCodeRegistry.newCodeRegistry()
registerNeo4jAdapter(codeRegistryBuilder, neo4jAdapter)

return SchemaGenerator().makeExecutableSchema(
typeDefinitionRegistry,
runtimeWiringBuilder.codeRegistry(codeRegistryBuilder).build()
)

}


/**
* Generated additionally query and mutation fields according to the types present in the [typeDefinitionRegistry].
Expand Down Expand Up @@ -266,6 +290,12 @@ class SchemaBuilder @JvmOverloads constructor(
.forEach { (name, definition) ->
val scalar = when (name) {
Constants.BIG_INT -> BigIntScalar.INSTANCE
Constants.DATE -> TemporalScalar.DATE
Constants.TIME -> TemporalScalar.TIME
Constants.LOCAL_TIME -> TemporalScalar.LOCAL_TIME
Constants.DATE_TIME -> TemporalScalar.DATE_TIME
Constants.LOCAL_DATE_TIME -> TemporalScalar.LOCAL_DATE_TIME
Constants.DURATION -> DurationScalar.INSTANCE
else -> GraphQLScalarType.newScalar()
.name(name)
.description(
Expand Down Expand Up @@ -310,20 +340,11 @@ class SchemaBuilder @JvmOverloads constructor(
neo4jAdapter: Neo4jAdapter,
) {
codeRegistryBuilder.defaultDataFetcher { AliasPropertyDataFetcher() }
augmentedFields.forEach { augmentedField ->
val interceptedDataFetcher: DataFetcher<*> = DataFetcher { env ->
val neo4jDialect = neo4jAdapter.getDialect()
env.graphQlContext.setQueryContext(QueryContext(neo4jDialect = neo4jDialect))
val (cypher, params, type, variable) = augmentedField.dataFetcher.get(env)
val result = neo4jAdapter.executeQuery(cypher, params)
return@DataFetcher if (type?.isList() == true) {
result.map { it[variable] }
} else {
result.map { it[variable] }
.firstOrNull() ?: emptyMap<String, Any>()
}
}
codeRegistryBuilder.dataFetcher(augmentedField.coordinates, interceptedDataFetcher)
augmentedFields.forEach { (coordinates, dataFetcher) ->
codeRegistryBuilder.dataFetcher(coordinates, DataFetcher { env ->
env.graphQlContext.put(Neo4jAdapter.CONTEXT_KEY, neo4jAdapter)
dataFetcher.get(env)
})
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,9 @@ class PointField(

enum class CoordinateType(
internal val inputType: TypeName,
internal val selectionFactory: (IResolveTree) -> BasePointSelection
internal val selectionFactory: (IResolveTree) -> BasePointSelection<*>
) {
GEOGRAPHIC(Constants.Types.PointDistance, ::PointSelection),
CARTESIAN(Constants.Types.CartesianPointDistance, ::CartesianPointSelection)
GEOGRAPHIC(Constants.Types.PointDistance, PointSelection::parse),
CARTESIAN(Constants.Types.CartesianPointDistance, CartesianPointSelection::parse)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ sealed class RelationshipBaseNames<T : RelationBaseField>(

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

val connectionFieldName get() = "${prefixForTypenameWithInheritance}Connection"
val connectionFieldName get() = "${relationship.fieldName}Connection"

fun getConnectionWhereTypename(target: ImplementingType) =
"$prefixForTypenameWithInheritance${target.useNameIfFieldIsUnion()}ConnectionWhere"
Expand Down
28 changes: 19 additions & 9 deletions core/src/main/kotlin/org/neo4j/graphql/handler/BaseDataFetcher.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,20 @@ import org.neo4j.cypherdsl.core.Statement
import org.neo4j.cypherdsl.core.renderer.Configuration
import org.neo4j.cypherdsl.core.renderer.Dialect
import org.neo4j.cypherdsl.core.renderer.Renderer
import org.neo4j.graphql.CypherDataFetcherResult
import org.neo4j.graphql.SchemaConfig
import org.neo4j.graphql.driver.adapter.Neo4jAdapter
import org.neo4j.graphql.queryContext
import org.neo4j.graphql.isList

/**
* This is a base class for the implementation of graphql data fetcher used in this project
*/
internal abstract class BaseDataFetcher(protected val schemaConfig: SchemaConfig) :
DataFetcher<CypherDataFetcherResult> {
DataFetcher<Any> {

final override fun get(env: DataFetchingEnvironment): CypherDataFetcherResult {
val variable = "this"
val statement = generateCypher(variable, env)
val dialect = when (env.queryContext().neo4jDialect) {
final override fun get(env: DataFetchingEnvironment): Any {
val statement = generateCypher(env)
val neo4jAdapter = env.graphQlContext.get<Neo4jAdapter?>(Neo4jAdapter.CONTEXT_KEY)
val dialect = when (neo4jAdapter.getDialect()) {
Neo4jAdapter.Dialect.NEO4J_4 -> Dialect.NEO4J_4
Neo4jAdapter.Dialect.NEO4J_5 -> Dialect.NEO4J_5
Neo4jAdapter.Dialect.NEO4J_5_23 -> Dialect.NEO4J_5_23
Expand All @@ -38,8 +37,19 @@ internal abstract class BaseDataFetcher(protected val schemaConfig: SchemaConfig
val params = statement.catalog.parameters.mapValues { (_, value) ->
(value as? VariableReference)?.let { env.variables[it.name] } ?: value
}
return CypherDataFetcherResult(query, params, env.fieldDefinition.type, variable = variable)

val result = neo4jAdapter.executeQuery(query, params)
return if (env.fieldDefinition.type?.isList() == true) {
result.map { it[RESULT_VARIABLE] }
} else {
result.map { it[RESULT_VARIABLE] }
.firstOrNull() ?: emptyMap<String, Any>()
}
}

protected abstract fun generateCypher(variable: String, env: DataFetchingEnvironment): Statement
protected abstract fun generateCypher(env: DataFetchingEnvironment): Statement

companion object {
const val RESULT_VARIABLE = "this"
}
}
Loading
Loading