diff --git a/.github/workflows/pr-build.yaml b/.github/workflows/pr-build.yaml index f255ef93..44df6387 100644 --- a/.github/workflows/pr-build.yaml +++ b/.github/workflows/pr-build.yaml @@ -20,9 +20,7 @@ jobs: java-version: 11 distribution: adopt - name: Run Maven build -# TODO after fixing all integration tests -# run: ./mvnw --no-transfer-progress -Dneo4j-graphql-java.integration-tests=true clean compile test - run: ./mvnw --no-transfer-progress clean compile test + run: ./mvnw --no-transfer-progress -Dneo4j-graphql-java.integration-tests=true -Dneo4j-graphql-java.generate-test-file-diff=false clean compile test - name: Publish Unit Test Results uses: EnricoMi/publish-unit-test-result-action@v1 if: always() diff --git a/core/src/test/kotlin/org/neo4j/graphql/CypherTests.kt b/core/src/test/kotlin/org/neo4j/graphql/CypherTests.kt index a66dc7ac..ea77e6f1 100644 --- a/core/src/test/kotlin/org/neo4j/graphql/CypherTests.kt +++ b/core/src/test/kotlin/org/neo4j/graphql/CypherTests.kt @@ -1,5 +1,6 @@ package org.neo4j.graphql +import apoc.coll.Coll import apoc.cypher.CypherFunctions import org.junit.jupiter.api.* import org.neo4j.graphql.utils.CypherTestSuite @@ -22,6 +23,8 @@ class CypherTests { .newInProcessBuilder(Path.of("target/test-db")) .withProcedure(apoc.cypher.Cypher::class.java) .withFunction(CypherFunctions::class.java) + .withProcedure(Coll::class.java) + .withFunction(Coll::class.java) .build() } } 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 e6bc9124..ddbcc892 100644 --- a/core/src/test/kotlin/org/neo4j/graphql/utils/CypherTestSuite.kt +++ b/core/src/test/kotlin/org/neo4j/graphql/utils/CypherTestSuite.kt @@ -1,5 +1,10 @@ package org.neo4j.graphql.utils +import graphql.ExecutionInput +import graphql.GraphQL +import graphql.schema.DataFetcher +import graphql.schema.DataFetchingEnvironment +import graphql.schema.GraphQLSchema import org.assertj.core.api.Assertions import org.junit.jupiter.api.Assumptions import org.junit.jupiter.api.DynamicNode @@ -44,9 +49,8 @@ class CypherTestSuite(fileName: String, val neo4j: Neo4j? = null) : AsciiDocTest if (neo4j != null) { val testData = globalBlocks[TEST_DATA_MARKER] val response = getOrCreateBlock(codeBlocks, GRAPHQL_RESPONSE_MARKER, "GraphQL-Response") - if (testData != null && response != null) { - tests.add(integrationTest(testData, response, result)) + tests.add(integrationTest(title, globalBlocks, codeBlocks, testData, response)) } } @@ -56,17 +60,27 @@ class CypherTestSuite(fileName: String, val neo4j: Neo4j? = null) : AsciiDocTest return tests } - private fun createTransformationTask(title: String, globalBlocks: Map, codeBlocks: Map): () -> Cypher { + private fun createSchema( + globalBlocks: Map, + codeBlocks: Map, + dataFetchingInterceptor: DataFetchingInterceptor? = null + ): GraphQLSchema { val schemaString = globalBlocks[SCHEMA_MARKER]?.code() ?: throw IllegalStateException("Schema should be defined") + val schemaConfig = (codeBlocks[SCHEMA_CONFIG_MARKER] ?: globalBlocks[SCHEMA_CONFIG_MARKER])?.code() + ?.let { return@let MAPPER.readValue(it, SchemaConfig::class.java) } + ?: SchemaConfig() + return SchemaBuilder.buildSchema(schemaString, schemaConfig, dataFetchingInterceptor) + } + private fun createTransformationTask( + title: String, + globalBlocks: Map, + codeBlocks: Map + ): () -> Cypher { val transformationTask = FutureTask { - val schemaConfig = (codeBlocks[SCHEMA_CONFIG_MARKER] ?: globalBlocks[SCHEMA_CONFIG_MARKER])?.code() - ?.let { return@let MAPPER.readValue(it, SchemaConfig::class.java) } - ?: SchemaConfig() - val schema = SchemaBuilder.buildSchema(schemaString, schemaConfig) - + val schema = createSchema(globalBlocks, codeBlocks) val request = codeBlocks[GRAPHQL_MARKER]?.code() ?: throw IllegalStateException("missing graphql for $title") @@ -139,37 +153,72 @@ class CypherTestSuite(fileName: String, val neo4j: Neo4j? = null) : AsciiDocTest } } - private fun integrationTest(testData: ParsedBlock, response: ParsedBlock, result: () -> Cypher): DynamicNode = DynamicTest.dynamicTest("Integration Test", response.uri) { - neo4j?.defaultDatabaseService()?.let { db -> - db.executeTransactionally("MATCH (n) DETACH DELETE n") - if (testData.code().isNotBlank()) { - testData.code() - .split(";") - .filter { it.isNotBlank() } - .forEach { db.executeTransactionally(it) } - } - val (cypher, params, type, variable) = result() - val values = db.executeTransactionally(cypher, params) { result -> - mutableMapOf(variable to result.stream().map { it[variable] }.let { - when { - type?.isList() == true -> it.toList() - else -> it.findFirst().orElse(null) + private fun setupDataFetchingInterceptor(testData: ParsedBlock): DataFetchingInterceptor { + return object : DataFetchingInterceptor { + override fun fetchData(env: DataFetchingEnvironment, delegate: DataFetcher): Any? = neo4j + ?.defaultDatabaseService()?.let { db -> + db.executeTransactionally("MATCH (n) DETACH DELETE n") + if (testData.code().isNotBlank()) { + testData.code() + .split(";") + .filter { it.isNotBlank() } + .forEach { db.executeTransactionally(it) } } - }) - } + val (cypher, params, type, variable) = delegate.get(env) + return db.executeTransactionally(cypher, params) { result -> + result.stream().map { it[variable] }.let { + when { + type?.isList() == true -> it.toList() + else -> it.findFirst().orElse(null) + } + } - if (response.code.isEmpty()) { + } + } + } + } + + private fun integrationTest( + title: String, + globalBlocks: Map, + codeBlocks: Map, + testData: ParsedBlock, + response: ParsedBlock + ): DynamicNode = DynamicTest.dynamicTest("Integration Test", response.uri) { + val dataFetchingInterceptor = setupDataFetchingInterceptor(testData) + val request = codeBlocks[GRAPHQL_MARKER]?.code() + ?: throw IllegalStateException("missing graphql for $title") + + + val requestParams = codeBlocks[GRAPHQL_VARIABLES_MARKER]?.code()?.parseJsonMap() ?: emptyMap() + + val queryContext = codeBlocks[QUERY_CONFIG_MARKER]?.code() + ?.let { config -> return@let MAPPER.readValue(config, QueryContext::class.java) } + ?: QueryContext() + + + val schema = createSchema(globalBlocks, codeBlocks, dataFetchingInterceptor) + val graphql = GraphQL.newGraphQL(schema).build() + val result = graphql.execute(ExecutionInput.newExecutionInput() + .query(request) + .variables(requestParams) + .context(queryContext) + .build()) + Assertions.assertThat(result.errors).isEmpty() + + val values = result?.getData() + + if (response.code.isEmpty()) { + val actualCode = MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(values) + response.adjustedCode = actualCode + } else { + val expected = fixNumbers(response.code().parseJsonMap()) + val actual = fixNumber(values) + if (!Objects.equals(expected, actual)) { val actualCode = MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(values) response.adjustedCode = actualCode - } else { - val expected = fixNumbers(response.code().parseJsonMap()) - val actual = fixNumber(values) - if (!Objects.equals(expected, actual)) { - val actualCode = MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(values) - response.adjustedCode = actualCode - } - Assertions.assertThat(actual).isEqualTo(expected) } + Assertions.assertThat(actual).isEqualTo(expected) } } diff --git a/core/src/test/resources/filter-tests.adoc b/core/src/test/resources/filter-tests.adoc index 3710d72f..96e1dc3d 100644 --- a/core/src/test/resources/filter-tests.adoc +++ b/core/src/test/resources/filter-tests.adoc @@ -3206,7 +3206,7 @@ RETURN p { .GraphQL-Query [source,graphql] ---- -{ p: company { employees(filter: { OR: [{ name: "Jane" },{name:"Joe"}]}) { name }}} +{ p: company { employees(filter: { OR: [{ name: "Jane" },{name:"Joe"}]}, orderBy: name_desc) { name }}} ---- .GraphQL-Response @@ -3239,10 +3239,10 @@ RETURN p { ---- MATCH (p:Company) RETURN p { - employees: [(p)<-[:WORKS_AT]-(pEmployees:Person) WHERE (pEmployees.name = $filterPEmployeesOr1Name - OR pEmployees.name = $filterPEmployeesOr2Name) | pEmployees { + employees: apoc.coll.sortMulti([(p)<-[:WORKS_AT]-(pEmployees:Person) WHERE (pEmployees.name = $filterPEmployeesOr1Name + OR pEmployees.name = $filterPEmployeesOr2Name) | pEmployees { .name - }] + }], ['name']) } AS p ---- diff --git a/core/src/test/resources/issues/gh-112.adoc b/core/src/test/resources/issues/gh-112.adoc index 112879a4..0953179d 100644 --- a/core/src/test/resources/issues/gh-112.adoc +++ b/core/src/test/resources/issues/gh-112.adoc @@ -31,7 +31,7 @@ CREATE .GraphQL-Query [source,graphql] ---- -query { +query user( $uuid: ID ){ user(uuid: $uuid) { uuid name diff --git a/core/src/test/resources/issues/gh-147.adoc b/core/src/test/resources/issues/gh-147.adoc index e82d024a..260e7cb2 100644 --- a/core/src/test/resources/issues/gh-147.adoc +++ b/core/src/test/resources/issues/gh-147.adoc @@ -60,7 +60,9 @@ query { person(name: "Kevin Bacon") { born ... on Actor { + __typename namedColleagues(name: "Meg") { + __typename ... name } } @@ -75,11 +77,13 @@ fragment name on Actor { name } ---- { "person" : [ { + "born" : 1958, + "__typename" : "Actor", "namedColleagues" : [ { + "__typename" : "Actor", "name" : "Meg Ryan" } ], - "score" : 7, - "born" : 1958 + "score" : 7 } ] } ---- @@ -90,7 +94,9 @@ fragment name on Actor { name } { "personName" : "Kevin Bacon", "personNamedColleaguesName" : "Meg", - "personScoreValue" : 7 + "personNamedColleaguesValidTypes" : [ "Actor" ], + "personScoreValue" : 7, + "personValidTypes" : [ "Actor" ] } ---- @@ -101,10 +107,12 @@ MATCH (person:Person) WHERE person.name = $personName RETURN person { .born, + __typename: head([label IN labels(person) WHERE label IN $personValidTypes]), namedColleagues: [personNamedColleagues IN apoc.cypher.runFirstColumnMany('WITH $this AS this, $name AS name WITH $this AS this MATCH (this)-[:ACTED_IN]->()<-[:ACTED_IN]-(other) WHERE other.name CONTAINS $name RETURN other', { this: person, name: $personNamedColleaguesName }) | personNamedColleagues { + __typename: head([label IN labels(personNamedColleagues) WHERE label IN $personNamedColleaguesValidTypes]), .name }], score: apoc.cypher.runFirstColumnSingle('WITH $this AS this, $value AS value RETURN $value', {