From 0f7118fc0a4a83ba960440d080369442a4dc284c Mon Sep 17 00:00:00 2001 From: Andreas Berger Date: Thu, 29 Aug 2024 12:35:15 +0200 Subject: [PATCH] Improve test-framework: add support for semantically comparison of cypher queries --- core/pom.xml | 19 +- .../neo4j/graphql/utils/AsciiDocTestSuite.kt | 53 ++-- .../neo4j/graphql/utils/CypherTestSuite.kt | 263 ++++++++++++++++-- .../graphql/utils/GraphQLSchemaTestSuite.kt | 72 +++-- .../resources/cypher-directive-tests.adoc | 4 +- pom.xml | 1 + 6 files changed, 321 insertions(+), 91 deletions(-) diff --git a/core/pom.xml b/core/pom.xml index 1b62676b..08036cb5 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -48,7 +48,13 @@ org.neo4j neo4j-cypher-dsl - 2024.0.3 + ${neo4j-cypher-dsl.version} + + + org.neo4j + neo4j-cypher-dsl-parser + ${neo4j-cypher-dsl.version} + test com.graphql-java @@ -94,6 +100,17 @@ ${junit-jupiter.version} test + + com.fasterxml.jackson.core + jackson-annotations + 2.17.2 + + + com.fasterxml.jackson.module + jackson-module-kotlin + 2.17.2 + test + diff --git a/core/src/test/kotlin/org/neo4j/graphql/utils/AsciiDocTestSuite.kt b/core/src/test/kotlin/org/neo4j/graphql/utils/AsciiDocTestSuite.kt index d11a12fc..58219385 100644 --- a/core/src/test/kotlin/org/neo4j/graphql/utils/AsciiDocTestSuite.kt +++ b/core/src/test/kotlin/org/neo4j/graphql/utils/AsciiDocTestSuite.kt @@ -1,13 +1,13 @@ package org.neo4j.graphql.utils import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.registerKotlinModule import com.intellij.rt.execution.junit.FileComparisonFailure import org.junit.jupiter.api.DynamicContainer import org.junit.jupiter.api.DynamicNode import org.junit.jupiter.api.DynamicTest import java.io.File import java.io.FileWriter -import java.math.BigInteger import java.net.URI import java.util.* import java.util.regex.Pattern @@ -57,6 +57,7 @@ open class AsciiDocTestSuite( var start: Int? = null var end: Int? = null var adjustedCode: String? = null + var reformattedCode: String? = null val code: StringBuilder = StringBuilder() fun code() = code.trim().toString() @@ -153,6 +154,10 @@ open class AsciiDocTestSuite( this@AsciiDocTestSuite::writeAdjustedTestFile ) ) + } else if (REFORMAT_TEST_FILE) { + root?.afterTests?.add( + DynamicTest.dynamicTest("Reformat Testfile", srcLocation, this@AsciiDocTestSuite::reformatTestFile) + ) } else if (GENERATE_TEST_FILE_DIFF) { // this test prints out the adjusted test file root?.afterTests?.add( @@ -207,29 +212,40 @@ open class AsciiDocTestSuite( } } + private fun reformatTestFile() { + val content = generateAdjustedFileContent { it.reformattedCode } + FileWriter(File("src/test/resources/", fileName)).use { + it.write(content) + } + } + private fun printAdjustedTestFile() { val rebuildTest = generateAdjustedFileContent() if (!Objects.equals(rebuildTest, fileContent.toString())) { // This special exception will be handled by intellij so that you can diff directly with the file throw FileComparisonFailure( - null, rebuildTest, fileContent.toString(), - null, File("src/test/resources/", fileName).absolutePath + null, fileContent.toString(), rebuildTest, + File("src/test/resources/", fileName).absolutePath, null ) } } - private fun generateAdjustedFileContent(): String { + private fun generateAdjustedFileContent(extractor: (ParsedBlock) -> String? = { it.adjustedCode }): String { knownBlocks.sortWith(compareByDescending { it.start } .thenByDescending { testCaseMarkers.indexOf(it.marker) }) val rebuildTest = StringBuffer(fileContent) - knownBlocks.filter { it.adjustedCode != null }.forEach { block -> - val start = block.start ?: throw IllegalArgumentException("unknown start position") - if (block.end == null) { - rebuildTest.insert(start, ".${block.headline}\n${block.marker}\n----\n${block.adjustedCode}\n----\n\n") - } else { - rebuildTest.replace(start, block.end!!, block.adjustedCode + "\n") + knownBlocks.filter { extractor(it) != null } + .forEach { block -> + val start = block.start ?: throw IllegalArgumentException("unknown start position") + if (block.end == null) { + rebuildTest.insert( + start, + ".${block.headline}\n${block.marker}\n----\n${extractor(block)}\n----\n\n" + ) + } else { + rebuildTest.replace(start, block.end!!, extractor(block) + "\n") + } } - } return rebuildTest.toString() } @@ -278,8 +294,9 @@ open class AsciiDocTestSuite( */ val FLATTEN_TESTS = System.getProperty("neo4j-graphql-java.flatten-tests", "false") == "true" val GENERATE_TEST_FILE_DIFF = System.getProperty("neo4j-graphql-java.generate-test-file-diff", "true") == "true" + val REFORMAT_TEST_FILE = System.getProperty("neo4j-graphql-java.reformat", "false") == "true" val UPDATE_TEST_FILE = System.getProperty("neo4j-graphql-java.update-test-file", "false") == "true" - val MAPPER = ObjectMapper() + val MAPPER = ObjectMapper().registerKotlinModule() val HEADLINE_PATTERN: Pattern = Pattern.compile("^(=+) (.*)$") const val SCHEMA_MARKER = "[source,graphql,schema=true]" @@ -330,18 +347,6 @@ open class AsciiDocTestSuite( } } - fun fixNumber(v: Any?): Any? = when (v) { - is Float -> v.toDouble() - is Int -> v.toLong() - is BigInteger -> v.toLong() - is Iterable<*> -> v.map { fixNumber(it) } - is Sequence<*> -> v.map { fixNumber(it) } - is Map<*, *> -> v.mapValues { fixNumber(it.value) } - else -> v - } - - fun fixNumbers(params: Map) = params.mapValues { (_, v) -> fixNumber(v) } - fun String.parseJsonMap(): Map = this.let { @Suppress("UNCHECKED_CAST") MAPPER.readValue(this, Map::class.java) as Map diff --git a/core/src/test/kotlin/org/neo4j/graphql/utils/CypherTestSuite.kt b/core/src/test/kotlin/org/neo4j/graphql/utils/CypherTestSuite.kt index 8892a77e..f43fdd3f 100644 --- a/core/src/test/kotlin/org/neo4j/graphql/utils/CypherTestSuite.kt +++ b/core/src/test/kotlin/org/neo4j/graphql/utils/CypherTestSuite.kt @@ -10,26 +10,32 @@ import org.assertj.core.api.InstanceOfAssertFactories import org.junit.jupiter.api.Assumptions import org.junit.jupiter.api.DynamicNode import org.junit.jupiter.api.DynamicTest +import org.neo4j.cypherdsl.core.renderer.Configuration +import org.neo4j.cypherdsl.core.renderer.Renderer +import org.neo4j.cypherdsl.parser.CypherParser +import org.neo4j.cypherdsl.parser.Options +import org.neo4j.driver.internal.InternalIsoDuration +import org.neo4j.driver.types.IsoDuration import org.neo4j.graphql.* import org.neo4j.harness.Neo4j import org.opentest4j.AssertionFailedError +import java.math.BigInteger +import java.time.Duration +import java.time.LocalTime +import java.time.Period +import java.time.ZonedDateTime +import java.time.format.DateTimeFormatter +import java.time.format.DateTimeParseException import java.util.* import java.util.concurrent.FutureTask import java.util.function.Consumer +import java.util.regex.Matcher +import java.util.regex.Pattern class CypherTestSuite(fileName: String, val neo4j: Neo4j? = null) : AsciiDocTestSuite( fileName, - listOf( - SCHEMA_CONFIG_MARKER, - GRAPHQL_MARKER, - GRAPHQL_VARIABLES_MARKER, - GRAPHQL_RESPONSE_MARKER, - GRAPHQL_RESPONSE_IGNORE_ORDER_MARKER, - QUERY_CONFIG_MARKER, - CYPHER_PARAMS_MARKER, - CYPHER_MARKER - ), - listOf(SCHEMA_MARKER, SCHEMA_CONFIG_MARKER, TEST_DATA_MARKER) + TEST_CASE_MARKERS, + GLOBAL_MARKERS ) { override fun testFactory( @@ -66,6 +72,26 @@ class CypherTestSuite(fileName: String, val neo4j: Neo4j? = null) : AsciiDocTest tests.add(integrationTest(title, globalBlocks, codeBlocks, testData, response, ignoreOrder)) } } + if (REFORMAT_TEST_FILE) { + cypherBlocks.forEach { + val statement = CypherParser.parse(it.code(), Options.defaultOptions()) + val query = Renderer.getRenderer( + Configuration + .newConfig() + .withIndentStyle(Configuration.IndentStyle.TAB) + .withPrettyPrint(true) + .build() + ).render(statement) + it.reformattedCode = query + } + getOrCreateBlocks(codeBlocks, CYPHER_PARAMS_MARKER, "Cypher Params").forEach { + val cypherParams = it.code().parseJsonMap() + it.reformattedCode = MAPPER + .writerWithDefaultPrettyPrinter() + .writeValueAsString(cypherParams.toSortedMap()) + } + + } tests.addAll(testCypher(title, cypherBlocks, result)) tests.addAll(testCypherParams(codeBlocks, result)) @@ -145,17 +171,31 @@ class CypherTestSuite(fileName: String, val neo4j: Neo4j? = null) : AsciiDocTest name += " (${index + 1})" } DynamicTest.dynamicTest(name, cypherBlock.uri) { + val cfg = Configuration.newConfig() + .withPrettyPrint(true) + .withGeneratedNames(true) + .build() + val renderer = Renderer.getRenderer(cfg) + val cypher = cypherBlock.code() - val expected = cypher.normalize() + val expectedNormalized = renderer.render(CypherParser.parse(cypher, PARSE_OPTIONS)) val actual = (result().getOrNull(index)?.query ?: throw IllegalStateException("missing cypher query for $title ($index)")) - val actualNormalized = actual.normalize() + val actualNormalized = renderer.render(CypherParser.parse(actual, PARSE_OPTIONS)) - if (!Objects.equals(expected, actual)) { + if (!Objects.equals(expectedNormalized, actual)) { cypherBlock.adjustedCode = actual } - if (actualNormalized != expected) { - throw AssertionFailedError("Cypher does not match", cypher, actual) + if (actualNormalized != expectedNormalized) { + val SPLITTER = + "\n\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n~ source query\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n" + throw AssertionFailedError( + "Cypher does not match", + expectedNormalized + SPLITTER + cypher, + actualNormalized + SPLITTER + actual + ) + // TODO + // throw AssertionFailedError("Cypher does not match", cypher, actual) } } } @@ -172,24 +212,39 @@ class CypherTestSuite(fileName: String, val neo4j: Neo4j? = null) : AsciiDocTest name += " (${index + 1})" } DynamicTest.dynamicTest(name, cypherParamsBlock.uri) { - val resultParams = result().getOrNull(index)?.params + val (cypher, params) = result().getOrNull(index) ?: throw IllegalStateException("Expected a cypher query with index $index") - val actualParams = MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(resultParams) + val actualParamsJson = MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(params) if (cypherParamsBlock.code().isBlank()) { - if (resultParams.isNotEmpty()) { - cypherParamsBlock.adjustedCode = actualParams + if (params.isNotEmpty()) { + cypherParamsBlock.adjustedCode = actualParamsJson Assertions.fail("No params defined") } return@dynamicTest } - val cypherParams = cypherParamsBlock.code().parseJsonMap() - val expected = fixNumbers(cypherParams) - val actual = fixNumbers(resultParams) + val expectedCypherParams = cypherParamsBlock.code().parseJsonMap() + val expected = fixNumbers(expectedCypherParams) + val actual = fixNumbers(actualParamsJson.parseJsonMap()) if (!Objects.equals(expected, actual)) { - cypherParamsBlock.adjustedCode = actualParams + cypherParamsBlock.adjustedCode = actualParamsJson + } + + val expectedRenamedParameters = codeBlocks[CYPHER_MARKER]?.get(index)?.code() + ?.let { CypherParser.parse(it, PARSE_OPTIONS).catalog.renamedParameters } + + if (expectedRenamedParameters != null) { + val actualRenamedParameters = CypherParser.parse(cypher, PARSE_OPTIONS).catalog + .renamedParameters + .map { (k, v) -> v to k } + .toMap() + val remappedExpectedCypherParams = expectedCypherParams.mapKeys { (k, _) -> + actualRenamedParameters[expectedRenamedParameters[k]] ?: k + } + assertCypherParams(remappedExpectedCypherParams, params) + } else { + assertCypherParams(expectedCypherParams, params) } - Assertions.assertThat(actual).isEqualTo(expected) } } } @@ -245,7 +300,7 @@ class CypherTestSuite(fileName: String, val neo4j: Neo4j? = null) : AsciiDocTest ExecutionInput.newExecutionInput() .query(request) .variables(requestParams) - .context(queryContext) + .graphQLContext(mapOf(QueryContext.KEY to queryContext)) .build() ) Assertions.assertThat(result.errors).isEmpty() @@ -291,6 +346,7 @@ class CypherTestSuite(fileName: String, val neo4j: Neo4j? = null) : AsciiDocTest companion object { private val DEBUG = System.getProperty("neo4j-graphql-java.debug", "false") == "true" + private val CONVERT_NUMBER = System.getProperty("neo4j-graphql-java.convert-number", "true") == "true" private const val TEST_DATA_MARKER = "[source,cypher,test-data=true]" private const val CYPHER_MARKER = "[source,cypher]" @@ -300,5 +356,160 @@ class CypherTestSuite(fileName: String, val neo4j: Neo4j? = null) : AsciiDocTest private const val GRAPHQL_RESPONSE_IGNORE_ORDER_MARKER = "[source,json,response=true,ignore-order]" private const val QUERY_CONFIG_MARKER = "[source,json,query-config=true]" private const val CYPHER_PARAMS_MARKER = "[source,json]" + + private val TEST_CASE_MARKERS: List = listOf( + SCHEMA_CONFIG_MARKER, + GRAPHQL_MARKER, + GRAPHQL_VARIABLES_MARKER, + GRAPHQL_RESPONSE_MARKER, + GRAPHQL_RESPONSE_IGNORE_ORDER_MARKER, + QUERY_CONFIG_MARKER, + CYPHER_PARAMS_MARKER, + CYPHER_MARKER + ) + private val GLOBAL_MARKERS: List = listOf(SCHEMA_MARKER, SCHEMA_CONFIG_MARKER, TEST_DATA_MARKER) + + private val DURATION_PATTERN: Pattern = Pattern.compile("^P(.*?)(?:T(.*))?$") + + private val PARSE_OPTIONS = Options.newOptions() + .createSortedMaps(true) + // TODO enable after https://github.com/neo4j/cypher-dsl/issues/1059 is solved + // .alwaysCreateRelationshipsLTR(true) + .build() + + private fun assertCypherParams(expected: Map, actual: Map) { + Assertions.assertThat(actual).asInstanceOf(InstanceOfAssertFactories.MAP) + .hasSize(expected.size) + .containsOnlyKeys(*expected.keys.toTypedArray()) + .allSatisfy({ key, value -> assertCypherParam(expected[key], value) }) + } + + private fun assertCypherParam(expected: Any?, actual: Any?) { + when (expected) { + is Int -> when (actual) { + is Double -> { + Assertions.assertThat(actual).isEqualTo(expected.toDouble()) + return + } + + is Long -> { + Assertions.assertThat(actual).isEqualTo(expected.toLong()) + return + } + + is BigInteger -> { + Assertions.assertThat(actual).isEqualTo(expected.toBigInteger()) + return + } + } + + is String -> { + try { + val expectedDate = ZonedDateTime.parse(expected) + val actualDate = ZonedDateTime.parse(actual as String) + Assertions.assertThat(actualDate).isEqualTo(expectedDate) + return + } catch (ignore: DateTimeParseException) { + } + try { + val expectedTime = LocalTime.parse(expected, DateTimeFormatter.ISO_TIME) + val actualTime = LocalTime.parse(actual as String) + Assertions.assertThat(actualTime).isEqualTo(expectedTime) + return + } catch (ignore: DateTimeParseException) { + } + try { + val expectedTime = LocalTime.parse(expected, DateTimeFormatter.ISO_TIME) + val actualTime = LocalTime.parse(actual as String) + Assertions.assertThat(actualTime).isEqualTo(expectedTime) + return + } catch (ignore: DateTimeParseException) { + } + try { + parseDuration(expected)?.let { expectedDuration -> + parseDuration(actual as String)?.let { actualDuration -> + Assertions.assertThat(actualDuration).isEqualTo(expectedDuration) + return + } + } + } catch (ignore: DateTimeParseException) { + } + } + + is Map<*, *> -> { + if (actual is Number) { + val low = expected["low"] as? Number + val high = expected["high"] as? Number + if (low != null && high != null && expected.size == 2) { + // this is to convert the js bigint into a java long + val highLong = high.toLong() shl 32 + val lowLong = low.toLong() and 0xFFFFFFFFL + val expectedLong = highLong or lowLong + when (actual) { + is BigInteger -> Assertions.assertThat(actual.toLong()).isEqualTo(expectedLong) + is Int -> Assertions.assertThat(actual.toLong()).isEqualTo(expectedLong) + is Long -> Assertions.assertThat(actual).isEqualTo(expectedLong) + else -> Assertions.fail("Unexpected type ${actual::class.java}") + } + return + } + } + } + + is Iterable<*> -> { + if (actual is Iterable<*>) { + Assertions.assertThat(actual).hasSize(expected.count()) + expected.forEachIndexed { index, expectedValue -> + assertCypherParam(expectedValue, actual.elementAt(index)) + } + return + } + } + } + Assertions.assertThat(actual).isEqualTo(expected) + } + + private fun fixNumber(v: Any?): Any? = when (v) { + is Double -> v.toDouble().convertNumber() + is Float -> v.toDouble().convertNumber() + is Int -> v.toLong() + is BigInteger -> v.toLong() + is Iterable<*> -> v.map { fixNumber(it) } + is Sequence<*> -> v.map { fixNumber(it) } + is Map<*, *> -> { + val low = v["low"] as? Number + val high = v["high"] as? Number + if (low != null && high != null && v.size == 2) { + // this is to convert th js bigint into a java long + val highLong = high.toLong() shl 32 + val lowLong = low.toLong() and 0xFFFFFFFFL + highLong or lowLong + } else { + v.mapValues { fixNumber(it.value) } + } + } + + else -> v + } + + private fun fixNumbers(params: Map) = params.mapValues { (_, v) -> fixNumber(v) } + private fun Double.convertNumber() = when { + CONVERT_NUMBER && this == toLong().toDouble() -> toLong() + else -> this + } + + private fun parseDuration(text: String): IsoDuration? { + val matcher: Matcher = DURATION_PATTERN.matcher(text) + if (!matcher.find()) { + return null + } + val periodString = matcher.group(1) + val timeString = matcher.group(2) + val period = if (!periodString.isNullOrBlank()) Period.parse("P$periodString") else Period.ZERO + val duration = if (!timeString.isNullOrBlank()) Duration.parse("PT$timeString") else Duration.ZERO + return InternalIsoDuration(period.toTotalMonths(), period.days.toLong(), duration.seconds, duration.nano) + } + + } } diff --git a/core/src/test/kotlin/org/neo4j/graphql/utils/GraphQLSchemaTestSuite.kt b/core/src/test/kotlin/org/neo4j/graphql/utils/GraphQLSchemaTestSuite.kt index 8f1c4ae0..694fc2a8 100644 --- a/core/src/test/kotlin/org/neo4j/graphql/utils/GraphQLSchemaTestSuite.kt +++ b/core/src/test/kotlin/org/neo4j/graphql/utils/GraphQLSchemaTestSuite.kt @@ -1,9 +1,9 @@ package org.neo4j.graphql.utils import graphql.language.InterfaceTypeDefinition +import graphql.language.UnionTypeDefinition import graphql.schema.GraphQLScalarType import graphql.schema.GraphQLSchema -import graphql.schema.GraphQLType import graphql.schema.diff.SchemaDiff import graphql.schema.diff.SchemaDiffSet import graphql.schema.diff.reporting.CapturingReporter @@ -16,15 +16,10 @@ import org.junit.jupiter.api.DynamicTest import org.neo4j.graphql.NoOpCoercing import org.neo4j.graphql.SchemaBuilder import org.neo4j.graphql.SchemaConfig -import org.neo4j.graphql.requiredName import org.opentest4j.AssertionFailedError import java.util.* -import java.util.regex.Pattern -class GraphQLSchemaTestSuite(fileName: String) : AsciiDocTestSuite( - fileName, - listOf(SCHEMA_CONFIG_MARKER, GRAPHQL_MARKER) -) { +class GraphQLSchemaTestSuite(fileName: String) : AsciiDocTestSuite(fileName, TEST_CASE_MARKERS) { override fun testFactory( title: String, @@ -33,6 +28,12 @@ class GraphQLSchemaTestSuite(fileName: String) : AsciiDocTestSuite( ignore: Boolean ): List { val targetSchemaBlock = codeBlocks[GRAPHQL_MARKER]?.first() + targetSchemaBlock?.let { + try { + it.reformattedCode = SCHEMA_PRINTER.print(createMockSchema(it.code())) + } catch (ignore: Exception) { + } + } val compareSchemaTest = DynamicTest.dynamicTest("compare schema", targetSchemaBlock?.uri) { val configBlock = codeBlocks[SCHEMA_CONFIG_MARKER]?.first() val config = configBlock?.code()?.let { MAPPER.readValue(it, SchemaConfig::class.java) } ?: SchemaConfig() @@ -46,27 +47,7 @@ class GraphQLSchemaTestSuite(fileName: String) : AsciiDocTestSuite( val schema = globalBlocks[SCHEMA_MARKER]?.first()?.code() ?: throw IllegalStateException("Schema should be defined") augmentedSchema = SchemaBuilder.buildSchema(schema, config) - val schemaParser = SchemaParser() - - val reg = schemaParser.parse(targetSchema) - val schemaGenerator = SchemaGenerator() - val runtimeWiring = RuntimeWiring.newRuntimeWiring() - reg - .getTypes(InterfaceTypeDefinition::class.java) - .forEach { typeDefinition -> runtimeWiring.type(typeDefinition.name) { it.typeResolver { null } } } - reg - .scalars() - .filterNot { entry -> ScalarInfo.GRAPHQL_SPECIFICATION_SCALARS_DEFINITIONS.containsKey(entry.key) } - .forEach { (name, definition) -> - runtimeWiring.scalar( - GraphQLScalarType.newScalar() - .name(name) - .definition(definition) - .coercing(NoOpCoercing) - .build() - ) - } - expectedSchema = schemaGenerator.makeExecutableSchema(reg, runtimeWiring.build()) + expectedSchema = createMockSchema(targetSchema) diff(expectedSchema, augmentedSchema) diff(augmentedSchema, expectedSchema) @@ -91,9 +72,33 @@ class GraphQLSchemaTestSuite(fileName: String) : AsciiDocTestSuite( return Collections.singletonList(compareSchemaTest) } + private fun createMockSchema(targetSchema: String): GraphQLSchema { + val schemaParser = SchemaParser() + + val reg = schemaParser.parse(targetSchema) + val schemaGenerator = SchemaGenerator() + val runtimeWiring = RuntimeWiring.newRuntimeWiring() + (reg.getTypes(InterfaceTypeDefinition::class.java) + + reg.getTypes(UnionTypeDefinition::class.java)) + .forEach { typeDefinition -> runtimeWiring.type(typeDefinition.name) { it.typeResolver { null } } } + reg + .scalars() + .filterNot { entry -> ScalarInfo.GRAPHQL_SPECIFICATION_SCALARS_DEFINITIONS.containsKey(entry.key) } + .forEach { (name, definition) -> + runtimeWiring.scalar( + GraphQLScalarType.newScalar() + .name(name) + .definition(definition) + .coercing(NoOpCoercing) + .build() + ) + } + return schemaGenerator.makeExecutableSchema(reg, runtimeWiring.build()) + } + companion object { private const val GRAPHQL_MARKER = "[source,graphql]" - private val METHOD_PATTERN = Pattern.compile("(add|delete|update|merge|create)(.*)") + private val TEST_CASE_MARKERS: List = listOf(SCHEMA_CONFIG_MARKER, GRAPHQL_MARKER) private val SCHEMA_PRINTER = SchemaPrinter( SchemaPrinter.Options.defaultOptions() @@ -103,15 +108,6 @@ class GraphQLSchemaTestSuite(fileName: String) : AsciiDocTestSuite( .includeIntrospectionTypes(false) ) - fun GraphQLType.splitName(): Pair { - val m = METHOD_PATTERN.matcher(this.requiredName()) - return if (m.find()) { - m.group(1) to m.group(2).lowercase() - } else { - null to this.requiredName().lowercase() - } - } - fun diff(augmentedSchema: GraphQLSchema, expected: GraphQLSchema) { val diffSet = SchemaDiffSet.diffSetFromIntrospection(augmentedSchema, expected) val capture = CapturingReporter() diff --git a/core/src/test/resources/cypher-directive-tests.adoc b/core/src/test/resources/cypher-directive-tests.adoc index f35f4963..5d1f1d81 100644 --- a/core/src/test/resources/cypher-directive-tests.adoc +++ b/core/src/test/resources/cypher-directive-tests.adoc @@ -17,7 +17,7 @@ type Query { person : [Person] p2: [Person] @cypher(statement:"MATCH (p:Person) RETURN p") p3(name:String): Person @cypher(statement:"MATCH (p:Person) WHERE p.name = name RETURN p") - getUser(userId: ID): UserData @cypher(statement: "MATCH (u:User{id: {userId}})-[:CREATED_MAP]->(m:Map) WITH collect({id: m.id, name: m.name}) AS mapsCreated, u RETURN {firstName: u.firstName, lastName: u.lastName, organization: u.organization, mapsCreated: mapsCreated}", passThrough:true) + getUser(userId: ID): UserData @cypher(statement: "MATCH (u:User{id: userId})-[:CREATED_MAP]->(m:Map) WITH collect({id: m.id, name: m.name}) AS mapsCreated, u RETURN {firstName: u.firstName, lastName: u.lastName, organization: u.organization, mapsCreated: mapsCreated}", passThrough:true) } type Mutation { createPerson(name:String): Person @cypher(statement:"CREATE (p:Person) SET p.name = name RETURN p") @@ -408,7 +408,7 @@ query queriesRootQuery { ---- CALL { WITH $userUserId AS userId - MATCH (u:User{id: {userId}})-[:CREATED_MAP]->(m:Map) WITH collect({id: m.id, name: m.name}) AS mapsCreated, u RETURN {firstName: u.firstName, lastName: u.lastName, organization: u.organization, mapsCreated: mapsCreated} AS user LIMIT 1 + MATCH (u:User{id: userId})-[:CREATED_MAP]->(m:Map) WITH collect({id: m.id, name: m.name}) AS mapsCreated, u RETURN {firstName: u.firstName, lastName: u.lastName, organization: u.organization, mapsCreated: mapsCreated} AS user LIMIT 1 } RETURN user AS user ---- diff --git a/pom.xml b/pom.xml index f74bfce2..8b9e9fe2 100755 --- a/pom.xml +++ b/pom.xml @@ -28,6 +28,7 @@ 5.23.0 5.23.0 5.11.0 + 2024.0.3