From 171a7e7dfa3be838a3443e87d23691d72d2fc60b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20Barc=C3=A9los?= Date: Thu, 17 Sep 2020 12:08:18 +0200 Subject: [PATCH 1/6] Rename tests end with BoltKitTest to the BoltKitIT (#760) The BoltKitTests depends on the TeamCity environment with the new test environment be running to not be skipped, so it should be moved to the integration tests phase avoiding being always skipped during the unit tests phase. Rename it to `BoltKitIT` does this job. --- ...erBoltKitTest.java => RoutingDriverBoltKitIT.java} | 11 +++++------ ....java => RoutingDriverMultidatabaseBoltKitIT.java} | 4 ++-- ...verBoltKitTest.java => DirectDriverBoltKitIT.java} | 2 +- 3 files changed, 8 insertions(+), 9 deletions(-) rename driver/src/test/java/org/neo4j/driver/integration/{RoutingDriverBoltKitTest.java => RoutingDriverBoltKitIT.java} (99%) rename driver/src/test/java/org/neo4j/driver/integration/{RoutingDriverMultidatabaseBoltKitTest.java => RoutingDriverMultidatabaseBoltKitIT.java} (98%) rename driver/src/test/java/org/neo4j/driver/internal/{DirectDriverBoltKitTest.java => DirectDriverBoltKitIT.java} (99%) diff --git a/driver/src/test/java/org/neo4j/driver/integration/RoutingDriverBoltKitTest.java b/driver/src/test/java/org/neo4j/driver/integration/RoutingDriverBoltKitIT.java similarity index 99% rename from driver/src/test/java/org/neo4j/driver/integration/RoutingDriverBoltKitTest.java rename to driver/src/test/java/org/neo4j/driver/integration/RoutingDriverBoltKitIT.java index c1b55a3177..e586750d66 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/RoutingDriverBoltKitTest.java +++ b/driver/src/test/java/org/neo4j/driver/integration/RoutingDriverBoltKitIT.java @@ -36,7 +36,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Function; import org.neo4j.driver.AccessMode; import org.neo4j.driver.AuthToken; @@ -92,7 +91,7 @@ import static org.neo4j.driver.util.StubServer.insecureBuilder; import static org.neo4j.driver.util.TestUtil.asOrderedSet; -class RoutingDriverBoltKitTest +class RoutingDriverBoltKitIT { private static StubServerController stubController; @@ -568,7 +567,7 @@ void shouldHandleLeaderSwitchAndRetryWhenWritingInTxFunction() throws IOExceptio catch ( InterruptedException ex ) { } - return tx.run( "MATCH (n) RETURN n.name" ).list( RoutingDriverBoltKitTest::extractNameField ); + return tx.run( "MATCH (n) RETURN n.name" ).list( RoutingDriverBoltKitIT::extractNameField ); } ); } @@ -604,7 +603,7 @@ void shouldHandleLeaderSwitchAndRetryWhenWritingInTxFunctionAsync() throws IOExc } return tx.runAsync( "MATCH (n) RETURN n.name" ); } ) - .thenComposeAsync( cursor -> cursor.listAsync( RoutingDriverBoltKitTest::extractNameField ) ) ) ); + .thenComposeAsync( cursor -> cursor.listAsync( RoutingDriverBoltKitIT::extractNameField ) ) ) ); assertEquals( asList( "Foo", "Bar" ), names ); @@ -639,7 +638,7 @@ void shouldHandleLeaderSwitchAndRetryWhenWritingInTxFunctionRX() throws IOExcept { RxResult result = tx.run( "RETURN 1" ); return Flux.from( result.records() ).limitRate( 100 ).thenMany( tx.run( "MATCH (n) RETURN n.name" ).records() ).limitRate( 100 ).map( - RoutingDriverBoltKitTest::extractNameField ); + RoutingDriverBoltKitIT::extractNameField ); } ), RxSession::close ); StepVerifier.create( fluxOfNames ).expectNext( "Foo", "Bar" ).verifyComplete(); @@ -1255,7 +1254,7 @@ void shouldUseResolverDuringRediscoveryWhenExistingRoutersFail() throws Exceptio assertEquals( asList( "Alice", "Bob", "Eve" ), names1 ); // run second query with retries, it should rediscover using 9042 returned by the resolver and read from 9005 - List names2 = session.readTransaction( tx -> tx.run( "MATCH (n) RETURN n.name" ).list( RoutingDriverBoltKitTest::extractNameField ) ); + List names2 = session.readTransaction( tx -> tx.run( "MATCH (n) RETURN n.name" ).list( RoutingDriverBoltKitIT::extractNameField ) ); assertEquals( asList( "Bob", "Alice", "Tina" ), names2 ); } } diff --git a/driver/src/test/java/org/neo4j/driver/integration/RoutingDriverMultidatabaseBoltKitTest.java b/driver/src/test/java/org/neo4j/driver/integration/RoutingDriverMultidatabaseBoltKitIT.java similarity index 98% rename from driver/src/test/java/org/neo4j/driver/integration/RoutingDriverMultidatabaseBoltKitTest.java rename to driver/src/test/java/org/neo4j/driver/integration/RoutingDriverMultidatabaseBoltKitIT.java index 91a4d3c9a3..aa8f3141da 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/RoutingDriverMultidatabaseBoltKitTest.java +++ b/driver/src/test/java/org/neo4j/driver/integration/RoutingDriverMultidatabaseBoltKitIT.java @@ -34,7 +34,7 @@ import org.neo4j.driver.Session; import org.neo4j.driver.exceptions.FatalDiscoveryException; import org.neo4j.driver.exceptions.ServiceUnavailableException; -import org.neo4j.driver.integration.RoutingDriverBoltKitTest.PortBasedServerAddressComparator; +import org.neo4j.driver.integration.RoutingDriverBoltKitIT.PortBasedServerAddressComparator; import org.neo4j.driver.Bookmark; import org.neo4j.driver.net.ServerAddress; import org.neo4j.driver.net.ServerAddressResolver; @@ -51,7 +51,7 @@ import static org.neo4j.driver.util.StubServer.INSECURE_CONFIG; import static org.neo4j.driver.util.StubServer.insecureBuilder; -class RoutingDriverMultidatabaseBoltKitTest +class RoutingDriverMultidatabaseBoltKitIT { @Test void shouldDiscoverForDatabase() throws IOException, InterruptedException, StubServer.ForceKilled diff --git a/driver/src/test/java/org/neo4j/driver/internal/DirectDriverBoltKitTest.java b/driver/src/test/java/org/neo4j/driver/internal/DirectDriverBoltKitIT.java similarity index 99% rename from driver/src/test/java/org/neo4j/driver/internal/DirectDriverBoltKitTest.java rename to driver/src/test/java/org/neo4j/driver/internal/DirectDriverBoltKitIT.java index 79358a8017..e8f5fdc066 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/DirectDriverBoltKitTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/DirectDriverBoltKitIT.java @@ -82,7 +82,7 @@ import static org.neo4j.driver.util.TestUtil.asOrderedSet; import static org.neo4j.driver.util.TestUtil.await; -class DirectDriverBoltKitTest +class DirectDriverBoltKitIT { private static StubServerController stubController; From cecf1bba226ab7e2669eef2b1de17ff04994cf0e Mon Sep 17 00:00:00 2001 From: gjmwoods <42248895+gjmwoods@users.noreply.github.com> Date: Mon, 21 Sep 2020 16:02:27 +0100 Subject: [PATCH 2/6] 4.2 TestKit Backend (#761) This change introduces a testing component that allows for tests to be written once in a central repo (https://github.com/neo4j-drivers/testkit) and then executed for each driver. The build produces an executable server that acts as the intermediatory with TestKit. Co-authored-by: Peter Wilhelmsson --- pom.xml | 1 + testkit-backend/pom.xml | 65 ++++ .../src/main/java/CommandProcessor.java | 347 ++++++++++++++++++ testkit-backend/src/main/java/Runner.java | 56 +++ .../src/main/java/SessionState.java | 33 ++ testkit-backend/src/main/java/Testkit.java | 44 +++ .../src/main/java/TestkitTypes.java | 123 +++++++ 7 files changed, 669 insertions(+) create mode 100644 testkit-backend/pom.xml create mode 100644 testkit-backend/src/main/java/CommandProcessor.java create mode 100644 testkit-backend/src/main/java/Runner.java create mode 100644 testkit-backend/src/main/java/SessionState.java create mode 100644 testkit-backend/src/main/java/Testkit.java create mode 100644 testkit-backend/src/main/java/TestkitTypes.java diff --git a/pom.xml b/pom.xml index d81d1a04dc..e950a0ca2b 100644 --- a/pom.xml +++ b/pom.xml @@ -28,6 +28,7 @@ driver examples + testkit-backend diff --git a/testkit-backend/pom.xml b/testkit-backend/pom.xml new file mode 100644 index 0000000000..5298e6bf11 --- /dev/null +++ b/testkit-backend/pom.xml @@ -0,0 +1,65 @@ + + + + neo4j-java-driver-parent + org.neo4j.driver + 4.2-SNAPSHOT + + 4.0.0 + + + ${project.basedir}/.. + + + testkit-backend + + Neo4j Java Driver Testkit Backend + Integration component for use with Testkit + + + + org.neo4j.driver + neo4j-java-driver + ${project.version} + + + com.fasterxml.jackson.core + jackson-core + 2.11.0 + + + com.fasterxml.jackson.core + jackson-databind + 2.11.0 + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.4 + + + package + + shade + + + + + Runner + + + testkit-backend + + + + + + + + diff --git a/testkit-backend/src/main/java/CommandProcessor.java b/testkit-backend/src/main/java/CommandProcessor.java new file mode 100644 index 0000000000..a594d1e764 --- /dev/null +++ b/testkit-backend/src/main/java/CommandProcessor.java @@ -0,0 +1,347 @@ +/* + * Copyright (c) 2002-2020 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import org.neo4j.driver.AccessMode; +import org.neo4j.driver.AuthTokens; +import org.neo4j.driver.AuthToken; +import org.neo4j.driver.Bookmark; +import org.neo4j.driver.Driver; +import org.neo4j.driver.GraphDatabase; +import org.neo4j.driver.Query; +import org.neo4j.driver.Record; +import org.neo4j.driver.Result; +import org.neo4j.driver.Session; +import org.neo4j.driver.SessionConfig; +import org.neo4j.driver.Transaction; +import org.neo4j.driver.TransactionWork; +import org.neo4j.driver.exceptions.Neo4jException; +import org.neo4j.driver.exceptions.ClientException; +import org.neo4j.driver.internal.InternalBookmark; + +public class CommandProcessor +{ + private final Map drivers = new HashMap<>(); + private final Map sessionStates = new HashMap<>(); + private final Map results = new HashMap<>(); + private final Map transactions = new HashMap<>(); + private final Map errors = new HashMap<>(); + + private int idGenerator = 0; + + private final ObjectMapper objectMapper = new ObjectMapper(); + + private final BufferedReader in; + private final BufferedWriter out; + + public CommandProcessor(BufferedReader in, BufferedWriter out) { + this.in = in; + this.out = out; + } + + private String readLine() { + try { + return this.in.readLine(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private void write(String s) { + try { + this.out.write(s); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + // Logs to frontend + private void log(String s) { + try { + this.out.write(s + "\n"); + this.out.flush(); + } catch (IOException e) { } + System.out.println(s); + } + + private void flush() { + try { + this.out.flush(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + // Reads one request and writes the response. Returns false when not able to read anymore. + public boolean process() { + boolean inRequest = false; + StringBuilder request = new StringBuilder(); + + log("Waiting for request"); + + while (true) { + String currentLine = readLine(); + // End of stream + if ( currentLine == null) { + return false; + } + + if ( currentLine.equals( "#request begin" )) + { + inRequest = true; + } else if (currentLine.equals( "#request end" )) + { + if ( !inRequest ) + { + throw new RuntimeException( "Request end not expected" ); + } + try + { + processRequest( request.toString()); + } catch ( Exception e ){ + if (e instanceof Neo4jException) { + // Error to track + String id = newId(); + errors.put(id, (Neo4jException)e); + writeResponse(Testkit.wrap("DriverError", Testkit.id(id))); + System.out.println("Neo4jException: " + e); + } else { + // Unknown error, interpret this as a backend error. + // Report to frontend and rethrow, note that if socket been + // closed the writing will throw itself... + writeResponse(Testkit.wrap("BackendError", Testkit.msg(e.toString()))); + // This won't print if there was an IO exception since line above will rethrow + e.printStackTrace(); + throw e; + } + } + return true; + } else + { + if ( !inRequest ) + { + throw new RuntimeException( "Command Received whilst not in request"); + } + request.append( currentLine ); + } + } + } + + public void processRequest( String request) + { + System.out.println( "request = " + request + ", in = " + in + ", out = " + out ); + + JsonNode jsonRequest; + try { + jsonRequest = objectMapper.readTree( request ); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + + String requestType = jsonRequest.get( "name" ).asText(); + JsonNode requestData = jsonRequest.get("data"); + log("Received request: " + requestType); + + if (requestType.equals( "NewDriver" )) + { + String id = newId(); + String response = Testkit.wrap("Driver", Testkit.id(id)); + + String uri = requestData.get("uri").asText(); + JsonNode requestAuth = requestData.get("authorizationToken").get("data"); + AuthToken authToken; + switch (requestAuth.get("scheme").asText()) { + case "basic": + authToken = AuthTokens.basic(requestAuth.get("principal").asText(), requestAuth.get("credentials").asText(), requestAuth.get("realm").asText()); + break; + default: + writeResponse(Testkit.wrap("BackendError", Testkit.msg("Unsupported auth scheme"))); + return; + } + + drivers.putIfAbsent( id, GraphDatabase.driver(uri, authToken)); + writeResponse( response); + } else if ( requestType.equals( "NewSession" )) + { + String id = requestData.get( "driverId" ).asText(); + Driver driver = drivers.get( id ); + AccessMode accessMode = requestData.get( "accessMode" ).asText().equals( "r" ) ? AccessMode.READ : AccessMode.WRITE; + //Bookmark bookmark = InternalBookmark.parse( requestData.get("bookmarks").asText() ); + Session session = driver.session( SessionConfig.builder() + .withDefaultAccessMode( accessMode ).build()); + String newId = newId(); + sessionStates.put( newId, new SessionState(session) ); + String response = Testkit.wrap("Session", Testkit.id(newId)); + + writeResponse( response); + } else if ( requestType.equals( "SessionRun" )) + { + String id = requestData.get( "sessionId" ).asText(); + Session session = sessionStates.get( id ).session; + JsonNode jsonParams = (JsonNode)requestData.get("params"); + Map params = new HashMap(); + Iterator> iter = jsonParams.fields(); + while (iter.hasNext()) { + Map.Entry entry = iter.next(); + params.put(entry.getKey(), TestkitTypes.toDriver(entry.getValue())); + } + Query query = new Query(requestData.get("cypher").asText(), params); + Result result = session.run(query); + String newId = newId(); + + results.put( newId, result ); + String response = Testkit.wrap("Result", Testkit.id(newId)); + writeResponse( response); + } else if ( requestType.equals( "TransactionRun" )) + { + String txId = requestData.get("txId").asText(); + String cypher = requestData.get("cypher").asText(); + Transaction tx = transactions.get(txId); + Result result = tx.run(cypher); + String newId = newId(); + results.put(newId, result); + writeResponse(Testkit.wrap("Result", Testkit.id(newId))); + } else if ( requestType.equals( "RetryablePositive" )) + { + String id = requestData.get( "sessionId" ).asText(); + SessionState sessState = sessionStates.get( id ); + sessState.retryableState = 1; + } else if ( requestType.equals( "RetryableNegative" )) + { + String id = requestData.get( "sessionId" ).asText(); + SessionState sessState = sessionStates.get( id ); + sessState.retryableState = -1; + sessState.retryableErrorId = requestData.get("errorId").asText(); + } else if ( requestType.equals( "SessionReadTransaction" )) + { + String id = requestData.get( "sessionId" ).asText(); + SessionState sessState = sessionStates.get( id ); + sessState.session.readTransaction((Transaction tx) -> { + // Reset session state + sessState.retryableState = 0; + // Stash this transaction as there will be commands using it + String txId = newId(); + transactions.put(txId, tx); + // Instruct testkit client to send it's commands within the transaction + try { + writeResponse(Testkit.wrap("RetryableTry", Testkit.id(txId))); + } catch ( Exception e) { + e.printStackTrace(); + } + while ( true ) { + // Process commands as usual but blocking in here + process(); + // Check if state changed on session + switch (sessState.retryableState) { + case 0: + // Nothing happened to session state while processing command + break; + case 1: + // Client is happy to commit + return 0; + case -1: + // Client wants to rollback + if (sessState.retryableErrorId != "") { + Neo4jException err = errors.get(sessState.retryableErrorId); + throw err; + } else { + throw new RuntimeException("Error from client in retryable tx"); + } + } + } + }); + // Assume that exception is thrown by readTransaction when retry fails so this is + // never reached. + writeResponse(Testkit.wrap("RetryableDone", "{}")); + } else if ( requestType.equals( "ResultNext" )) + { + String id = requestData.get( "resultId" ).asText(); + Result result = results.get( id ); + + // TODO: Should we call next here and catch the exception instead? + if (!result.hasNext()) { + writeResponse(Testkit.wrap("NullRecord", "{}")); + return; + } + + Record record = result.next(); + String newId = newId(); + + results.put( newId, result ); + String response = Testkit.wrap("Record", Testkit.values(TestkitTypes.fromRecord(record))); + + writeResponse( response); + } else if ( requestType.equals( "SessionClose" )) + { + String id = requestData.get( "sessionId" ).asText(); + Session session = sessionStates.get( id ).session; + session.close(); + sessionStates.remove( id ); + + String response = Testkit.wrap("Session", Testkit.id(id)); + + writeResponse( response); + } + else if ( requestType.equals( "DriverClose" )) + { + String id = requestData.get( "driverId" ).asText(); + Driver driver = drivers.get( id ); + driver.close(); + drivers.remove( id ); + + String response = Testkit.wrap("Driver", Testkit.id(id)); + + writeResponse( response); + } + else + { + throw new RuntimeException("Request " + requestType + " not handled"); + } + } + + private void writeResponse(String response) + { + System.out.println( "response = " + response ); + + write( "#response begin\n" ); + write( response + "\n" ); + write( "#response end\n" ); + flush(); + } + + private String newId() + { + int nextNumber = idGenerator++; + return String.valueOf( nextNumber ); + } + + private SessionState getSessionState(JsonNode req) { + String id = req.get( "data" ).get( "sessionId" ).asText(); + return sessionStates.get( id ); + } +} diff --git a/testkit-backend/src/main/java/Runner.java b/testkit-backend/src/main/java/Runner.java new file mode 100644 index 0000000000..f0580e68a4 --- /dev/null +++ b/testkit-backend/src/main/java/Runner.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2002-2020 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.UncheckedIOException; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.net.ServerSocket; +import java.net.Socket; + +public class Runner +{ + public static void main( String[] args ) throws IOException + { + ServerSocket serverSocket = new ServerSocket(9876); + System.out.println("Starting Java Testkit Backend Started"); + + while (true) { + System.out.println("Listening on socket"); + Socket clientSocket = serverSocket.accept(); + + System.out.println( "Handling connection from: " + clientSocket.getRemoteSocketAddress() ); + BufferedReader in = new BufferedReader( new InputStreamReader( clientSocket.getInputStream() ) ); + BufferedWriter out = new BufferedWriter( new OutputStreamWriter( clientSocket.getOutputStream() ) ); + CommandProcessor commandProcessor = new CommandProcessor(in, out); + + boolean cont = true; + while (cont) { + try { + cont = commandProcessor.process(); + } catch (Exception e) { + e.printStackTrace(); + clientSocket.close(); + cont = false; + } + } + } + } +} diff --git a/testkit-backend/src/main/java/SessionState.java b/testkit-backend/src/main/java/SessionState.java new file mode 100644 index 0000000000..e3487a25d2 --- /dev/null +++ b/testkit-backend/src/main/java/SessionState.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2002-2020 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import org.neo4j.driver.Session; + +public class SessionState +{ + public Session session; + public int retryableState; + public String retryableErrorId; + + public SessionState(Session session) { + this.session = session; + this.retryableState = 0; + this.retryableErrorId = ""; + } +} diff --git a/testkit-backend/src/main/java/Testkit.java b/testkit-backend/src/main/java/Testkit.java new file mode 100644 index 0000000000..ea2d4fda55 --- /dev/null +++ b/testkit-backend/src/main/java/Testkit.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2002-2020 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class Testkit +{ + public static String wrap(String name, String data) + { + return String.format("{\"name\": \"%s\", \"data\":%s}", name, data); + } + + public static String value(String v) { + return "{\"value\":"+v+"}"; + } + + public static String values(String v) { + return "{\"values\":["+v+"]}"; + } + + public static String id(String v) { + return "{\"id\":"+v+"}"; + } + + public static String msg(String msg) { + return "{\"msg\":\""+msg+"\"}"; + } +} + + diff --git a/testkit-backend/src/main/java/TestkitTypes.java b/testkit-backend/src/main/java/TestkitTypes.java new file mode 100644 index 0000000000..479f3e4069 --- /dev/null +++ b/testkit-backend/src/main/java/TestkitTypes.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2002-2020 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import com.fasterxml.jackson.databind.JsonNode; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.lang.reflect.Array; + +import org.neo4j.driver.Record; +import org.neo4j.driver.Value; +import org.neo4j.driver.types.Node; +import org.neo4j.driver.internal.value.IntegerValue; +import org.neo4j.driver.internal.value.ListValue; +import org.neo4j.driver.internal.value.MapValue; +import org.neo4j.driver.internal.value.NodeValue; +import org.neo4j.driver.internal.value.NullValue; +import org.neo4j.driver.internal.value.StringValue; + + +public class TestkitTypes +{ + + public static String fromRecord( Record record ) + { + String v = ""; + + for ( Value value : record.values() ) + { + if (v != "") { + v += ","; + } + v += toTestkitType(value); + } + + return v; + } + + + private static String toTestkitType( Object obj ) + { + if ( obj instanceof Value) { + Value value = (Value) obj; + if ( value instanceof NullValue ) + { + return "{\"data\" : null, \"name\" : \"CypherNull\"}"; + } else if ( value instanceof IntegerValue ) + { + return toTestkitType(value.asInt()); + } else if ( value instanceof StringValue ) + { + return toTestkitType(value.asString()); + } else if ( value instanceof ListValue ) + { + return toTestkitType(((ListValue)value).asList()); + } else if ( value instanceof NodeValue ) + { + Node node = ((NodeValue)value).asNode(); + String v = String.format("{\"id\":%s,\"labels\":%s,\"props\":%s}", + toTestkitType(node.id()), toTestkitType(node.labels()), toTestkitType(node.asMap())); + return Testkit.wrap("Node", v); + } else if ( value instanceof MapValue ) + { + return "{\"data\" : {\"value\": {}, \"name\" : \"CypherMap\"}"; + } + } else { + if (obj instanceof Integer || obj instanceof Long) { + return Testkit.wrap("CypherInt", Testkit.value(obj.toString())); + } else if (obj instanceof String) { + return Testkit.wrap("CypherString", Testkit.value("\""+obj.toString()+"\"")); + } else if (obj instanceof List) { + List list = (List)obj; + String v = ""; + for (int i = 0; i < list.size(); i++) { + if (i > 0) { + v += ","; + } + v += toTestkitType(list.get(i)); + } + return Testkit.wrap("CypherList", Testkit.value("["+v+"]")); + } else if (obj instanceof Map) { + Map map = (Map)obj; + String v = ""; + + for (Map.Entry entry : map.entrySet()) { + if (v != "") { + v += ","; + } + v += String.format("\"%s\":%s", entry.getKey(), toTestkitType(entry.getValue())); + } + return Testkit.wrap("CypherMap", Testkit.value("{"+v+"}")); + } + } + + throw new RuntimeException("Can not convert to testkit type:"+obj.getClass()); + } + + public static Object toDriver(JsonNode node) + { + String name = node.get("name").asText(); + JsonNode data = node.get("data"); + if (name.equals("CypherInt")) { + return data.get("value").asInt(); + } + throw new RuntimeException("Can not convert from " + node + " to driver type"); + } +} From 26b2d2d1b369d2eab8d7398a1cc6f01cdb329f16 Mon Sep 17 00:00:00 2001 From: Michael Simons Date: Wed, 30 Sep 2020 12:40:52 +0200 Subject: [PATCH 3/6] Exclude America/Santiago from randomized tz tests. (#753) America/Santiago can cause issues on windows, see: https://stackoverflow.com/questions/37533796/java-calendar-returns-wrong-hour-in-ms-windows-for-america-santiago-zone --- driver/src/test/java/org/neo4j/driver/util/TemporalUtil.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/driver/src/test/java/org/neo4j/driver/util/TemporalUtil.java b/driver/src/test/java/org/neo4j/driver/util/TemporalUtil.java index 1dc13f6f21..a181619b8c 100644 --- a/driver/src/test/java/org/neo4j/driver/util/TemporalUtil.java +++ b/driver/src/test/java/org/neo4j/driver/util/TemporalUtil.java @@ -59,7 +59,8 @@ public final class TemporalUtil "Chile/EasterIsland", "Africa/Casablanca", "tzid", - "Asia/Qostanay" + "Asia/Qostanay", + "America/Santiago" // Can cause flakyness on windows, see https://stackoverflow.com/questions/37533796/java-calendar-returns-wrong-hour-in-ms-windows-for-america-santiago-zone. ); private TemporalUtil() From 16bdde313b0a4099e11aeb0a86e60f1deceac739 Mon Sep 17 00:00:00 2001 From: gjmwoods <42248895+gjmwoods@users.noreply.github.com> Date: Wed, 30 Sep 2020 13:46:56 +0100 Subject: [PATCH 4/6] OCSP Stapling Checks (#756) Adds the ability for TrustStategy to configure whether or not certificate revocation checking is to be carried out. By default revocation checking is disabled. If enabled the driver will check the validity of stapled OCSP (Online Certificate Status Protocol) response(s). These responses are returned during the TLS handshake by the server and if not present, the driver will fail to accept the certificate. See: https://tools.ietf.org/html/rfc6961 --- .../main/java/org/neo4j/driver/Config.java | 51 +++++++- .../driver/internal/RevocationStrategy.java | 35 ++++++ .../driver/internal/SecuritySettings.java | 11 +- .../internal/security/SecurityPlan.java | 4 + .../internal/security/SecurityPlanImpl.java | 110 +++++++++++++++--- .../driver/internal/util/CertificateTool.java | 15 ++- .../java/org/neo4j/driver/ConfigTest.java | 19 +++ .../integration/ChannelConnectorImplIT.java | 3 +- .../driver/internal/SecuritySettingsTest.java | 96 ++++++++++----- .../NettyChannelInitializerTest.java | 5 +- .../neo4j/driver/util/CertificateUtil.java | 2 + .../neo4j/docs/driver/HelloWorldExample.java | 2 +- 12 files changed, 296 insertions(+), 57 deletions(-) create mode 100644 driver/src/main/java/org/neo4j/driver/internal/RevocationStrategy.java diff --git a/driver/src/main/java/org/neo4j/driver/Config.java b/driver/src/main/java/org/neo4j/driver/Config.java index 857e1cb673..864e9d4f05 100644 --- a/driver/src/main/java/org/neo4j/driver/Config.java +++ b/driver/src/main/java/org/neo4j/driver/Config.java @@ -30,7 +30,7 @@ import org.neo4j.driver.exceptions.ServiceUnavailableException; import org.neo4j.driver.exceptions.SessionExpiredException; import org.neo4j.driver.exceptions.TransientException; -import org.neo4j.driver.internal.ConnectionSettings; +import org.neo4j.driver.internal.RevocationStrategy; import org.neo4j.driver.internal.SecuritySettings; import org.neo4j.driver.internal.async.pool.PoolSettings; import org.neo4j.driver.internal.cluster.RoutingSettings; @@ -802,6 +802,7 @@ public enum Strategy private final Strategy strategy; private final File certFile; private boolean hostnameVerificationEnabled = true; + private RevocationStrategy revocationStrategy = RevocationStrategy.NO_CHECKS; private TrustStrategy( Strategy strategy ) { @@ -901,5 +902,53 @@ public static TrustStrategy trustAllCertificates() { return new TrustStrategy( Strategy.TRUST_ALL_CERTIFICATES ); } + + /** + * The revocation strategy used for verifying certificates. + * @return this {@link TrustStrategy}'s revocation strategy + */ + public RevocationStrategy revocationStrategy() + { + return revocationStrategy; + } + + /** + * Configures the {@link TrustStrategy} to not carry out OCSP revocation checks on certificates. This is the + * option that is configured by default. + * @return the current trust strategy + */ + public TrustStrategy withoutCertificateRevocationChecks() + { + this.revocationStrategy = RevocationStrategy.NO_CHECKS; + return this; + } + + /** + * Configures the {@link TrustStrategy} to carry out OCSP revocation checks when the revocation status is + * stapled to the certificate. If no stapled response is found, then certificate verification continues + * (and does not fail verification). This setting also requires the server to be configured to enable + * OCSP stapling. + * @return the current trust strategy + */ + public TrustStrategy withVerifyIfPresentRevocationChecks() + { + this.revocationStrategy = RevocationStrategy.VERIFY_IF_PRESENT; + return this; + } + + /** + * Configures the {@link TrustStrategy} to carry out strict OCSP revocation checks for revocation status that + * are stapled to the certificate. If no stapled response is found, then the driver will fail certificate verification + * and not connect to the server. This setting also requires the server to be configured to enable OCSP stapling. + * + * Note: enabling this setting will prevent the driver connecting to the server when the server is unable to reach + * the certificate's configured OCSP responder URL. + * @return the current trust strategy + */ + public TrustStrategy withStrictRevocationChecks() + { + this.revocationStrategy = RevocationStrategy.STRICT; + return this; + } } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/RevocationStrategy.java b/driver/src/main/java/org/neo4j/driver/internal/RevocationStrategy.java new file mode 100644 index 0000000000..8dd2bd0fbc --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/RevocationStrategy.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2002-2020 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.neo4j.driver.internal; + +public enum RevocationStrategy +{ + /** Don't do any OCSP revocation checks, regardless whether there are stapled revocation statuses or not. */ + NO_CHECKS, + /** Verify OCSP revocation checks when the revocation status is stapled to the certificate, continue if not. */ + VERIFY_IF_PRESENT, + /** Require stapled revocation status and verify OCSP revocation checks, fail if no revocation status is stapled to the certificate. */ + STRICT; + + public static boolean requiresRevocationChecking( RevocationStrategy revocationStrategy ) + { + return revocationStrategy.equals( STRICT ) || revocationStrategy.equals( VERIFY_IF_PRESENT ); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/SecuritySettings.java b/driver/src/main/java/org/neo4j/driver/internal/SecuritySettings.java index 0595366c6a..0c3a8eb89d 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/SecuritySettings.java +++ b/driver/src/main/java/org/neo4j/driver/internal/SecuritySettings.java @@ -92,11 +92,11 @@ private SecurityPlan createSecurityPlanFromScheme( String scheme ) throws Genera { if ( isHighTrustScheme(scheme) ) { - return SecurityPlanImpl.forSystemCASignedCertificates( true ); + return SecurityPlanImpl.forSystemCASignedCertificates( true, RevocationStrategy.NO_CHECKS ); } else { - return SecurityPlanImpl.forAllCertificates( false ); + return SecurityPlanImpl.forAllCertificates( false, RevocationStrategy.NO_CHECKS ); } } @@ -110,14 +110,15 @@ private static SecurityPlan createSecurityPlanImpl( boolean encrypted, Config.Tr if ( encrypted ) { boolean hostnameVerificationEnabled = trustStrategy.isHostnameVerificationEnabled(); + RevocationStrategy revocationStrategy = trustStrategy.revocationStrategy(); switch ( trustStrategy.strategy() ) { case TRUST_CUSTOM_CA_SIGNED_CERTIFICATES: - return SecurityPlanImpl.forCustomCASignedCertificates( trustStrategy.certFile(), hostnameVerificationEnabled ); + return SecurityPlanImpl.forCustomCASignedCertificates( trustStrategy.certFile(), hostnameVerificationEnabled, revocationStrategy ); case TRUST_SYSTEM_CA_SIGNED_CERTIFICATES: - return SecurityPlanImpl.forSystemCASignedCertificates( hostnameVerificationEnabled ); + return SecurityPlanImpl.forSystemCASignedCertificates( hostnameVerificationEnabled, revocationStrategy ); case TRUST_ALL_CERTIFICATES: - return SecurityPlanImpl.forAllCertificates( hostnameVerificationEnabled ); + return SecurityPlanImpl.forAllCertificates( hostnameVerificationEnabled, revocationStrategy ); default: throw new ClientException( "Unknown TLS authentication strategy: " + trustStrategy.strategy().name() ); diff --git a/driver/src/main/java/org/neo4j/driver/internal/security/SecurityPlan.java b/driver/src/main/java/org/neo4j/driver/internal/security/SecurityPlan.java index 0ec31d719e..9e8524afb4 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/security/SecurityPlan.java +++ b/driver/src/main/java/org/neo4j/driver/internal/security/SecurityPlan.java @@ -20,6 +20,8 @@ import javax.net.ssl.SSLContext; +import org.neo4j.driver.internal.RevocationStrategy; + /** * A SecurityPlan consists of encryption and trust details. */ @@ -30,4 +32,6 @@ public interface SecurityPlan SSLContext sslContext(); boolean requiresHostnameVerification(); + + RevocationStrategy revocationStrategy(); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/security/SecurityPlanImpl.java b/driver/src/main/java/org/neo4j/driver/internal/security/SecurityPlanImpl.java index 39a6da8508..5d62d85830 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/security/SecurityPlanImpl.java +++ b/driver/src/main/java/org/neo4j/driver/internal/security/SecurityPlanImpl.java @@ -22,15 +22,22 @@ import java.io.IOException; import java.security.GeneralSecurityException; import java.security.KeyStore; -import java.security.NoSuchAlgorithmException; +import java.security.Security; import java.security.cert.CertificateException; +import java.security.cert.PKIXBuilderParameters; +import java.security.cert.X509CertSelector; import java.security.cert.X509Certificate; +import javax.net.ssl.CertPathTrustManagerParameters; import javax.net.ssl.KeyManager; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; +import org.neo4j.driver.internal.RevocationStrategy; + +import static org.neo4j.driver.internal.RevocationStrategy.VERIFY_IF_PRESENT; +import static org.neo4j.driver.internal.RevocationStrategy.requiresRevocationChecking; import static org.neo4j.driver.internal.util.CertificateTool.loadX509Cert; /** @@ -38,54 +45,117 @@ */ public class SecurityPlanImpl implements SecurityPlan { - public static SecurityPlan forAllCertificates( boolean requiresHostnameVerification ) throws GeneralSecurityException + public static SecurityPlan forAllCertificates( boolean requiresHostnameVerification, RevocationStrategy revocationStrategy ) throws GeneralSecurityException { SSLContext sslContext = SSLContext.getInstance( "TLS" ); sslContext.init( new KeyManager[0], new TrustManager[]{new TrustAllTrustManager()}, null ); - return new SecurityPlanImpl( true, sslContext, requiresHostnameVerification ); + return new SecurityPlanImpl( true, sslContext, requiresHostnameVerification, revocationStrategy ); + } + + public static SecurityPlan forCustomCASignedCertificates( File certFile, boolean requiresHostnameVerification, + RevocationStrategy revocationStrategy ) + throws GeneralSecurityException, IOException + { + SSLContext sslContext = configureSSLContext( certFile, revocationStrategy ); + return new SecurityPlanImpl( true, sslContext, requiresHostnameVerification, revocationStrategy ); + } + + public static SecurityPlan forSystemCASignedCertificates( boolean requiresHostnameVerification, RevocationStrategy revocationStrategy ) + throws GeneralSecurityException, IOException + { + SSLContext sslContext = configureSSLContext( null, revocationStrategy ); + return new SecurityPlanImpl( true, sslContext, requiresHostnameVerification, revocationStrategy ); } - public static SecurityPlan forCustomCASignedCertificates( File certFile, boolean requiresHostnameVerification ) + private static SSLContext configureSSLContext( File customCertFile, RevocationStrategy revocationStrategy ) throws GeneralSecurityException, IOException { - // A certificate file is specified so we will load the certificates in the file - // Init a in memory TrustedKeyStore - KeyStore trustedKeyStore = KeyStore.getInstance( "JKS" ); + KeyStore trustedKeyStore = KeyStore.getInstance( KeyStore.getDefaultType() ); trustedKeyStore.load( null, null ); - // Load the certs from the file - loadX509Cert( certFile, trustedKeyStore ); + if ( customCertFile != null ) + { + // A certificate file is specified so we will load the certificates in the file + loadX509Cert( customCertFile, trustedKeyStore ); + } + else + { + loadSystemCertificates( trustedKeyStore ); + } + + // Configure certificate revocation checking (X509CertSelector() selects all certificates) + PKIXBuilderParameters pkixBuilderParameters = new PKIXBuilderParameters( trustedKeyStore, new X509CertSelector() ); - // Create TrustManager from TrustedKeyStore - TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance( "SunX509" ); - trustManagerFactory.init( trustedKeyStore ); + // sets checking of stapled ocsp response + pkixBuilderParameters.setRevocationEnabled( requiresRevocationChecking( revocationStrategy ) ); + + if ( requiresRevocationChecking( revocationStrategy ) ) + { + // enables status_request extension in client hello + System.setProperty( "jdk.tls.client.enableStatusRequestExtension", "true" ); + + if ( revocationStrategy.equals( VERIFY_IF_PRESENT ) ) + { + // enables soft-fail behaviour if no stapled response found. + Security.setProperty( "ocsp.enable", "true" ); + } + } SSLContext sslContext = SSLContext.getInstance( "TLS" ); + + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance( TrustManagerFactory.getDefaultAlgorithm() ); + trustManagerFactory.init( new CertPathTrustManagerParameters( pkixBuilderParameters ) ); sslContext.init( new KeyManager[0], trustManagerFactory.getTrustManagers(), null ); - return new SecurityPlanImpl( true, sslContext, requiresHostnameVerification ); + return sslContext; } - public static SecurityPlan forSystemCASignedCertificates( boolean requiresHostnameVerification ) throws NoSuchAlgorithmException + private static void loadSystemCertificates( KeyStore trustedKeyStore ) throws GeneralSecurityException, IOException { - return new SecurityPlanImpl( true, SSLContext.getDefault(), requiresHostnameVerification ); + // To customize the PKIXParameters we need to get hold of the default KeyStore, no other elegant way available + TrustManagerFactory tempFactory = TrustManagerFactory.getInstance( TrustManagerFactory.getDefaultAlgorithm() ); + tempFactory.init( (KeyStore) null ); + + // Get hold of the default trust manager + X509TrustManager x509TrustManager = null; + for ( TrustManager trustManager : tempFactory.getTrustManagers() ) + { + if ( trustManager instanceof X509TrustManager ) + { + x509TrustManager = (X509TrustManager) trustManager; + break; + } + } + + if ( x509TrustManager == null ) + { + throw new CertificateException( "No system certificates found" ); + } + else + { + // load system default certificates into KeyStore + loadX509Cert( x509TrustManager.getAcceptedIssuers(), trustedKeyStore ); + } } public static SecurityPlan insecure() { - return new SecurityPlanImpl( false, null, false ); + return new SecurityPlanImpl( false, null, false, + RevocationStrategy.NO_CHECKS ); } private final boolean requiresEncryption; private final SSLContext sslContext; private final boolean requiresHostnameVerification; + private final RevocationStrategy revocationStrategy; - private SecurityPlanImpl( boolean requiresEncryption, SSLContext sslContext, boolean requiresHostnameVerification ) + private SecurityPlanImpl( boolean requiresEncryption, SSLContext sslContext, boolean requiresHostnameVerification, RevocationStrategy revocationStrategy ) { this.requiresEncryption = requiresEncryption; this.sslContext = sslContext; this.requiresHostnameVerification = requiresHostnameVerification; + this.revocationStrategy = revocationStrategy; } @Override @@ -106,6 +176,12 @@ public boolean requiresHostnameVerification() return requiresHostnameVerification; } + @Override + public RevocationStrategy revocationStrategy() + { + return revocationStrategy; + } + private static class TrustAllTrustManager implements X509TrustManager { public void checkClientTrusted( X509Certificate[] chain, String authType ) throws CertificateException diff --git a/driver/src/main/java/org/neo4j/driver/internal/util/CertificateTool.java b/driver/src/main/java/org/neo4j/driver/internal/util/CertificateTool.java index e1fddfbd3e..809ca25a0a 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/util/CertificateTool.java +++ b/driver/src/main/java/org/neo4j/driver/internal/util/CertificateTool.java @@ -30,12 +30,13 @@ import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; import java.util.Base64; /** * A tool used to save, load certs, etc. */ -public class CertificateTool +public final class CertificateTool { private static final String BEGIN_CERT = "-----BEGIN CERTIFICATE-----"; private static final String END_CERT = "-----END CERTIFICATE-----"; @@ -139,6 +140,14 @@ public static void loadX509Cert( File certFile, KeyStore keyStore ) throws Gener } } + public static void loadX509Cert( X509Certificate[] certificates, KeyStore keyStore ) throws GeneralSecurityException, IOException + { + for ( int i = 0; i < certificates.length; i++ ) + { + loadX509Cert( certificates[i], "neo4j.javadriver.trustedcert." + i, keyStore ); + } + } + /** * Load a certificate to a key store with a name * @@ -161,6 +170,10 @@ public static String X509CertToString( String cert ) String cert64CharPerLine = cert.replaceAll( "(.{64})", "$1\n" ); return BEGIN_CERT + "\n" + cert64CharPerLine + "\n"+ END_CERT + "\n"; } + + private CertificateTool() + { + } } diff --git a/driver/src/test/java/org/neo4j/driver/ConfigTest.java b/driver/src/test/java/org/neo4j/driver/ConfigTest.java index 3a9f4830ab..1139bb9097 100644 --- a/driver/src/test/java/org/neo4j/driver/ConfigTest.java +++ b/driver/src/test/java/org/neo4j/driver/ConfigTest.java @@ -35,6 +35,9 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; +import static org.neo4j.driver.internal.RevocationStrategy.STRICT; +import static org.neo4j.driver.internal.RevocationStrategy.NO_CHECKS; +import static org.neo4j.driver.internal.RevocationStrategy.VERIFY_IF_PRESENT; import static org.neo4j.driver.internal.handlers.pulln.FetchSizeUtil.DEFAULT_FETCH_SIZE; class ConfigTest @@ -264,6 +267,22 @@ void shouldEnableAndDisableHostnameVerificationOnTrustStrategy() assertFalse( trustStrategy.isHostnameVerificationEnabled() ); } + @Test + void shouldEnableAndDisableCertificateRevocationChecksOnTestStrategy() + { + Config.TrustStrategy trustStrategy = Config.TrustStrategy.trustSystemCertificates(); + assertEquals( NO_CHECKS, trustStrategy.revocationStrategy() ); + + assertSame( trustStrategy, trustStrategy.withoutCertificateRevocationChecks() ); + assertEquals( NO_CHECKS, trustStrategy.revocationStrategy() ); + + assertSame( trustStrategy, trustStrategy.withStrictRevocationChecks() ); + assertEquals( STRICT, trustStrategy.revocationStrategy() ); + + assertSame( trustStrategy, trustStrategy.withVerifyIfPresentRevocationChecks() ); + assertEquals( VERIFY_IF_PRESENT, trustStrategy.revocationStrategy() ); + } + @Test void shouldAllowToConfigureResolver() { diff --git a/driver/src/test/java/org/neo4j/driver/integration/ChannelConnectorImplIT.java b/driver/src/test/java/org/neo4j/driver/integration/ChannelConnectorImplIT.java index 4a32d20cab..9ad4faa5ef 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/ChannelConnectorImplIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/ChannelConnectorImplIT.java @@ -42,6 +42,7 @@ import org.neo4j.driver.exceptions.ServiceUnavailableException; import org.neo4j.driver.internal.BoltServerAddress; import org.neo4j.driver.internal.ConnectionSettings; +import org.neo4j.driver.internal.RevocationStrategy; import org.neo4j.driver.internal.async.connection.BootstrapFactory; import org.neo4j.driver.internal.async.connection.ChannelConnector; import org.neo4j.driver.internal.async.connection.ChannelConnectorImpl; @@ -237,6 +238,6 @@ private ChannelConnectorImpl newConnector( AuthToken authToken, SecurityPlan sec private static SecurityPlan trustAllCertificates() throws GeneralSecurityException { - return SecurityPlanImpl.forAllCertificates( false ); + return SecurityPlanImpl.forAllCertificates( false, RevocationStrategy.NO_CHECKS ); } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/SecuritySettingsTest.java b/driver/src/test/java/org/neo4j/driver/internal/SecuritySettingsTest.java index 1c301807a1..e42517d54d 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/SecuritySettingsTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/SecuritySettingsTest.java @@ -19,22 +19,22 @@ package org.neo4j.driver.internal; -import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -import java.security.NoSuchAlgorithmException; import java.util.stream.Stream; -import javax.net.ssl.SSLContext; import org.neo4j.driver.Config; import org.neo4j.driver.exceptions.ClientException; import org.neo4j.driver.internal.security.SecurityPlan; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.neo4j.driver.internal.RevocationStrategy.STRICT; +import static org.neo4j.driver.internal.RevocationStrategy.NO_CHECKS; +import static org.neo4j.driver.internal.RevocationStrategy.VERIFY_IF_PRESENT; class SecuritySettingsTest { @@ -48,13 +48,18 @@ private static Stream systemCertSchemes() return Stream.of( "neo4j+s", "bolt+s" ); } - private static Stream allSchemes() + private static Stream unencryptedSchemes() + { + return Stream.of( "neo4j", "bolt" ); + } + + private static Stream allSecureSchemes() { return Stream.concat( selfSignedSchemes(), systemCertSchemes() ); } @ParameterizedTest - @MethodSource( "allSchemes" ) + @MethodSource( "allSecureSchemes" ) void testEncryptionSchemeEnablesEncryption( String scheme ) { SecuritySettings securitySettings = new SecuritySettings.SecuritySettingsBuilder().build(); @@ -72,11 +77,9 @@ void testSystemCertCompatibleConfiguration( String scheme ) throws Exception SecurityPlan securityPlan = securitySettings.createSecurityPlan( scheme ); - SSLContext defaultContext = SSLContext.getDefault(); - assertTrue( securityPlan.requiresEncryption() ); assertTrue( securityPlan.requiresHostnameVerification() ); - assertEquals( defaultContext, securityPlan.sslContext() ); + assertEquals( NO_CHECKS, securityPlan.revocationStrategy() ); } @ParameterizedTest @@ -92,7 +95,7 @@ void testSelfSignedCertConfigDisablesHostnameVerification( String scheme ) throw } @ParameterizedTest - @MethodSource( "allSchemes" ) + @MethodSource( "allSecureSchemes" ) void testThrowsOnUserCustomizedEncryption( String scheme ) { SecuritySettings securitySettings = new SecuritySettings.SecuritySettingsBuilder() @@ -107,7 +110,7 @@ void testThrowsOnUserCustomizedEncryption( String scheme ) } @ParameterizedTest - @MethodSource( "allSchemes" ) + @MethodSource( "allSecureSchemes" ) void testThrowsOnUserCustomizedTrustConfiguration( String scheme ) { SecuritySettings securitySettings = new SecuritySettings.SecuritySettingsBuilder() @@ -122,7 +125,7 @@ void testThrowsOnUserCustomizedTrustConfiguration( String scheme ) } @ParameterizedTest - @MethodSource( "allSchemes" ) + @MethodSource( "allSecureSchemes" ) void testThrowsOnUserCustomizedTrustConfigurationAndEncryption( String scheme ) { SecuritySettings securitySettings = new SecuritySettings.SecuritySettingsBuilder() @@ -137,48 +140,83 @@ void testThrowsOnUserCustomizedTrustConfigurationAndEncryption( String scheme ) assertTrue( ex.getMessage().contains( String.format( "Scheme %s is not configurable with manual encryption and trust settings", scheme ) )); } - @Test - void testNeo4jSchemeNoEncryption() + @ParameterizedTest + @MethodSource( "unencryptedSchemes" ) + void testNoEncryption( String scheme ) { SecuritySettings securitySettings = new SecuritySettings.SecuritySettingsBuilder().build(); - SecurityPlan securityPlan = securitySettings.createSecurityPlan( "neo4j" ); + SecurityPlan securityPlan = securitySettings.createSecurityPlan( scheme ); assertFalse( securityPlan.requiresEncryption() ); } - @Test - void testBoltSchemeNoEncryption() + @ParameterizedTest + @MethodSource( "unencryptedSchemes" ) + void testConfiguredEncryption( String scheme ) { - SecuritySettings securitySettings = new SecuritySettings.SecuritySettingsBuilder().build(); + SecuritySettings securitySettings = new SecuritySettings.SecuritySettingsBuilder() + .withEncryption().build(); - SecurityPlan securityPlan = securitySettings.createSecurityPlan( "bolt" ); + SecurityPlan securityPlan = securitySettings.createSecurityPlan( scheme ); - assertFalse( securityPlan.requiresEncryption() ); + assertTrue( securityPlan.requiresEncryption() ); } - @Test - void testConfiguredEncryption() + @ParameterizedTest + @MethodSource( "unencryptedSchemes" ) + void testConfiguredAllCertificates( String scheme) { SecuritySettings securitySettings = new SecuritySettings.SecuritySettingsBuilder() - .withEncryption().build(); + .withEncryption() + .withTrustStrategy( Config.TrustStrategy.trustAllCertificates() ) + .build(); - SecurityPlan securityPlan = securitySettings.createSecurityPlan( "neo4j" ); + SecurityPlan securityPlan = securitySettings.createSecurityPlan( scheme ); assertTrue( securityPlan.requiresEncryption() ); } - @Test - void testConfiguredAllCertificates() + @ParameterizedTest + @MethodSource( "unencryptedSchemes" ) + void testConfigureStrictRevocationChecking( String scheme ) { SecuritySettings securitySettings = new SecuritySettings.SecuritySettingsBuilder() + .withTrustStrategy( Config.TrustStrategy.trustSystemCertificates().withStrictRevocationChecks() ) + .withEncryption() + .build(); + + SecurityPlan securityPlan = securitySettings.createSecurityPlan( scheme ); + + assertEquals( STRICT, securityPlan.revocationStrategy() ); + } + + @ParameterizedTest + @MethodSource( "unencryptedSchemes" ) + void testConfigureVerifyIfPresentRevocationChecking( String scheme ) + { + SecuritySettings securitySettings = new SecuritySettings.SecuritySettingsBuilder() + .withTrustStrategy( Config.TrustStrategy.trustSystemCertificates().withVerifyIfPresentRevocationChecks() ) .withEncryption() - .withTrustStrategy( Config.TrustStrategy.trustAllCertificates() ) .build(); - SecurityPlan securityPlan = securitySettings.createSecurityPlan( "neo4j" ); + SecurityPlan securityPlan = securitySettings.createSecurityPlan( scheme ); - assertTrue( securityPlan.requiresEncryption() ); + assertEquals( VERIFY_IF_PRESENT, securityPlan.revocationStrategy() ); + } + + @ParameterizedTest + @MethodSource( "unencryptedSchemes" ) + void testRevocationCheckingDisabledByDefault( String scheme ) + { + SecuritySettings securitySettings = new SecuritySettings.SecuritySettingsBuilder() + .withTrustStrategy( Config.TrustStrategy.trustSystemCertificates() ) + .withEncryption() + .build(); + + SecurityPlan securityPlan = securitySettings.createSecurityPlan( scheme ); + + assertEquals( NO_CHECKS, securityPlan.revocationStrategy() ); } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/connection/NettyChannelInitializerTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/connection/NettyChannelInitializerTest.java index 8ea7cf152a..8036d09cf4 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/connection/NettyChannelInitializerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/connection/NettyChannelInitializerTest.java @@ -31,6 +31,7 @@ import javax.net.ssl.SSLParameters; import org.neo4j.driver.internal.BoltServerAddress; +import org.neo4j.driver.internal.RevocationStrategy; import org.neo4j.driver.internal.security.SecurityPlanImpl; import org.neo4j.driver.internal.security.SecurityPlan; import org.neo4j.driver.internal.util.Clock; @@ -143,7 +144,7 @@ void shouldNotEnableHostnameVerificationWhenNotConfigured() throws Exception private void testHostnameVerificationSetting( boolean enabled, String expectedValue ) throws Exception { - NettyChannelInitializer initializer = newInitializer( SecurityPlanImpl.forAllCertificates( enabled ) ); + NettyChannelInitializer initializer = newInitializer( SecurityPlanImpl.forAllCertificates( enabled, RevocationStrategy.NO_CHECKS ) ); initializer.initChannel( channel ); @@ -172,6 +173,6 @@ private static NettyChannelInitializer newInitializer( SecurityPlan securityPlan private static SecurityPlan trustAllCertificates() throws GeneralSecurityException { - return SecurityPlanImpl.forAllCertificates( false ); + return SecurityPlanImpl.forAllCertificates( false, RevocationStrategy.NO_CHECKS ); } } diff --git a/driver/src/test/java/org/neo4j/driver/util/CertificateUtil.java b/driver/src/test/java/org/neo4j/driver/util/CertificateUtil.java index 581e28dbf9..14a7155ebe 100644 --- a/driver/src/test/java/org/neo4j/driver/util/CertificateUtil.java +++ b/driver/src/test/java/org/neo4j/driver/util/CertificateUtil.java @@ -19,6 +19,7 @@ package org.neo4j.driver.util; import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x509.BasicConstraints; import org.bouncycastle.asn1.x509.Extension; import org.bouncycastle.asn1.x509.GeneralName; import org.bouncycastle.asn1.x509.GeneralNames; @@ -93,6 +94,7 @@ private static X509Certificate generateCert( X500Name issuer, X500Name subject, // Subject alternative name (part of SNI extension, used for hostname verification) GeneralNames subjectAlternativeName = new GeneralNames( new GeneralName( GeneralName.dNSName, DEFAULT_HOST_NAME ) ); certBuilder.addExtension( Extension.subjectAlternativeName, false, subjectAlternativeName ); + certBuilder.addExtension( Extension.basicConstraints, false, new BasicConstraints( true ) ); // Get the certificate back ContentSigner signer = new JcaContentSignerBuilder( "SHA512WithRSAEncryption" ).build( issuerKeys.getPrivate() ); diff --git a/examples/src/main/java/org/neo4j/docs/driver/HelloWorldExample.java b/examples/src/main/java/org/neo4j/docs/driver/HelloWorldExample.java index c80b2121ee..2bb4afa1f9 100644 --- a/examples/src/main/java/org/neo4j/docs/driver/HelloWorldExample.java +++ b/examples/src/main/java/org/neo4j/docs/driver/HelloWorldExample.java @@ -23,8 +23,8 @@ import org.neo4j.driver.AuthTokens; import org.neo4j.driver.Driver; import org.neo4j.driver.GraphDatabase; -import org.neo4j.driver.Session; import org.neo4j.driver.Result; +import org.neo4j.driver.Session; import org.neo4j.driver.Transaction; import org.neo4j.driver.TransactionWork; From 53fa23f52e209ff4108ffc33acc00e1ec9b665de Mon Sep 17 00:00:00 2001 From: gjmwoods <42248895+gjmwoods@users.noreply.github.com> Date: Wed, 30 Sep 2020 15:02:30 +0100 Subject: [PATCH 5/6] Adding bolt v4.2 handshake. (#762) --- .../async/connection/BoltProtocolUtil.java | 7 +- .../internal/messaging/BoltProtocol.java | 6 +- .../messaging/v42/BoltProtocolV42.java | 38 +++++++++ .../driver/internal/util/ServerVersion.java | 5 ++ .../connection/BoltProtocolUtilTest.java | 9 +-- .../connection/HandshakeHandlerTest.java | 77 ++++++++++--------- .../loadbalancing/LoadBalancerTest.java | 5 +- .../messaging/BoltProtocolVersionTest.java | 2 +- .../messaging/v42/BoltProtocolV42Test.java | 31 ++++++++ .../internal/util/ServerVersionTest.java | 2 + .../java/org/neo4j/driver/util/TestUtil.java | 3 +- 11 files changed, 133 insertions(+), 52 deletions(-) create mode 100644 driver/src/main/java/org/neo4j/driver/internal/messaging/v42/BoltProtocolV42.java create mode 100644 driver/src/test/java/org/neo4j/driver/internal/messaging/v42/BoltProtocolV42Test.java diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/connection/BoltProtocolUtil.java b/driver/src/main/java/org/neo4j/driver/internal/async/connection/BoltProtocolUtil.java index 065837b762..bc191a9434 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/connection/BoltProtocolUtil.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/connection/BoltProtocolUtil.java @@ -21,11 +21,10 @@ import io.netty.buffer.ByteBuf; import org.neo4j.driver.internal.messaging.BoltProtocolVersion; -import org.neo4j.driver.internal.messaging.v1.BoltProtocolV1; -import org.neo4j.driver.internal.messaging.v2.BoltProtocolV2; import org.neo4j.driver.internal.messaging.v3.BoltProtocolV3; import org.neo4j.driver.internal.messaging.v4.BoltProtocolV4; import org.neo4j.driver.internal.messaging.v41.BoltProtocolV41; +import org.neo4j.driver.internal.messaging.v42.BoltProtocolV42; import static io.netty.buffer.Unpooled.copyInt; import static io.netty.buffer.Unpooled.unreleasableBuffer; @@ -42,10 +41,10 @@ public final class BoltProtocolUtil private static final ByteBuf HANDSHAKE_BUF = unreleasableBuffer( copyInt( BOLT_MAGIC_PREAMBLE, + BoltProtocolV42.VERSION.toInt(), BoltProtocolV41.VERSION.toInt(), BoltProtocolV4.VERSION.toInt(), - BoltProtocolV3.VERSION.toInt(), - 0 ) ).asReadOnly(); + BoltProtocolV3.VERSION.toInt() ) ).asReadOnly(); private static final String HANDSHAKE_STRING = createHandshakeString(); diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/BoltProtocol.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/BoltProtocol.java index 713123ffc9..7a75492b64 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/BoltProtocol.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/BoltProtocol.java @@ -21,7 +21,6 @@ import io.netty.channel.Channel; import io.netty.channel.ChannelPromise; -import java.util.Map; import java.util.concurrent.CompletionStage; import org.neo4j.driver.AuthToken; @@ -30,7 +29,6 @@ import org.neo4j.driver.Session; import org.neo4j.driver.Transaction; import org.neo4j.driver.TransactionConfig; -import org.neo4j.driver.Value; import org.neo4j.driver.exceptions.ClientException; import org.neo4j.driver.internal.BookmarkHolder; import org.neo4j.driver.internal.InternalBookmark; @@ -42,6 +40,7 @@ import org.neo4j.driver.internal.messaging.v3.BoltProtocolV3; import org.neo4j.driver.internal.messaging.v4.BoltProtocolV4; import org.neo4j.driver.internal.messaging.v41.BoltProtocolV41; +import org.neo4j.driver.internal.messaging.v42.BoltProtocolV42; import org.neo4j.driver.internal.spi.Connection; import static org.neo4j.driver.internal.async.connection.ChannelAttributes.protocolVersion; @@ -173,6 +172,9 @@ else if ( BoltProtocolV4.VERSION.equals( version ) ) } else if ( BoltProtocolV41.VERSION.equals( version ) ) { return BoltProtocolV41.INSTANCE; + } else if ( BoltProtocolV42.VERSION.equals( version ) ) + { + return BoltProtocolV42.INSTANCE; } throw new ClientException( "Unknown protocol version: " + version ); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/v42/BoltProtocolV42.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/v42/BoltProtocolV42.java new file mode 100644 index 0000000000..db9e603541 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/v42/BoltProtocolV42.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2002-2020 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.messaging.v42; + +import org.neo4j.driver.internal.messaging.BoltProtocol; +import org.neo4j.driver.internal.messaging.BoltProtocolVersion; +import org.neo4j.driver.internal.messaging.v41.BoltProtocolV41; + +/** + * Bolt V4.2 is identical to V4.1 + */ +public class BoltProtocolV42 extends BoltProtocolV41 +{ + public static final BoltProtocolVersion VERSION = new BoltProtocolVersion( 4, 2 ); + public static final BoltProtocol INSTANCE = new BoltProtocolV42(); + + @Override + public BoltProtocolVersion version() + { + return VERSION; + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/util/ServerVersion.java b/driver/src/main/java/org/neo4j/driver/internal/util/ServerVersion.java index 245949a397..b5c3814922 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/util/ServerVersion.java +++ b/driver/src/main/java/org/neo4j/driver/internal/util/ServerVersion.java @@ -27,6 +27,7 @@ import org.neo4j.driver.internal.messaging.BoltProtocolVersion; import org.neo4j.driver.internal.messaging.v4.BoltProtocolV4; import org.neo4j.driver.internal.messaging.v41.BoltProtocolV41; +import org.neo4j.driver.internal.messaging.v42.BoltProtocolV42; import static java.lang.Integer.compare; @@ -34,6 +35,7 @@ public class ServerVersion { public static final String NEO4J_PRODUCT = "Neo4j"; + public static final ServerVersion v4_2_0 = new ServerVersion( NEO4J_PRODUCT, 4, 2, 0 ); public static final ServerVersion v4_1_0 = new ServerVersion( NEO4J_PRODUCT, 4, 1, 0 ); public static final ServerVersion v4_0_0 = new ServerVersion( NEO4J_PRODUCT, 4, 0, 0 ); public static final ServerVersion v3_5_0 = new ServerVersion( NEO4J_PRODUCT, 3, 5, 0 ); @@ -188,6 +190,9 @@ public static ServerVersion fromBoltProtocolVersion( BoltProtocolVersion protoco else if ( BoltProtocolV41.VERSION.equals( protocolVersion ) ) { return ServerVersion.v4_1_0; + } else if ( BoltProtocolV42.VERSION.equals( protocolVersion ) ) + { + return ServerVersion.v4_2_0; } return ServerVersion.vInDev; diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/connection/BoltProtocolUtilTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/connection/BoltProtocolUtilTest.java index a37668bb69..4657e31fc8 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/connection/BoltProtocolUtilTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/connection/BoltProtocolUtilTest.java @@ -22,11 +22,10 @@ import io.netty.buffer.Unpooled; import org.junit.jupiter.api.Test; -import org.neo4j.driver.internal.messaging.v1.BoltProtocolV1; -import org.neo4j.driver.internal.messaging.v2.BoltProtocolV2; import org.neo4j.driver.internal.messaging.v3.BoltProtocolV3; import org.neo4j.driver.internal.messaging.v4.BoltProtocolV4; import org.neo4j.driver.internal.messaging.v41.BoltProtocolV41; +import org.neo4j.driver.internal.messaging.v42.BoltProtocolV42; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.neo4j.driver.internal.async.connection.BoltProtocolUtil.BOLT_MAGIC_PREAMBLE; @@ -44,15 +43,15 @@ void shouldReturnHandshakeBuf() { assertByteBufContains( handshakeBuf(), - BOLT_MAGIC_PREAMBLE, BoltProtocolV41.VERSION.toInt(), BoltProtocolV4.VERSION.toInt(), - BoltProtocolV3.VERSION.toInt(), 0 + BOLT_MAGIC_PREAMBLE, BoltProtocolV42.VERSION.toInt(), BoltProtocolV41.VERSION.toInt(), + BoltProtocolV4.VERSION.toInt(), BoltProtocolV3.VERSION.toInt() ); } @Test void shouldReturnHandshakeString() { - assertEquals( "[0x6060b017, 260, 4, 3, 0]", handshakeString() ); + assertEquals( "[0x6060b017, 516, 260, 4, 3]", handshakeString() ); } @Test diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/connection/HandshakeHandlerTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/connection/HandshakeHandlerTest.java index b31513013d..90e3d5f0a2 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/connection/HandshakeHandlerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/connection/HandshakeHandlerTest.java @@ -25,8 +25,12 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import java.io.IOException; +import java.util.stream.Stream; import javax.net.ssl.SSLHandshakeException; import org.neo4j.driver.Logging; @@ -40,10 +44,12 @@ import org.neo4j.driver.internal.async.outbound.OutboundMessageHandler; import org.neo4j.driver.internal.messaging.BoltProtocolVersion; import org.neo4j.driver.internal.messaging.MessageFormat; -import org.neo4j.driver.internal.messaging.v1.BoltProtocolV1; -import org.neo4j.driver.internal.messaging.v1.MessageFormatV1; -import org.neo4j.driver.internal.messaging.v2.BoltProtocolV2; -import org.neo4j.driver.internal.messaging.v2.MessageFormatV2; +import org.neo4j.driver.internal.messaging.v3.BoltProtocolV3; +import org.neo4j.driver.internal.messaging.v3.MessageFormatV3; +import org.neo4j.driver.internal.messaging.v4.BoltProtocolV4; +import org.neo4j.driver.internal.messaging.v4.MessageFormatV4; +import org.neo4j.driver.internal.messaging.v41.BoltProtocolV41; +import org.neo4j.driver.internal.messaging.v42.BoltProtocolV42; import org.neo4j.driver.internal.util.ErrorUtil; import static io.netty.buffer.Unpooled.copyInt; @@ -54,6 +60,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.params.provider.Arguments.arguments; import static org.neo4j.driver.internal.async.connection.BoltProtocolUtil.NO_PROTOCOL_VERSION; import static org.neo4j.driver.internal.async.connection.ChannelAttributes.setMessageDispatcher; import static org.neo4j.driver.internal.logging.DevNullLogging.DEV_NULL_LOGGING; @@ -187,16 +194,33 @@ void shouldTranslateSSLHandshakeException() assertNull( await( channel.closeFuture() ) ); } - @Test - void shouldSelectProtocolV1WhenServerSuggests() + @ParameterizedTest + @MethodSource( "protocolVersions" ) + public void testProtocolSelection( BoltProtocolVersion protocolVersion, Class expectedMessageFormatClass ) { - testProtocolSelection( BoltProtocolV1.VERSION, MessageFormatV1.class ); - } + ChannelPromise handshakeCompletedPromise = channel.newPromise(); + MemorizingChannelPipelineBuilder pipelineBuilder = new MemorizingChannelPipelineBuilder(); + HandshakeHandler handler = newHandler( pipelineBuilder, handshakeCompletedPromise ); + channel.pipeline().addLast( handler ); - @Test - void shouldSelectProtocolV2WhenServerSuggests() - { - testProtocolSelection( BoltProtocolV2.VERSION, MessageFormatV2.class ); + channel.pipeline().fireChannelRead( copyInt( protocolVersion.toInt() ) ); + + // expected message format should've been used + assertThat( pipelineBuilder.usedMessageFormat, instanceOf( expectedMessageFormatClass ) ); + + // handshake handler itself should be removed + assertNull( channel.pipeline().get( HandshakeHandler.class ) ); + + // all inbound handlers should be set + assertNotNull( channel.pipeline().get( ChunkDecoder.class ) ); + assertNotNull( channel.pipeline().get( MessageDecoder.class ) ); + assertNotNull( channel.pipeline().get( InboundMessageHandler.class ) ); + + // all outbound handlers should be set + assertNotNull( channel.pipeline().get( OutboundMessageHandler.class ) ); + + // promise should be successful + assertNull( await( handshakeCompletedPromise ) ); } @Test @@ -254,31 +278,12 @@ private void testFailure( BoltProtocolVersion serverSuggestedVersion, String exp assertNull( await( channel.closeFuture() ) ); } - private void testProtocolSelection( BoltProtocolVersion protocolVersion, Class expectedMessageFormatClass ) + private static Stream protocolVersions() { - ChannelPromise handshakeCompletedPromise = channel.newPromise(); - MemorizingChannelPipelineBuilder pipelineBuilder = new MemorizingChannelPipelineBuilder(); - HandshakeHandler handler = newHandler( pipelineBuilder, handshakeCompletedPromise ); - channel.pipeline().addLast( handler ); - - channel.pipeline().fireChannelRead( copyInt( protocolVersion.toInt() ) ); - - // expected message format should've been used - assertThat( pipelineBuilder.usedMessageFormat, instanceOf( expectedMessageFormatClass ) ); - - // handshake handler itself should be removed - assertNull( channel.pipeline().get( HandshakeHandler.class ) ); - - // all inbound handlers should be set - assertNotNull( channel.pipeline().get( ChunkDecoder.class ) ); - assertNotNull( channel.pipeline().get( MessageDecoder.class ) ); - assertNotNull( channel.pipeline().get( InboundMessageHandler.class ) ); - - // all outbound handlers should be set - assertNotNull( channel.pipeline().get( OutboundMessageHandler.class ) ); - - // promise should be successful - assertNull( await( handshakeCompletedPromise ) ); + return Stream.of( arguments( BoltProtocolV3.VERSION, MessageFormatV3.class ), + arguments( BoltProtocolV4.VERSION, MessageFormatV4.class ), + arguments( BoltProtocolV41.VERSION, MessageFormatV4.class ), + arguments( BoltProtocolV42.VERSION, MessageFormatV4.class ) ); } private static HandshakeHandler newHandler( ChannelPromise handshakeCompletedPromise ) diff --git a/driver/src/test/java/org/neo4j/driver/internal/cluster/loadbalancing/LoadBalancerTest.java b/driver/src/test/java/org/neo4j/driver/internal/cluster/loadbalancing/LoadBalancerTest.java index a780028d6f..0d911d7445 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/cluster/loadbalancing/LoadBalancerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/cluster/loadbalancing/LoadBalancerTest.java @@ -47,8 +47,7 @@ import org.neo4j.driver.internal.cluster.RoutingTableHandler; import org.neo4j.driver.internal.cluster.RoutingTableRegistry; import org.neo4j.driver.internal.messaging.BoltProtocol; -import org.neo4j.driver.internal.messaging.v4.BoltProtocolV4; -import org.neo4j.driver.internal.messaging.v41.BoltProtocolV41; +import org.neo4j.driver.internal.messaging.v42.BoltProtocolV42; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.spi.ConnectionPool; import org.neo4j.driver.internal.util.FakeClock; @@ -395,7 +394,7 @@ private static Connection newBoltV4Connection( BoltServerAddress address ) { Connection connection = mock( Connection.class ); when( connection.serverAddress() ).thenReturn( address ); - when( connection.protocol() ).thenReturn( BoltProtocol.forVersion( BoltProtocolV41.VERSION ) ); + when( connection.protocol() ).thenReturn( BoltProtocol.forVersion( BoltProtocolV42.VERSION ) ); when( connection.serverVersion() ).thenReturn( ServerVersion.v4_1_0 ); when( connection.release() ).thenReturn( completedWithNull() ); return connection; diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/BoltProtocolVersionTest.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/BoltProtocolVersionTest.java index 15c3998a71..6f9ddb0fe5 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/BoltProtocolVersionTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/BoltProtocolVersionTest.java @@ -28,7 +28,7 @@ class BoltProtocolVersionTest { @ParameterizedTest( name = "V{0}.{1}" ) - @CsvSource( {"3, 0", "4, 0", "4, 1", "100, 100", "255, 255", "0, 0"} ) + @CsvSource( {"3, 0", "4, 0", "4, 1", "4, 2", "100, 100", "255, 255", "0, 0"} ) void shouldParseVersion( int major, int minor ) { BoltProtocolVersion protocolVersion = new BoltProtocolVersion( major, minor ); diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v42/BoltProtocolV42Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v42/BoltProtocolV42Test.java new file mode 100644 index 0000000000..db6892c15d --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v42/BoltProtocolV42Test.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2002-2020 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.messaging.v42; + +import org.neo4j.driver.internal.messaging.BoltProtocol; +import org.neo4j.driver.internal.messaging.v41.BoltProtocolV41Test; + +public class BoltProtocolV42Test extends BoltProtocolV41Test +{ + @Override + protected BoltProtocol createProtocol() + { + return BoltProtocolV42.INSTANCE; + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/util/ServerVersionTest.java b/driver/src/test/java/org/neo4j/driver/internal/util/ServerVersionTest.java index 2129023123..fe3dc549ce 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/util/ServerVersionTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/util/ServerVersionTest.java @@ -23,6 +23,7 @@ import org.neo4j.driver.internal.messaging.BoltProtocolVersion; import org.neo4j.driver.internal.messaging.v4.BoltProtocolV4; import org.neo4j.driver.internal.messaging.v41.BoltProtocolV41; +import org.neo4j.driver.internal.messaging.v42.BoltProtocolV42; import static java.lang.Integer.MAX_VALUE; import static org.hamcrest.MatcherAssert.assertThat; @@ -71,6 +72,7 @@ void shouldReturnCorrectServerVersionFromBoltProtocolVersion() { assertEquals( ServerVersion.v4_0_0, ServerVersion.fromBoltProtocolVersion( BoltProtocolV4.VERSION ) ); assertEquals( ServerVersion.v4_1_0, ServerVersion.fromBoltProtocolVersion( BoltProtocolV41.VERSION ) ); + assertEquals( ServerVersion.v4_2_0, ServerVersion.fromBoltProtocolVersion( BoltProtocolV42.VERSION ) ); assertEquals( ServerVersion.vInDev, ServerVersion.fromBoltProtocolVersion( new BoltProtocolVersion( MAX_VALUE, MAX_VALUE ) ) ); } } diff --git a/driver/src/test/java/org/neo4j/driver/util/TestUtil.java b/driver/src/test/java/org/neo4j/driver/util/TestUtil.java index 8db61f8682..da0e4bf4c6 100644 --- a/driver/src/test/java/org/neo4j/driver/util/TestUtil.java +++ b/driver/src/test/java/org/neo4j/driver/util/TestUtil.java @@ -71,6 +71,7 @@ import org.neo4j.driver.internal.messaging.v3.BoltProtocolV3; import org.neo4j.driver.internal.messaging.v4.BoltProtocolV4; import org.neo4j.driver.internal.messaging.v41.BoltProtocolV41; +import org.neo4j.driver.internal.messaging.v42.BoltProtocolV42; import org.neo4j.driver.internal.retry.RetryLogic; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.spi.ConnectionProvider; @@ -532,7 +533,7 @@ public static Connection connectionMock( String databaseName, AccessMode mode, B setupSuccessfulPullAll( connection, "BEGIN" ); } else if ( version.equals( BoltProtocolV3.VERSION ) || version.equals( BoltProtocolV4.VERSION ) || - version.equals( BoltProtocolV41.VERSION )) + version.equals( BoltProtocolV41.VERSION ) || version.equals( BoltProtocolV42.VERSION )) { setupSuccessResponse( connection, CommitMessage.class ); setupSuccessResponse( connection, RollbackMessage.class ); From 7f82e7b0eb4cff31ec6dc88cef8ba76eb91e83c7 Mon Sep 17 00:00:00 2001 From: Antonio Barcelos Date: Wed, 30 Sep 2020 17:39:43 +0200 Subject: [PATCH 6/6] Remove protocol 1 and 2 The reference this protocols are not needed since it's not used by the driver anymore. During the remotion of these protocol version, the tests were refactoried to be specific per version instead of assume the re-use done on the protocols implementation. --- .../org/neo4j/driver/TransactionConfig.java | 1 - .../internal/handlers/PullHandlers.java | 8 - .../internal/messaging/BoltProtocol.java | 18 +- .../CommonMessageReader.java} | 12 +- .../CommonValuePacker.java} | 148 ++++- .../CommonValueUnpacker.java} | 176 +++++- .../internal/messaging/v1/BoltProtocolV1.java | 287 ---------- .../messaging/v1/MessageFormatV1.java | 45 -- .../messaging/v1/MessageWriterV1.java | 61 --- .../internal/messaging/v1/ValuePackerV1.java | 129 ----- .../internal/messaging/v2/BoltProtocolV2.java | 43 -- .../messaging/v2/MessageFormatV2.java | 63 --- .../messaging/v2/MessageReaderV2.java | 30 - .../messaging/v2/MessageWriterV2.java | 30 - .../messaging/v2/ValueUnpackerV2.java | 179 ------ .../messaging/v3/MessageFormatV3.java | 4 +- .../messaging/v3/MessageWriterV3.java | 4 +- .../messaging/v4/MessageFormatV4.java | 4 +- .../messaging/v4/MessageWriterV4.java | 4 +- .../driver/internal/InternalResultTest.java | 11 +- .../async/AsyncResultCursorImplTest.java | 8 +- .../async/UnmanagedTransactionTest.java | 25 +- .../ChannelPipelineBuilderImplTest.java | 4 +- .../HandshakeCompletedListenerTest.java | 21 - .../inbound/InboundMessageHandlerTest.java | 12 +- .../outbound/OutboundMessageHandlerTest.java | 15 +- .../handlers/HelloResponseHandlerTest.java | 4 +- .../handlers/InitResponseHandlerTest.java | 8 +- .../LegacyPullAllResponseHandlerTest.java | 9 +- .../handlers/RunResponseHandlerTest.java | 10 +- ...ionPullResponseCompletionListenerTest.java | 6 +- ...ionPullResponseCompletionListenerTest.java | 2 +- .../pulln/AutoPullResponseHandlerTest.java | 12 +- .../internal/messaging/BoltProtocolTest.java | 6 +- .../internal/messaging/MessageFormatTest.java | 42 +- .../messaging/v1/BoltProtocolV1Test.java | 389 ------------- .../messaging/v1/MessageReaderV1Test.java | 59 -- .../messaging/v1/MessageWriterV1Test.java | 79 --- .../messaging/v2/BoltProtocolV2Test.java | 38 -- .../messaging/v2/MessageFormatV2Test.java | 422 -------------- .../messaging/v2/MessageReaderV2Test.java | 65 --- .../messaging/v2/MessageWriterV2Test.java | 76 --- .../messaging/v3/BoltProtocolV3Test.java | 24 +- .../messaging/v3/MessageFormatV3Test.java | 19 +- .../messaging/v3/MessageReaderV3Test.java | 120 ++++ .../messaging/v3/MessageWriterV3Test.java | 41 +- .../messaging/v4/BoltProtocolV4Test.java | 305 ++++++++++- .../messaging/v4/MessageFormatV4Test.java | 15 +- .../messaging/v4/MessageReaderV4Test.java | 120 ++++ .../messaging/v4/MessageWriterV4Test.java | 41 +- .../messaging/v41/BoltProtocolV41Test.java | 514 +++++++++++++++++- .../messaging/v41/MessageFormatV41Test.java | 58 ++ .../messaging/v41/MessageReaderV41Test.java | 120 ++++ .../messaging/v41/MessageWriterV41Test.java | 137 +++++ .../messaging/v42/BoltProtocolV42Test.java | 511 ++++++++++++++++- .../messaging/v42/MessageFormatV42Test.java | 57 ++ .../messaging/v42/MessageReaderV42Test.java | 120 ++++ .../messaging/v42/MessageWriterV42Test.java | 137 +++++ .../AbstractMessageReaderTestBase.java | 81 +-- .../messaging/KnowledgeableMessageFormat.java | 21 +- .../java/org/neo4j/driver/util/TestUtil.java | 64 +-- 61 files changed, 2753 insertions(+), 2321 deletions(-) rename driver/src/main/java/org/neo4j/driver/internal/messaging/{v1/MessageReaderV1.java => common/CommonMessageReader.java} (91%) rename driver/src/main/java/org/neo4j/driver/internal/messaging/{v2/ValuePackerV2.java => common/CommonValuePacker.java} (61%) rename driver/src/main/java/org/neo4j/driver/internal/messaging/{v1/ValueUnpackerV1.java => common/CommonValueUnpacker.java} (58%) delete mode 100644 driver/src/main/java/org/neo4j/driver/internal/messaging/v1/BoltProtocolV1.java delete mode 100644 driver/src/main/java/org/neo4j/driver/internal/messaging/v1/MessageFormatV1.java delete mode 100644 driver/src/main/java/org/neo4j/driver/internal/messaging/v1/MessageWriterV1.java delete mode 100644 driver/src/main/java/org/neo4j/driver/internal/messaging/v1/ValuePackerV1.java delete mode 100644 driver/src/main/java/org/neo4j/driver/internal/messaging/v2/BoltProtocolV2.java delete mode 100644 driver/src/main/java/org/neo4j/driver/internal/messaging/v2/MessageFormatV2.java delete mode 100644 driver/src/main/java/org/neo4j/driver/internal/messaging/v2/MessageReaderV2.java delete mode 100644 driver/src/main/java/org/neo4j/driver/internal/messaging/v2/MessageWriterV2.java delete mode 100644 driver/src/main/java/org/neo4j/driver/internal/messaging/v2/ValueUnpackerV2.java delete mode 100644 driver/src/test/java/org/neo4j/driver/internal/messaging/v1/BoltProtocolV1Test.java delete mode 100644 driver/src/test/java/org/neo4j/driver/internal/messaging/v1/MessageReaderV1Test.java delete mode 100644 driver/src/test/java/org/neo4j/driver/internal/messaging/v1/MessageWriterV1Test.java delete mode 100644 driver/src/test/java/org/neo4j/driver/internal/messaging/v2/BoltProtocolV2Test.java delete mode 100644 driver/src/test/java/org/neo4j/driver/internal/messaging/v2/MessageFormatV2Test.java delete mode 100644 driver/src/test/java/org/neo4j/driver/internal/messaging/v2/MessageReaderV2Test.java delete mode 100644 driver/src/test/java/org/neo4j/driver/internal/messaging/v2/MessageWriterV2Test.java create mode 100644 driver/src/test/java/org/neo4j/driver/internal/messaging/v3/MessageReaderV3Test.java create mode 100644 driver/src/test/java/org/neo4j/driver/internal/messaging/v4/MessageReaderV4Test.java create mode 100644 driver/src/test/java/org/neo4j/driver/internal/messaging/v41/MessageFormatV41Test.java create mode 100644 driver/src/test/java/org/neo4j/driver/internal/messaging/v41/MessageReaderV41Test.java create mode 100644 driver/src/test/java/org/neo4j/driver/internal/messaging/v41/MessageWriterV41Test.java create mode 100644 driver/src/test/java/org/neo4j/driver/internal/messaging/v42/MessageFormatV42Test.java create mode 100644 driver/src/test/java/org/neo4j/driver/internal/messaging/v42/MessageReaderV42Test.java create mode 100644 driver/src/test/java/org/neo4j/driver/internal/messaging/v42/MessageWriterV42Test.java diff --git a/driver/src/main/java/org/neo4j/driver/TransactionConfig.java b/driver/src/main/java/org/neo4j/driver/TransactionConfig.java index d7dee3e0bd..3739699cfc 100644 --- a/driver/src/main/java/org/neo4j/driver/TransactionConfig.java +++ b/driver/src/main/java/org/neo4j/driver/TransactionConfig.java @@ -202,7 +202,6 @@ public Builder withTimeout( Duration timeout ) public Builder withMetadata( Map metadata ) { requireNonNull( metadata, "Transaction metadata should not be null" ); - this.metadata = Extract.mapOfValues( metadata ); return this; } diff --git a/driver/src/main/java/org/neo4j/driver/internal/handlers/PullHandlers.java b/driver/src/main/java/org/neo4j/driver/internal/handlers/PullHandlers.java index 14e383a787..8f12430d7c 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/handlers/PullHandlers.java +++ b/driver/src/main/java/org/neo4j/driver/internal/handlers/PullHandlers.java @@ -24,19 +24,11 @@ import org.neo4j.driver.internal.handlers.pulln.AutoPullResponseHandler; import org.neo4j.driver.internal.handlers.pulln.BasicPullResponseHandler; import org.neo4j.driver.internal.handlers.pulln.PullResponseHandler; -import org.neo4j.driver.internal.messaging.v1.BoltProtocolV1; import org.neo4j.driver.internal.messaging.v3.BoltProtocolV3; import org.neo4j.driver.internal.spi.Connection; public class PullHandlers { - public static PullAllResponseHandler newBoltV1PullAllHandler(Query query, RunResponseHandler runHandler, - Connection connection, UnmanagedTransaction tx ) - { - PullResponseCompletionListener completionListener = createPullResponseCompletionListener( connection, BookmarkHolder.NO_OP, tx ); - - return new LegacyPullAllResponseHandler(query, runHandler, connection, BoltProtocolV1.METADATA_EXTRACTOR, completionListener ); - } public static PullAllResponseHandler newBoltV3PullAllHandler(Query query, RunResponseHandler runHandler, Connection connection, BookmarkHolder bookmarkHolder, UnmanagedTransaction tx ) diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/BoltProtocol.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/BoltProtocol.java index 7a75492b64..ee6c72eee5 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/BoltProtocol.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/BoltProtocol.java @@ -35,8 +35,6 @@ import org.neo4j.driver.internal.async.UnmanagedTransaction; import org.neo4j.driver.internal.cluster.RoutingContext; import org.neo4j.driver.internal.cursor.ResultCursorFactory; -import org.neo4j.driver.internal.messaging.v1.BoltProtocolV1; -import org.neo4j.driver.internal.messaging.v2.BoltProtocolV2; import org.neo4j.driver.internal.messaging.v3.BoltProtocolV3; import org.neo4j.driver.internal.messaging.v4.BoltProtocolV4; import org.neo4j.driver.internal.messaging.v41.BoltProtocolV41; @@ -154,25 +152,19 @@ static BoltProtocol forChannel( Channel channel ) */ static BoltProtocol forVersion( BoltProtocolVersion version ) { - if ( BoltProtocolV1.VERSION.equals( version ) ) - { - return BoltProtocolV1.INSTANCE; - } - else if ( BoltProtocolV2.VERSION.equals( version ) ) - { - return BoltProtocolV2.INSTANCE; - } - else if ( BoltProtocolV3.VERSION.equals( version ) ) + if ( BoltProtocolV3.VERSION.equals( version ) ) { return BoltProtocolV3.INSTANCE; } else if ( BoltProtocolV4.VERSION.equals( version ) ) { return BoltProtocolV4.INSTANCE; - } else if ( BoltProtocolV41.VERSION.equals( version ) ) + } + else if ( BoltProtocolV41.VERSION.equals( version ) ) { return BoltProtocolV41.INSTANCE; - } else if ( BoltProtocolV42.VERSION.equals( version ) ) + } + else if ( BoltProtocolV42.VERSION.equals( version ) ) { return BoltProtocolV42.INSTANCE; } diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/v1/MessageReaderV1.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/common/CommonMessageReader.java similarity index 91% rename from driver/src/main/java/org/neo4j/driver/internal/messaging/v1/MessageReaderV1.java rename to driver/src/main/java/org/neo4j/driver/internal/messaging/common/CommonMessageReader.java index f4920c8e99..e221c6a453 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/v1/MessageReaderV1.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/common/CommonMessageReader.java @@ -16,11 +16,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal.messaging.v1; +package org.neo4j.driver.internal.messaging.common; import java.io.IOException; import java.util.Map; +import org.neo4j.driver.Value; import org.neo4j.driver.internal.messaging.MessageFormat; import org.neo4j.driver.internal.messaging.ResponseMessageHandler; import org.neo4j.driver.internal.messaging.ValueUnpacker; @@ -29,18 +30,17 @@ import org.neo4j.driver.internal.messaging.response.RecordMessage; import org.neo4j.driver.internal.messaging.response.SuccessMessage; import org.neo4j.driver.internal.packstream.PackInput; -import org.neo4j.driver.Value; -public class MessageReaderV1 implements MessageFormat.Reader +public class CommonMessageReader implements MessageFormat.Reader { private final ValueUnpacker unpacker; - public MessageReaderV1( PackInput input ) + public CommonMessageReader( PackInput input ) { - this( new ValueUnpackerV1( input ) ); + this( new CommonValueUnpacker( input ) ); } - protected MessageReaderV1( ValueUnpacker unpacker ) + protected CommonMessageReader( ValueUnpacker unpacker ) { this.unpacker = unpacker; } diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/v2/ValuePackerV2.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/common/CommonValuePacker.java similarity index 61% rename from driver/src/main/java/org/neo4j/driver/internal/messaging/v2/ValuePackerV2.java rename to driver/src/main/java/org/neo4j/driver/internal/messaging/common/CommonValuePacker.java index 89bb611ba0..74fb43da33 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/v2/ValuePackerV2.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/common/CommonValuePacker.java @@ -16,7 +16,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal.messaging.v2; + +package org.neo4j.driver.internal.messaging.common; import java.io.IOException; import java.time.LocalDate; @@ -26,47 +27,99 @@ import java.time.ZoneId; import java.time.ZoneOffset; import java.time.ZonedDateTime; +import java.util.Map; +import org.neo4j.driver.Value; import org.neo4j.driver.internal.InternalPoint2D; import org.neo4j.driver.internal.InternalPoint3D; -import org.neo4j.driver.internal.messaging.v1.ValuePackerV1; +import org.neo4j.driver.internal.messaging.ValuePacker; import org.neo4j.driver.internal.packstream.PackOutput; -import org.neo4j.driver.internal.types.TypeConstructor; +import org.neo4j.driver.internal.packstream.PackStream; import org.neo4j.driver.internal.value.InternalValue; import org.neo4j.driver.types.IsoDuration; import org.neo4j.driver.types.Point; import static java.time.ZoneOffset.UTC; -import static org.neo4j.driver.internal.messaging.v2.MessageFormatV2.DATE; -import static org.neo4j.driver.internal.messaging.v2.MessageFormatV2.DATE_STRUCT_SIZE; -import static org.neo4j.driver.internal.messaging.v2.MessageFormatV2.DATE_TIME_STRUCT_SIZE; -import static org.neo4j.driver.internal.messaging.v2.MessageFormatV2.DATE_TIME_WITH_ZONE_ID; -import static org.neo4j.driver.internal.messaging.v2.MessageFormatV2.DATE_TIME_WITH_ZONE_OFFSET; -import static org.neo4j.driver.internal.messaging.v2.MessageFormatV2.DURATION; -import static org.neo4j.driver.internal.messaging.v2.MessageFormatV2.DURATION_TIME_STRUCT_SIZE; -import static org.neo4j.driver.internal.messaging.v2.MessageFormatV2.LOCAL_DATE_TIME; -import static org.neo4j.driver.internal.messaging.v2.MessageFormatV2.LOCAL_DATE_TIME_STRUCT_SIZE; -import static org.neo4j.driver.internal.messaging.v2.MessageFormatV2.LOCAL_TIME; -import static org.neo4j.driver.internal.messaging.v2.MessageFormatV2.LOCAL_TIME_STRUCT_SIZE; -import static org.neo4j.driver.internal.messaging.v2.MessageFormatV2.POINT_2D_STRUCT_SIZE; -import static org.neo4j.driver.internal.messaging.v2.MessageFormatV2.POINT_2D_STRUCT_TYPE; -import static org.neo4j.driver.internal.messaging.v2.MessageFormatV2.POINT_3D_STRUCT_SIZE; -import static org.neo4j.driver.internal.messaging.v2.MessageFormatV2.POINT_3D_STRUCT_TYPE; -import static org.neo4j.driver.internal.messaging.v2.MessageFormatV2.TIME; -import static org.neo4j.driver.internal.messaging.v2.MessageFormatV2.TIME_STRUCT_SIZE; - -public class ValuePackerV2 extends ValuePackerV1 + +public class CommonValuePacker implements ValuePacker { - public ValuePackerV2( PackOutput output ) + + public static final byte DATE = 'D'; + public static final int DATE_STRUCT_SIZE = 1; + + public static final byte TIME = 'T'; + public static final int TIME_STRUCT_SIZE = 2; + + public static final byte LOCAL_TIME = 't'; + public static final int LOCAL_TIME_STRUCT_SIZE = 1; + + public static final byte LOCAL_DATE_TIME = 'd'; + public static final int LOCAL_DATE_TIME_STRUCT_SIZE = 2; + + public static final byte DATE_TIME_WITH_ZONE_OFFSET = 'F'; + public static final byte DATE_TIME_WITH_ZONE_ID = 'f'; + public static final int DATE_TIME_STRUCT_SIZE = 3; + + public static final byte DURATION = 'E'; + public static final int DURATION_TIME_STRUCT_SIZE = 4; + + public static final byte POINT_2D_STRUCT_TYPE = 'X'; + public static final int POINT_2D_STRUCT_SIZE = 3; + + public static final byte POINT_3D_STRUCT_TYPE = 'Y'; + public static final int POINT_3D_STRUCT_SIZE = 4; + + protected final PackStream.Packer packer; + + public CommonValuePacker( PackOutput output ) + { + this.packer = new PackStream.Packer( output ); + } + + @Override + public final void packStructHeader( int size, byte signature ) throws IOException + { + packer.packStructHeader( size, signature ); + } + + @Override + public final void pack( String string ) throws IOException { - super( output ); + packer.pack( string ); + } + + @Override + public final void pack( Value value ) throws IOException + { + if ( value instanceof InternalValue ) + { + packInternalValue( ((InternalValue) value) ); + } + else + { + throw new IllegalArgumentException( "Unable to pack: " + value ); + } } @Override + public final void pack( Map map ) throws IOException + { + if ( map == null || map.size() == 0 ) + { + packer.packMapHeader( 0 ); + return; + } + packer.packMapHeader( map.size() ); + for ( Map.Entry entry : map.entrySet() ) + { + packer.pack( entry.getKey() ); + pack( entry.getValue() ); + } + } + protected void packInternalValue( InternalValue value ) throws IOException { - TypeConstructor typeConstructor = value.typeConstructor(); - switch ( typeConstructor ) + switch ( value.typeConstructor() ) { case DATE: packDate( value.asLocalDate() ); @@ -89,8 +142,49 @@ protected void packInternalValue( InternalValue value ) throws IOException case POINT: packPoint( value.asPoint() ); break; + case NULL: + packer.packNull(); + break; + + case BYTES: + packer.pack( value.asByteArray() ); + break; + + case STRING: + packer.pack( value.asString() ); + break; + + case BOOLEAN: + packer.pack( value.asBoolean() ); + break; + + case INTEGER: + packer.pack( value.asLong() ); + break; + + case FLOAT: + packer.pack( value.asDouble() ); + break; + + case MAP: + packer.packMapHeader( value.size() ); + for ( String s : value.keys() ) + { + packer.pack( s ); + pack( value.get( s ) ); + } + break; + + case LIST: + packer.packListHeader( value.size() ); + for ( Value item : value.values() ) + { + pack( item ); + } + break; + default: - super.packInternalValue( value ); + throw new IOException( "Unknown type: " + value.type().name() ); } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/v1/ValueUnpackerV1.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/common/CommonValueUnpacker.java similarity index 58% rename from driver/src/main/java/org/neo4j/driver/internal/messaging/v1/ValueUnpackerV1.java rename to driver/src/main/java/org/neo4j/driver/internal/messaging/common/CommonValueUnpacker.java index f690656c38..ee3a574e6f 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/v1/ValueUnpackerV1.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/common/CommonValueUnpacker.java @@ -1,3 +1,4 @@ + /* * Copyright (c) 2002-2020 "Neo4j," * Neo4j Sweden AB [http://neo4j.com] @@ -16,15 +17,25 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.neo4j.driver.internal.messaging.v1; +package org.neo4j.driver.internal.messaging.common; import java.io.IOException; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; +import org.neo4j.driver.Value; +import org.neo4j.driver.exceptions.ClientException; import org.neo4j.driver.internal.InternalNode; import org.neo4j.driver.internal.InternalPath; import org.neo4j.driver.internal.InternalRelationship; @@ -39,19 +50,53 @@ import org.neo4j.driver.internal.value.NodeValue; import org.neo4j.driver.internal.value.PathValue; import org.neo4j.driver.internal.value.RelationshipValue; -import org.neo4j.driver.Value; -import org.neo4j.driver.exceptions.ClientException; import org.neo4j.driver.types.Node; import org.neo4j.driver.types.Path; import org.neo4j.driver.types.Relationship; +import static java.time.ZoneOffset.UTC; +import static org.neo4j.driver.Values.isoDuration; +import static org.neo4j.driver.Values.point; import static org.neo4j.driver.Values.value; -public class ValueUnpackerV1 implements ValueUnpacker +public class CommonValueUnpacker implements ValueUnpacker { + + public static final byte DATE = 'D'; + public static final int DATE_STRUCT_SIZE = 1; + + public static final byte TIME = 'T'; + public static final int TIME_STRUCT_SIZE = 2; + + public static final byte LOCAL_TIME = 't'; + public static final int LOCAL_TIME_STRUCT_SIZE = 1; + + public static final byte LOCAL_DATE_TIME = 'd'; + public static final int LOCAL_DATE_TIME_STRUCT_SIZE = 2; + + public static final byte DATE_TIME_WITH_ZONE_OFFSET = 'F'; + public static final byte DATE_TIME_WITH_ZONE_ID = 'f'; + public static final int DATE_TIME_STRUCT_SIZE = 3; + + public static final byte DURATION = 'E'; + public static final int DURATION_TIME_STRUCT_SIZE = 4; + + public static final byte POINT_2D_STRUCT_TYPE = 'X'; + public static final int POINT_2D_STRUCT_SIZE = 3; + + public static final byte POINT_3D_STRUCT_TYPE = 'Y'; + public static final int POINT_3D_STRUCT_SIZE = 4; + + public static final byte NODE = 'N'; + public static final byte RELATIONSHIP = 'R'; + public static final byte UNBOUND_RELATIONSHIP = 'r'; + public static final byte PATH = 'P'; + + public static final int NODE_FIELDS = 3; + protected final PackStream.Unpacker unpacker; - public ValueUnpackerV1( PackInput input ) + public CommonValueUnpacker( PackInput input ) { this.unpacker = new PackStream.Unpacker( input ); } @@ -142,14 +187,41 @@ protected Value unpackStruct( long size, byte type ) throws IOException { switch ( type ) { - case MessageFormatV1.NODE: - ensureCorrectStructSize( TypeConstructor.NODE, MessageFormatV1.NODE_FIELDS, size ); + case DATE: + ensureCorrectStructSize( TypeConstructor.DATE, DATE_STRUCT_SIZE, size ); + return unpackDate(); + case TIME: + ensureCorrectStructSize( TypeConstructor.TIME, TIME_STRUCT_SIZE, size ); + return unpackTime(); + case LOCAL_TIME: + ensureCorrectStructSize( TypeConstructor.LOCAL_TIME, LOCAL_TIME_STRUCT_SIZE, size ); + return unpackLocalTime(); + case LOCAL_DATE_TIME: + ensureCorrectStructSize( TypeConstructor.LOCAL_DATE_TIME, LOCAL_DATE_TIME_STRUCT_SIZE, size ); + return unpackLocalDateTime(); + case DATE_TIME_WITH_ZONE_OFFSET: + ensureCorrectStructSize( TypeConstructor.DATE_TIME, DATE_TIME_STRUCT_SIZE, size ); + return unpackDateTimeWithZoneOffset(); + case DATE_TIME_WITH_ZONE_ID: + ensureCorrectStructSize( TypeConstructor.DATE_TIME, DATE_TIME_STRUCT_SIZE, size ); + return unpackDateTimeWithZoneId(); + case DURATION: + ensureCorrectStructSize( TypeConstructor.DURATION, DURATION_TIME_STRUCT_SIZE, size ); + return unpackDuration(); + case POINT_2D_STRUCT_TYPE: + ensureCorrectStructSize( TypeConstructor.POINT, POINT_2D_STRUCT_SIZE, size ); + return unpackPoint2D(); + case POINT_3D_STRUCT_TYPE: + ensureCorrectStructSize( TypeConstructor.POINT, POINT_3D_STRUCT_SIZE, size ); + return unpackPoint3D(); + case NODE: + ensureCorrectStructSize( TypeConstructor.NODE, NODE_FIELDS, size ); InternalNode adapted = unpackNode(); return new NodeValue( adapted ); - case MessageFormatV1.RELATIONSHIP: + case RELATIONSHIP: ensureCorrectStructSize( TypeConstructor.RELATIONSHIP, 5, size ); return unpackRelationship(); - case MessageFormatV1.PATH: + case PATH: ensureCorrectStructSize( TypeConstructor.PATH, 3, size ); return unpackPath(); default: @@ -196,8 +268,8 @@ private Value unpackPath() throws IOException Node[] uniqNodes = new Node[(int) unpacker.unpackListHeader()]; for ( int i = 0; i < uniqNodes.length; i++ ) { - ensureCorrectStructSize( TypeConstructor.NODE, MessageFormatV1.NODE_FIELDS, unpacker.unpackStructHeader() ); - ensureCorrectStructSignature( "NODE", MessageFormatV1.NODE, unpacker.unpackStructSignature() ); + ensureCorrectStructSize( TypeConstructor.NODE, NODE_FIELDS, unpacker.unpackStructHeader() ); + ensureCorrectStructSignature( "NODE", NODE, unpacker.unpackStructSignature() ); uniqNodes[i] = unpackNode(); } @@ -206,7 +278,7 @@ private Value unpackPath() throws IOException for ( int i = 0; i < uniqRels.length; i++ ) { ensureCorrectStructSize( TypeConstructor.RELATIONSHIP, 3, unpacker.unpackStructHeader() ); - ensureCorrectStructSignature( "UNBOUND_RELATIONSHIP", MessageFormatV1.UNBOUND_RELATIONSHIP, unpacker.unpackStructSignature() ); + ensureCorrectStructSignature( "UNBOUND_RELATIONSHIP", UNBOUND_RELATIONSHIP, unpacker.unpackStructSignature() ); long id = unpacker.unpackLong(); String relType = unpacker.unpackString(); Map props = unpackMap(); @@ -268,4 +340,84 @@ private void ensureCorrectStructSignature( String structName, byte expected, byt structName, Integer.toHexString( expected ), Integer.toHexString( actual ) ) ); } } + + private Value unpackDate() throws IOException + { + long epochDay = unpacker.unpackLong(); + return value( LocalDate.ofEpochDay( epochDay ) ); + } + + private Value unpackTime() throws IOException + { + long nanoOfDayLocal = unpacker.unpackLong(); + int offsetSeconds = Math.toIntExact( unpacker.unpackLong() ); + + LocalTime localTime = LocalTime.ofNanoOfDay( nanoOfDayLocal ); + ZoneOffset offset = ZoneOffset.ofTotalSeconds( offsetSeconds ); + return value( OffsetTime.of( localTime, offset ) ); + } + + private Value unpackLocalTime() throws IOException + { + long nanoOfDayLocal = unpacker.unpackLong(); + return value( LocalTime.ofNanoOfDay( nanoOfDayLocal ) ); + } + + private Value unpackLocalDateTime() throws IOException + { + long epochSecondUtc = unpacker.unpackLong(); + int nano = Math.toIntExact( unpacker.unpackLong() ); + return value( LocalDateTime.ofEpochSecond( epochSecondUtc, nano, UTC ) ); + } + + private Value unpackDateTimeWithZoneOffset() throws IOException + { + long epochSecondLocal = unpacker.unpackLong(); + int nano = Math.toIntExact( unpacker.unpackLong() ); + int offsetSeconds = Math.toIntExact( unpacker.unpackLong() ); + return value( newZonedDateTime( epochSecondLocal, nano, ZoneOffset.ofTotalSeconds( offsetSeconds ) ) ); + } + + private Value unpackDateTimeWithZoneId() throws IOException + { + long epochSecondLocal = unpacker.unpackLong(); + int nano = Math.toIntExact( unpacker.unpackLong() ); + String zoneIdString = unpacker.unpackString(); + return value( newZonedDateTime( epochSecondLocal, nano, ZoneId.of( zoneIdString ) ) ); + } + + private Value unpackDuration() throws IOException + { + long months = unpacker.unpackLong(); + long days = unpacker.unpackLong(); + long seconds = unpacker.unpackLong(); + int nanoseconds = Math.toIntExact( unpacker.unpackLong() ); + return isoDuration( months, days, seconds, nanoseconds ); + } + + private Value unpackPoint2D() throws IOException + { + int srid = Math.toIntExact( unpacker.unpackLong() ); + double x = unpacker.unpackDouble(); + double y = unpacker.unpackDouble(); + return point( srid, x, y ); + } + + private Value unpackPoint3D() throws IOException + { + int srid = Math.toIntExact( unpacker.unpackLong() ); + double x = unpacker.unpackDouble(); + double y = unpacker.unpackDouble(); + double z = unpacker.unpackDouble(); + return point( srid, x, y, z ); + } + + private static ZonedDateTime newZonedDateTime( long epochSecondLocal, long nano, ZoneId zoneId ) + { + Instant instant = Instant.ofEpochSecond( epochSecondLocal, nano ); + LocalDateTime localDateTime = LocalDateTime.ofInstant( instant, UTC ); + return ZonedDateTime.of( localDateTime, zoneId ); + } } + + diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/v1/BoltProtocolV1.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/v1/BoltProtocolV1.java deleted file mode 100644 index 289a4bace7..0000000000 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/v1/BoltProtocolV1.java +++ /dev/null @@ -1,287 +0,0 @@ -/* - * Copyright (c) 2002-2020 "Neo4j," - * Neo4j Sweden AB [http://neo4j.com] - * - * This file is part of Neo4j. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.neo4j.driver.internal.messaging.v1; - -import io.netty.channel.Channel; -import io.netty.channel.ChannelPromise; - -import java.util.Iterator; -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; - -import org.neo4j.driver.AuthToken; -import org.neo4j.driver.Bookmark; -import org.neo4j.driver.Query; -import org.neo4j.driver.TransactionConfig; -import org.neo4j.driver.Value; -import org.neo4j.driver.exceptions.ClientException; -import org.neo4j.driver.internal.BookmarkHolder; -import org.neo4j.driver.internal.DatabaseName; -import org.neo4j.driver.internal.InternalBookmark; -import org.neo4j.driver.internal.async.UnmanagedTransaction; -import org.neo4j.driver.internal.cluster.RoutingContext; -import org.neo4j.driver.internal.cursor.AsyncResultCursorOnlyFactory; -import org.neo4j.driver.internal.cursor.ResultCursorFactory; -import org.neo4j.driver.internal.handlers.BeginTxResponseHandler; -import org.neo4j.driver.internal.handlers.CommitTxResponseHandler; -import org.neo4j.driver.internal.handlers.InitResponseHandler; -import org.neo4j.driver.internal.handlers.NoOpResponseHandler; -import org.neo4j.driver.internal.handlers.PullAllResponseHandler; -import org.neo4j.driver.internal.handlers.PullHandlers; -import org.neo4j.driver.internal.handlers.RollbackTxResponseHandler; -import org.neo4j.driver.internal.handlers.RunResponseHandler; -import org.neo4j.driver.internal.messaging.BoltProtocol; -import org.neo4j.driver.internal.messaging.BoltProtocolVersion; -import org.neo4j.driver.internal.messaging.Message; -import org.neo4j.driver.internal.messaging.MessageFormat; -import org.neo4j.driver.internal.messaging.request.InitMessage; -import org.neo4j.driver.internal.messaging.request.PullAllMessage; -import org.neo4j.driver.internal.messaging.request.RunMessage; -import org.neo4j.driver.internal.security.InternalAuthToken; -import org.neo4j.driver.internal.spi.Connection; -import org.neo4j.driver.internal.spi.ResponseHandler; -import org.neo4j.driver.internal.util.Futures; -import org.neo4j.driver.internal.util.MetadataExtractor; - -import static java.util.Collections.emptyMap; -import static org.neo4j.driver.Values.ofValue; -import static org.neo4j.driver.Values.value; -import static org.neo4j.driver.internal.async.connection.ChannelAttributes.messageDispatcher; -import static org.neo4j.driver.internal.messaging.request.MultiDatabaseUtil.assertEmptyDatabaseName; -import static org.neo4j.driver.internal.util.Iterables.newHashMapWithSize; - -public class BoltProtocolV1 implements BoltProtocol -{ - public static final BoltProtocolVersion VERSION = new BoltProtocolVersion( 1, 0 ); - - public static final BoltProtocol INSTANCE = new BoltProtocolV1(); - - public static final MetadataExtractor METADATA_EXTRACTOR = new MetadataExtractor( "result_available_after", "result_consumed_after" ); - - private static final String BEGIN_QUERY = "BEGIN"; - private static final Message BEGIN_MESSAGE = new RunMessage( BEGIN_QUERY ); - private static final Message COMMIT_MESSAGE = new RunMessage( "COMMIT" ); - private static final Message ROLLBACK_MESSAGE = new RunMessage( "ROLLBACK" ); - - @Override - public MessageFormat createMessageFormat() - { - return new MessageFormatV1(); - } - - @Override - public void initializeChannel( String userAgent, AuthToken authToken, RoutingContext routingContext, - ChannelPromise channelInitializedPromise ) - { - Channel channel = channelInitializedPromise.channel(); - - InitMessage message = new InitMessage( userAgent, ((InternalAuthToken) authToken).toMap() ); - InitResponseHandler handler = new InitResponseHandler( channelInitializedPromise ); - - messageDispatcher( channel ).enqueue( handler ); - channel.writeAndFlush( message, channel.voidPromise() ); - } - - @Override - public void prepareToCloseChannel( Channel channel ) - { - // left empty on purpose. - } - - @Override - public CompletionStage beginTransaction( Connection connection, Bookmark bookmark, TransactionConfig config ) - { - try - { - verifyBeforeTransaction( config, connection.databaseName() ); - } - catch ( Exception error ) - { - return Futures.failedFuture( error ); - } - - if ( bookmark.isEmpty() ) - { - connection.write( - BEGIN_MESSAGE, NoOpResponseHandler.INSTANCE, - PullAllMessage.PULL_ALL, NoOpResponseHandler.INSTANCE ); - - return Futures.completedWithNull(); - } - else - { - CompletableFuture beginTxFuture = new CompletableFuture<>(); - connection.writeAndFlush( - new RunMessage( BEGIN_QUERY, SingleBookmarkHelper.asBeginTransactionParameters( bookmark ) ), NoOpResponseHandler.INSTANCE, - PullAllMessage.PULL_ALL, new BeginTxResponseHandler( beginTxFuture ) ); - - return beginTxFuture; - } - } - - - - @Override - public CompletionStage commitTransaction( Connection connection ) - { - CompletableFuture commitFuture = new CompletableFuture<>(); - - ResponseHandler pullAllHandler = new CommitTxResponseHandler( commitFuture ); - connection.writeAndFlush( - COMMIT_MESSAGE, NoOpResponseHandler.INSTANCE, - PullAllMessage.PULL_ALL, pullAllHandler ); - - return commitFuture; - } - - @Override - public CompletionStage rollbackTransaction( Connection connection ) - { - CompletableFuture rollbackFuture = new CompletableFuture<>(); - - ResponseHandler pullAllHandler = new RollbackTxResponseHandler( rollbackFuture ); - connection.writeAndFlush( - ROLLBACK_MESSAGE, NoOpResponseHandler.INSTANCE, - PullAllMessage.PULL_ALL, pullAllHandler ); - - return rollbackFuture; - } - - @Override - public ResultCursorFactory runInAutoCommitTransaction(Connection connection, Query query, BookmarkHolder bookmarkHolder, - TransactionConfig config, boolean waitForRunResponse, long ignored ) - { - // bookmarks are ignored for auto-commit transactions in this version of the protocol - verifyBeforeTransaction( config, connection.databaseName() ); - return buildResultCursorFactory( connection, query, null, waitForRunResponse ); - } - - @Override - public ResultCursorFactory runInUnmanagedTransaction(Connection connection, Query query, UnmanagedTransaction tx, - boolean waitForRunResponse, long ignored ) - { - return buildResultCursorFactory( connection, query, tx, waitForRunResponse ); - } - - @Override - public BoltProtocolVersion version() - { - return VERSION; - } - - private static ResultCursorFactory buildResultCursorFactory(Connection connection, Query query, - UnmanagedTransaction tx, boolean waitForRunResponse ) - { - String queryText = query.text(); - Map params = query.parameters().asMap( ofValue() ); - - RunMessage runMessage = new RunMessage( queryText, params ); - RunResponseHandler runHandler = new RunResponseHandler( METADATA_EXTRACTOR ); - PullAllResponseHandler pullAllHandler = PullHandlers.newBoltV1PullAllHandler( query, runHandler, connection, tx ); - - return new AsyncResultCursorOnlyFactory( connection, runMessage, runHandler, pullAllHandler, waitForRunResponse ); - } - - private void verifyBeforeTransaction( TransactionConfig config, DatabaseName databaseName ) - { - if ( config != null && !config.isEmpty() ) - { - throw txConfigNotSupported(); - } - assertEmptyDatabaseName( databaseName, version() ); - } - - private static ClientException txConfigNotSupported() - { - return new ClientException( "Driver is connected to the database that does not support transaction configuration. " + - "Please upgrade to neo4j 3.5.0 or later in order to use this functionality" ); - } - - static class SingleBookmarkHelper - { - private static final String BOOKMARK_PREFIX = "neo4j:bookmark:v1:tx"; - private static final long UNKNOWN_BOOKMARK_VALUE = -1; - - static Map asBeginTransactionParameters( Bookmark bookmark ) - { - if ( bookmark.isEmpty() ) - { - return emptyMap(); - } - - // Driver sends {bookmark: "max", bookmarks: ["one", "two", "max"]} instead of simple - // {bookmarks: ["one", "two", "max"]} for backwards compatibility reasons. Old servers can only accept single - // bookmark that is why driver has to parse and compare given list of bookmarks. This functionality will - // eventually be removed. - Map parameters = newHashMapWithSize( 1 ); - parameters.put( "bookmark", value( maxBookmark( bookmark.values() ) ) ); - parameters.put( "bookmarks", value( bookmark.values() ) ); - return parameters; - } - - private static String maxBookmark( Iterable bookmarks ) - { - if ( bookmarks == null ) - { - return null; - } - - Iterator iterator = bookmarks.iterator(); - - if ( !iterator.hasNext() ) - { - return null; - } - - String maxBookmark = iterator.next(); - long maxValue = bookmarkValue( maxBookmark ); - - while ( iterator.hasNext() ) - { - String bookmark = iterator.next(); - long value = bookmarkValue( bookmark ); - - if ( value > maxValue ) - { - maxBookmark = bookmark; - maxValue = value; - } - } - - return maxBookmark; - } - - private static long bookmarkValue( String value ) - { - if ( value != null && value.startsWith( BOOKMARK_PREFIX ) ) - { - try - { - return Long.parseLong( value.substring( BOOKMARK_PREFIX.length() ) ); - } - catch ( NumberFormatException e ) - { - return UNKNOWN_BOOKMARK_VALUE; - } - } - return UNKNOWN_BOOKMARK_VALUE; - } - } -} diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/v1/MessageFormatV1.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/v1/MessageFormatV1.java deleted file mode 100644 index 96fe643624..0000000000 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/v1/MessageFormatV1.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2002-2020 "Neo4j," - * Neo4j Sweden AB [http://neo4j.com] - * - * This file is part of Neo4j. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.neo4j.driver.internal.messaging.v1; - -import org.neo4j.driver.internal.messaging.MessageFormat; -import org.neo4j.driver.internal.packstream.PackInput; -import org.neo4j.driver.internal.packstream.PackOutput; - -public class MessageFormatV1 implements MessageFormat -{ - public static final byte NODE = 'N'; - public static final byte RELATIONSHIP = 'R'; - public static final byte UNBOUND_RELATIONSHIP = 'r'; - public static final byte PATH = 'P'; - - public static final int NODE_FIELDS = 3; - - @Override - public MessageFormat.Writer newWriter( PackOutput output ) - { - return new MessageWriterV1( output ); - } - - @Override - public MessageFormat.Reader newReader( PackInput input ) - { - return new MessageReaderV1( input ); - } -} diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/v1/MessageWriterV1.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/v1/MessageWriterV1.java deleted file mode 100644 index b67ae182a3..0000000000 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/v1/MessageWriterV1.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2002-2020 "Neo4j," - * Neo4j Sweden AB [http://neo4j.com] - * - * This file is part of Neo4j. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.neo4j.driver.internal.messaging.v1; - -import java.util.Map; - -import org.neo4j.driver.internal.messaging.AbstractMessageWriter; -import org.neo4j.driver.internal.messaging.MessageEncoder; -import org.neo4j.driver.internal.messaging.ValuePacker; -import org.neo4j.driver.internal.messaging.encode.DiscardAllMessageEncoder; -import org.neo4j.driver.internal.messaging.encode.InitMessageEncoder; -import org.neo4j.driver.internal.messaging.encode.PullAllMessageEncoder; -import org.neo4j.driver.internal.messaging.encode.ResetMessageEncoder; -import org.neo4j.driver.internal.messaging.encode.RunMessageEncoder; -import org.neo4j.driver.internal.messaging.request.DiscardAllMessage; -import org.neo4j.driver.internal.messaging.request.InitMessage; -import org.neo4j.driver.internal.messaging.request.PullAllMessage; -import org.neo4j.driver.internal.messaging.request.ResetMessage; -import org.neo4j.driver.internal.messaging.request.RunMessage; -import org.neo4j.driver.internal.packstream.PackOutput; -import org.neo4j.driver.internal.util.Iterables; - -public class MessageWriterV1 extends AbstractMessageWriter -{ - public MessageWriterV1( PackOutput output ) - { - this( new ValuePackerV1( output ) ); - } - - protected MessageWriterV1( ValuePacker packer ) - { - super( packer, buildEncoders() ); - } - - private static Map buildEncoders() - { - Map result = Iterables.newHashMapWithSize( 6 ); - result.put( DiscardAllMessage.SIGNATURE, new DiscardAllMessageEncoder() ); - result.put( InitMessage.SIGNATURE, new InitMessageEncoder() ); - result.put( PullAllMessage.SIGNATURE, new PullAllMessageEncoder() ); - result.put( ResetMessage.SIGNATURE, new ResetMessageEncoder() ); - result.put( RunMessage.SIGNATURE, new RunMessageEncoder() ); - return result; - } -} diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/v1/ValuePackerV1.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/v1/ValuePackerV1.java deleted file mode 100644 index 62d163abc0..0000000000 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/v1/ValuePackerV1.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (c) 2002-2020 "Neo4j," - * Neo4j Sweden AB [http://neo4j.com] - * - * This file is part of Neo4j. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.neo4j.driver.internal.messaging.v1; - -import java.io.IOException; -import java.util.Map; - -import org.neo4j.driver.internal.messaging.ValuePacker; -import org.neo4j.driver.internal.packstream.PackOutput; -import org.neo4j.driver.internal.packstream.PackStream; -import org.neo4j.driver.internal.value.InternalValue; -import org.neo4j.driver.Value; - -public class ValuePackerV1 implements ValuePacker -{ - protected final PackStream.Packer packer; - - public ValuePackerV1( PackOutput output ) - { - this.packer = new PackStream.Packer( output ); - } - - @Override - public final void packStructHeader( int size, byte signature ) throws IOException - { - packer.packStructHeader( size, signature ); - } - - @Override - public final void pack( String string ) throws IOException - { - packer.pack( string ); - } - - @Override - public final void pack( Value value ) throws IOException - { - if ( value instanceof InternalValue ) - { - packInternalValue( ((InternalValue) value) ); - } - else - { - throw new IllegalArgumentException( "Unable to pack: " + value ); - } - } - - @Override - public final void pack( Map map ) throws IOException - { - if ( map == null || map.size() == 0 ) - { - packer.packMapHeader( 0 ); - return; - } - packer.packMapHeader( map.size() ); - for ( Map.Entry entry : map.entrySet() ) - { - packer.pack( entry.getKey() ); - pack( entry.getValue() ); - } - } - - protected void packInternalValue( InternalValue value ) throws IOException - { - switch ( value.typeConstructor() ) - { - case NULL: - packer.packNull(); - break; - - case BYTES: - packer.pack( value.asByteArray() ); - break; - - case STRING: - packer.pack( value.asString() ); - break; - - case BOOLEAN: - packer.pack( value.asBoolean() ); - break; - - case INTEGER: - packer.pack( value.asLong() ); - break; - - case FLOAT: - packer.pack( value.asDouble() ); - break; - - case MAP: - packer.packMapHeader( value.size() ); - for ( String s : value.keys() ) - { - packer.pack( s ); - pack( value.get( s ) ); - } - break; - - case LIST: - packer.packListHeader( value.size() ); - for ( Value item : value.values() ) - { - pack( item ); - } - break; - - default: - throw new IOException( "Unknown type: " + value.type().name() ); - } - } -} diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/v2/BoltProtocolV2.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/v2/BoltProtocolV2.java deleted file mode 100644 index 8ca7fc0aac..0000000000 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/v2/BoltProtocolV2.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2002-2020 "Neo4j," - * Neo4j Sweden AB [http://neo4j.com] - * - * This file is part of Neo4j. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.neo4j.driver.internal.messaging.v2; - -import org.neo4j.driver.internal.messaging.BoltProtocol; -import org.neo4j.driver.internal.messaging.BoltProtocolVersion; -import org.neo4j.driver.internal.messaging.MessageFormat; -import org.neo4j.driver.internal.messaging.v1.BoltProtocolV1; - -public class BoltProtocolV2 extends BoltProtocolV1 -{ - public static final BoltProtocolVersion VERSION = new BoltProtocolVersion( 2, 0 ); - - public static final BoltProtocol INSTANCE = new BoltProtocolV2(); - - @Override - public MessageFormat createMessageFormat() - { - return new MessageFormatV2(); - } - - @Override - public BoltProtocolVersion version() - { - return VERSION; - } -} diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/v2/MessageFormatV2.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/v2/MessageFormatV2.java deleted file mode 100644 index addbc6ab23..0000000000 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/v2/MessageFormatV2.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) 2002-2020 "Neo4j," - * Neo4j Sweden AB [http://neo4j.com] - * - * This file is part of Neo4j. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.neo4j.driver.internal.messaging.v2; - -import org.neo4j.driver.internal.messaging.v1.MessageFormatV1; -import org.neo4j.driver.internal.packstream.PackInput; -import org.neo4j.driver.internal.packstream.PackOutput; - -public class MessageFormatV2 extends MessageFormatV1 -{ - public static final byte DATE = 'D'; - public static final int DATE_STRUCT_SIZE = 1; - - public static final byte TIME = 'T'; - public static final int TIME_STRUCT_SIZE = 2; - - public static final byte LOCAL_TIME = 't'; - public static final int LOCAL_TIME_STRUCT_SIZE = 1; - - public static final byte LOCAL_DATE_TIME = 'd'; - public static final int LOCAL_DATE_TIME_STRUCT_SIZE = 2; - - public static final byte DATE_TIME_WITH_ZONE_OFFSET = 'F'; - public static final byte DATE_TIME_WITH_ZONE_ID = 'f'; - public static final int DATE_TIME_STRUCT_SIZE = 3; - - public static final byte DURATION = 'E'; - public static final int DURATION_TIME_STRUCT_SIZE = 4; - - public static final byte POINT_2D_STRUCT_TYPE = 'X'; - public static final int POINT_2D_STRUCT_SIZE = 3; - - public static final byte POINT_3D_STRUCT_TYPE = 'Y'; - public static final int POINT_3D_STRUCT_SIZE = 4; - - @Override - public Writer newWriter( PackOutput output ) - { - return new MessageWriterV2( output ); - } - - @Override - public Reader newReader( PackInput input ) - { - return new MessageReaderV2( input ); - } -} diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/v2/MessageReaderV2.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/v2/MessageReaderV2.java deleted file mode 100644 index 914272eddf..0000000000 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/v2/MessageReaderV2.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2002-2020 "Neo4j," - * Neo4j Sweden AB [http://neo4j.com] - * - * This file is part of Neo4j. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.neo4j.driver.internal.messaging.v2; - -import org.neo4j.driver.internal.messaging.v1.MessageReaderV1; -import org.neo4j.driver.internal.packstream.PackInput; - -public class MessageReaderV2 extends MessageReaderV1 -{ - public MessageReaderV2( PackInput input ) - { - super( new ValueUnpackerV2( input ) ); - } -} diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/v2/MessageWriterV2.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/v2/MessageWriterV2.java deleted file mode 100644 index 3612b6cd74..0000000000 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/v2/MessageWriterV2.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2002-2020 "Neo4j," - * Neo4j Sweden AB [http://neo4j.com] - * - * This file is part of Neo4j. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.neo4j.driver.internal.messaging.v2; - -import org.neo4j.driver.internal.messaging.v1.MessageWriterV1; -import org.neo4j.driver.internal.packstream.PackOutput; - -public class MessageWriterV2 extends MessageWriterV1 -{ - public MessageWriterV2( PackOutput output ) - { - super( new ValuePackerV2( output ) ); - } -} diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/v2/ValueUnpackerV2.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/v2/ValueUnpackerV2.java deleted file mode 100644 index 34911f7108..0000000000 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/v2/ValueUnpackerV2.java +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright (c) 2002-2020 "Neo4j," - * Neo4j Sweden AB [http://neo4j.com] - * - * This file is part of Neo4j. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.neo4j.driver.internal.messaging.v2; - -import java.io.IOException; -import java.time.Instant; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.time.OffsetTime; -import java.time.ZoneId; -import java.time.ZoneOffset; -import java.time.ZonedDateTime; - -import org.neo4j.driver.internal.messaging.v1.ValueUnpackerV1; -import org.neo4j.driver.internal.packstream.PackInput; -import org.neo4j.driver.internal.types.TypeConstructor; -import org.neo4j.driver.Value; - -import static java.time.ZoneOffset.UTC; -import static org.neo4j.driver.internal.messaging.v2.MessageFormatV2.DATE; -import static org.neo4j.driver.internal.messaging.v2.MessageFormatV2.DATE_STRUCT_SIZE; -import static org.neo4j.driver.internal.messaging.v2.MessageFormatV2.DATE_TIME_STRUCT_SIZE; -import static org.neo4j.driver.internal.messaging.v2.MessageFormatV2.DATE_TIME_WITH_ZONE_ID; -import static org.neo4j.driver.internal.messaging.v2.MessageFormatV2.DATE_TIME_WITH_ZONE_OFFSET; -import static org.neo4j.driver.internal.messaging.v2.MessageFormatV2.DURATION; -import static org.neo4j.driver.internal.messaging.v2.MessageFormatV2.DURATION_TIME_STRUCT_SIZE; -import static org.neo4j.driver.internal.messaging.v2.MessageFormatV2.LOCAL_DATE_TIME; -import static org.neo4j.driver.internal.messaging.v2.MessageFormatV2.LOCAL_DATE_TIME_STRUCT_SIZE; -import static org.neo4j.driver.internal.messaging.v2.MessageFormatV2.LOCAL_TIME; -import static org.neo4j.driver.internal.messaging.v2.MessageFormatV2.LOCAL_TIME_STRUCT_SIZE; -import static org.neo4j.driver.internal.messaging.v2.MessageFormatV2.POINT_2D_STRUCT_SIZE; -import static org.neo4j.driver.internal.messaging.v2.MessageFormatV2.POINT_2D_STRUCT_TYPE; -import static org.neo4j.driver.internal.messaging.v2.MessageFormatV2.POINT_3D_STRUCT_SIZE; -import static org.neo4j.driver.internal.messaging.v2.MessageFormatV2.POINT_3D_STRUCT_TYPE; -import static org.neo4j.driver.internal.messaging.v2.MessageFormatV2.TIME; -import static org.neo4j.driver.internal.messaging.v2.MessageFormatV2.TIME_STRUCT_SIZE; -import static org.neo4j.driver.Values.isoDuration; -import static org.neo4j.driver.Values.point; -import static org.neo4j.driver.Values.value; - -public class ValueUnpackerV2 extends ValueUnpackerV1 -{ - public ValueUnpackerV2( PackInput input ) - { - super( input ); - } - - @Override - protected Value unpackStruct( long size, byte type ) throws IOException - { - switch ( type ) - { - case DATE: - ensureCorrectStructSize( TypeConstructor.DATE, DATE_STRUCT_SIZE, size ); - return unpackDate(); - case TIME: - ensureCorrectStructSize( TypeConstructor.TIME, TIME_STRUCT_SIZE, size ); - return unpackTime(); - case LOCAL_TIME: - ensureCorrectStructSize( TypeConstructor.LOCAL_TIME, LOCAL_TIME_STRUCT_SIZE, size ); - return unpackLocalTime(); - case LOCAL_DATE_TIME: - ensureCorrectStructSize( TypeConstructor.LOCAL_DATE_TIME, LOCAL_DATE_TIME_STRUCT_SIZE, size ); - return unpackLocalDateTime(); - case DATE_TIME_WITH_ZONE_OFFSET: - ensureCorrectStructSize( TypeConstructor.DATE_TIME, DATE_TIME_STRUCT_SIZE, size ); - return unpackDateTimeWithZoneOffset(); - case DATE_TIME_WITH_ZONE_ID: - ensureCorrectStructSize( TypeConstructor.DATE_TIME, DATE_TIME_STRUCT_SIZE, size ); - return unpackDateTimeWithZoneId(); - case DURATION: - ensureCorrectStructSize( TypeConstructor.DURATION, DURATION_TIME_STRUCT_SIZE, size ); - return unpackDuration(); - case POINT_2D_STRUCT_TYPE: - ensureCorrectStructSize( TypeConstructor.POINT, POINT_2D_STRUCT_SIZE, size ); - return unpackPoint2D(); - case POINT_3D_STRUCT_TYPE: - ensureCorrectStructSize( TypeConstructor.POINT, POINT_3D_STRUCT_SIZE, size ); - return unpackPoint3D(); - default: - return super.unpackStruct( size, type ); - } - } - - private Value unpackDate() throws IOException - { - long epochDay = unpacker.unpackLong(); - return value( LocalDate.ofEpochDay( epochDay ) ); - } - - private Value unpackTime() throws IOException - { - long nanoOfDayLocal = unpacker.unpackLong(); - int offsetSeconds = Math.toIntExact( unpacker.unpackLong() ); - - LocalTime localTime = LocalTime.ofNanoOfDay( nanoOfDayLocal ); - ZoneOffset offset = ZoneOffset.ofTotalSeconds( offsetSeconds ); - return value( OffsetTime.of( localTime, offset ) ); - } - - private Value unpackLocalTime() throws IOException - { - long nanoOfDayLocal = unpacker.unpackLong(); - return value( LocalTime.ofNanoOfDay( nanoOfDayLocal ) ); - } - - private Value unpackLocalDateTime() throws IOException - { - long epochSecondUtc = unpacker.unpackLong(); - int nano = Math.toIntExact( unpacker.unpackLong() ); - return value( LocalDateTime.ofEpochSecond( epochSecondUtc, nano, UTC ) ); - } - - private Value unpackDateTimeWithZoneOffset() throws IOException - { - long epochSecondLocal = unpacker.unpackLong(); - int nano = Math.toIntExact( unpacker.unpackLong() ); - int offsetSeconds = Math.toIntExact( unpacker.unpackLong() ); - return value( newZonedDateTime( epochSecondLocal, nano, ZoneOffset.ofTotalSeconds( offsetSeconds ) ) ); - } - - private Value unpackDateTimeWithZoneId() throws IOException - { - long epochSecondLocal = unpacker.unpackLong(); - int nano = Math.toIntExact( unpacker.unpackLong() ); - String zoneIdString = unpacker.unpackString(); - return value( newZonedDateTime( epochSecondLocal, nano, ZoneId.of( zoneIdString ) ) ); - } - - private Value unpackDuration() throws IOException - { - long months = unpacker.unpackLong(); - long days = unpacker.unpackLong(); - long seconds = unpacker.unpackLong(); - int nanoseconds = Math.toIntExact( unpacker.unpackLong() ); - return isoDuration( months, days, seconds, nanoseconds ); - } - - private Value unpackPoint2D() throws IOException - { - int srid = Math.toIntExact( unpacker.unpackLong() ); - double x = unpacker.unpackDouble(); - double y = unpacker.unpackDouble(); - return point( srid, x, y ); - } - - private Value unpackPoint3D() throws IOException - { - int srid = Math.toIntExact( unpacker.unpackLong() ); - double x = unpacker.unpackDouble(); - double y = unpacker.unpackDouble(); - double z = unpacker.unpackDouble(); - return point( srid, x, y, z ); - } - - private static ZonedDateTime newZonedDateTime( long epochSecondLocal, long nano, ZoneId zoneId ) - { - Instant instant = Instant.ofEpochSecond( epochSecondLocal, nano ); - LocalDateTime localDateTime = LocalDateTime.ofInstant( instant, UTC ); - return ZonedDateTime.of( localDateTime, zoneId ); - } -} diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/v3/MessageFormatV3.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/v3/MessageFormatV3.java index 843af206f9..51bdaabd35 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/v3/MessageFormatV3.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/v3/MessageFormatV3.java @@ -19,7 +19,7 @@ package org.neo4j.driver.internal.messaging.v3; import org.neo4j.driver.internal.messaging.MessageFormat; -import org.neo4j.driver.internal.messaging.v2.MessageReaderV2; +import org.neo4j.driver.internal.messaging.common.CommonMessageReader; import org.neo4j.driver.internal.packstream.PackInput; import org.neo4j.driver.internal.packstream.PackOutput; @@ -34,6 +34,6 @@ public Writer newWriter( PackOutput output ) @Override public Reader newReader( PackInput input ) { - return new MessageReaderV2( input ); + return new CommonMessageReader( input ); } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/v3/MessageWriterV3.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/v3/MessageWriterV3.java index f6c2fd2c5a..55a2fd0dc5 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/v3/MessageWriterV3.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/v3/MessageWriterV3.java @@ -22,6 +22,7 @@ import org.neo4j.driver.internal.messaging.AbstractMessageWriter; import org.neo4j.driver.internal.messaging.MessageEncoder; +import org.neo4j.driver.internal.messaging.common.CommonValuePacker; import org.neo4j.driver.internal.messaging.encode.BeginMessageEncoder; import org.neo4j.driver.internal.messaging.encode.CommitMessageEncoder; import org.neo4j.driver.internal.messaging.encode.DiscardAllMessageEncoder; @@ -40,7 +41,6 @@ import org.neo4j.driver.internal.messaging.request.ResetMessage; import org.neo4j.driver.internal.messaging.request.RollbackMessage; import org.neo4j.driver.internal.messaging.request.RunWithMetadataMessage; -import org.neo4j.driver.internal.messaging.v2.ValuePackerV2; import org.neo4j.driver.internal.packstream.PackOutput; import org.neo4j.driver.internal.util.Iterables; @@ -48,7 +48,7 @@ public class MessageWriterV3 extends AbstractMessageWriter { public MessageWriterV3( PackOutput output ) { - super( new ValuePackerV2( output ), buildEncoders() ); + super( new CommonValuePacker( output ), buildEncoders() ); } private static Map buildEncoders() diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/v4/MessageFormatV4.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/v4/MessageFormatV4.java index fe1c99cc87..8c413cd349 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/v4/MessageFormatV4.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/v4/MessageFormatV4.java @@ -19,7 +19,7 @@ package org.neo4j.driver.internal.messaging.v4; import org.neo4j.driver.internal.messaging.MessageFormat; -import org.neo4j.driver.internal.messaging.v2.MessageReaderV2; +import org.neo4j.driver.internal.messaging.common.CommonMessageReader; import org.neo4j.driver.internal.packstream.PackInput; import org.neo4j.driver.internal.packstream.PackOutput; @@ -34,6 +34,6 @@ public Writer newWriter( PackOutput output ) @Override public Reader newReader( PackInput input ) { - return new MessageReaderV2( input ); + return new CommonMessageReader( input ); } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/messaging/v4/MessageWriterV4.java b/driver/src/main/java/org/neo4j/driver/internal/messaging/v4/MessageWriterV4.java index 14c3c8ebfb..216ebf636f 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/messaging/v4/MessageWriterV4.java +++ b/driver/src/main/java/org/neo4j/driver/internal/messaging/v4/MessageWriterV4.java @@ -22,6 +22,7 @@ import org.neo4j.driver.internal.messaging.AbstractMessageWriter; import org.neo4j.driver.internal.messaging.MessageEncoder; +import org.neo4j.driver.internal.messaging.common.CommonValuePacker; import org.neo4j.driver.internal.messaging.encode.BeginMessageEncoder; import org.neo4j.driver.internal.messaging.encode.CommitMessageEncoder; import org.neo4j.driver.internal.messaging.encode.DiscardMessageEncoder; @@ -40,7 +41,6 @@ import org.neo4j.driver.internal.messaging.request.ResetMessage; import org.neo4j.driver.internal.messaging.request.RollbackMessage; import org.neo4j.driver.internal.messaging.request.RunWithMetadataMessage; -import org.neo4j.driver.internal.messaging.v2.ValuePackerV2; import org.neo4j.driver.internal.packstream.PackOutput; import org.neo4j.driver.internal.util.Iterables; @@ -48,7 +48,7 @@ public class MessageWriterV4 extends AbstractMessageWriter { public MessageWriterV4( PackOutput output ) { - super( new ValuePackerV2( output ), buildEncoders() ); + super( new CommonValuePacker( output ), buildEncoders() ); } private static Map buildEncoders() diff --git a/driver/src/test/java/org/neo4j/driver/internal/InternalResultTest.java b/driver/src/test/java/org/neo4j/driver/internal/InternalResultTest.java index 607d38fe61..5b2d3ced15 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/InternalResultTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/InternalResultTest.java @@ -25,19 +25,20 @@ import java.util.List; import java.util.concurrent.CompletableFuture; -import org.neo4j.driver.Record; import org.neo4j.driver.Query; +import org.neo4j.driver.Record; import org.neo4j.driver.Result; import org.neo4j.driver.Value; import org.neo4j.driver.exceptions.NoSuchRecordException; +import org.neo4j.driver.exceptions.ResultConsumedException; import org.neo4j.driver.internal.cursor.AsyncResultCursor; import org.neo4j.driver.internal.cursor.AsyncResultCursorImpl; import org.neo4j.driver.internal.cursor.DisposableAsyncResultCursor; import org.neo4j.driver.internal.handlers.LegacyPullAllResponseHandler; import org.neo4j.driver.internal.handlers.PullAllResponseHandler; import org.neo4j.driver.internal.handlers.PullResponseCompletionListener; -import org.neo4j.driver.exceptions.ResultConsumedException; import org.neo4j.driver.internal.handlers.RunResponseHandler; +import org.neo4j.driver.internal.messaging.v3.BoltProtocolV3; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.value.NullValue; import org.neo4j.driver.util.Pair; @@ -58,7 +59,6 @@ import static org.neo4j.driver.Values.ofString; import static org.neo4j.driver.Values.value; import static org.neo4j.driver.internal.BoltServerAddress.LOCAL_DEFAULT; -import static org.neo4j.driver.internal.messaging.v1.BoltProtocolV1.METADATA_EXTRACTOR; import static org.neo4j.driver.util.TestUtil.anyServerVersion; class InternalResultTest @@ -353,7 +353,7 @@ void shouldNotPeekIntoTheFutureWhenResultIsEmpty() private Result createResult(int numberOfRecords ) { - RunResponseHandler runHandler = new RunResponseHandler( new CompletableFuture<>(), METADATA_EXTRACTOR ); + RunResponseHandler runHandler = new RunResponseHandler( new CompletableFuture<>(), BoltProtocolV3.METADATA_EXTRACTOR ); runHandler.onSuccess( singletonMap( "fields", value( Arrays.asList( "k1", "k2" ) ) ) ); Query query = new Query( "" ); @@ -361,7 +361,8 @@ private Result createResult(int numberOfRecords ) when( connection.serverAddress() ).thenReturn( LOCAL_DEFAULT ); when( connection.serverVersion() ).thenReturn( anyServerVersion() ); PullAllResponseHandler pullAllHandler = - new LegacyPullAllResponseHandler(query, runHandler, connection, METADATA_EXTRACTOR, mock( PullResponseCompletionListener.class ) ); + new LegacyPullAllResponseHandler( query, runHandler, connection, BoltProtocolV3.METADATA_EXTRACTOR, + mock( PullResponseCompletionListener.class ) ); for ( int i = 1; i <= numberOfRecords; i++ ) { diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/AsyncResultCursorImplTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/AsyncResultCursorImplTest.java index c14a7d5a36..8bc2548f24 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/AsyncResultCursorImplTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/AsyncResultCursorImplTest.java @@ -27,8 +27,8 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; -import org.neo4j.driver.Record; import org.neo4j.driver.Query; +import org.neo4j.driver.Record; import org.neo4j.driver.exceptions.NoSuchRecordException; import org.neo4j.driver.exceptions.ServiceUnavailableException; import org.neo4j.driver.internal.BoltServerAddress; @@ -36,12 +36,12 @@ import org.neo4j.driver.internal.cursor.AsyncResultCursorImpl; import org.neo4j.driver.internal.handlers.PullAllResponseHandler; import org.neo4j.driver.internal.handlers.RunResponseHandler; -import org.neo4j.driver.internal.messaging.v1.BoltProtocolV1; +import org.neo4j.driver.internal.messaging.v3.BoltProtocolV3; import org.neo4j.driver.internal.summary.InternalResultSummary; import org.neo4j.driver.internal.summary.InternalServerInfo; import org.neo4j.driver.internal.summary.InternalSummaryCounters; -import org.neo4j.driver.summary.ResultSummary; import org.neo4j.driver.summary.QueryType; +import org.neo4j.driver.summary.ResultSummary; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; @@ -409,6 +409,6 @@ private static AsyncResultCursorImpl newCursor(RunResponseHandler runHandler, Pu private static RunResponseHandler newRunResponseHandler() { - return new RunResponseHandler( new CompletableFuture<>(), BoltProtocolV1.METADATA_EXTRACTOR ); + return new RunResponseHandler( new CompletableFuture<>(), BoltProtocolV3.METADATA_EXTRACTOR ); } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/UnmanagedTransactionTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/UnmanagedTransactionTest.java index 5ec591906c..418dd18b82 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/UnmanagedTransactionTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/UnmanagedTransactionTest.java @@ -31,8 +31,6 @@ import org.neo4j.driver.exceptions.ClientException; import org.neo4j.driver.internal.DefaultBookmarkHolder; import org.neo4j.driver.internal.InternalBookmark; -import org.neo4j.driver.internal.messaging.request.PullAllMessage; -import org.neo4j.driver.internal.messaging.request.RunMessage; import org.neo4j.driver.internal.messaging.v4.BoltProtocolV4; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.spi.ResponseHandler; @@ -44,17 +42,18 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.neo4j.driver.internal.handlers.pulln.FetchSizeUtil.UNLIMITED_FETCH_SIZE; import static org.neo4j.driver.util.TestUtil.await; +import static org.neo4j.driver.util.TestUtil.beginMessage; import static org.neo4j.driver.util.TestUtil.connectionMock; -import static org.neo4j.driver.util.TestUtil.runMessageWithQueryMatcher; import static org.neo4j.driver.util.TestUtil.setupSuccessfulRunAndPull; import static org.neo4j.driver.util.TestUtil.setupSuccessfulRunRx; +import static org.neo4j.driver.util.TestUtil.verifyBeginTx; +import static org.neo4j.driver.util.TestUtil.verifyRollbackTx; import static org.neo4j.driver.util.TestUtil.verifyRunAndPull; import static org.neo4j.driver.util.TestUtil.verifyRunRx; @@ -103,8 +102,8 @@ void shouldRollbackOnImplicitFailure() // Then InOrder order = inOrder( connection ); - order.verify( connection ).write( eq( new RunMessage( "BEGIN" ) ), any(), eq( PullAllMessage.PULL_ALL ), any() ); - order.verify( connection ).writeAndFlush( eq( new RunMessage( "ROLLBACK" ) ), any(), eq( PullAllMessage.PULL_ALL ), any() ); + verifyBeginTx( connection ); + verifyRollbackTx( connection ); order.verify( connection ).release(); } @@ -115,7 +114,7 @@ void shouldOnlyQueueMessagesWhenNoBookmarkGiven() beginTx( connection, InternalBookmark.empty() ); - verify( connection ).write( eq( new RunMessage( "BEGIN" ) ), any(), eq( PullAllMessage.PULL_ALL ), any() ); + verifyBeginTx( connection ); verify( connection, never() ).writeAndFlush( any(), any(), any(), any() ); } @@ -127,7 +126,7 @@ void shouldFlushWhenBookmarkGiven() beginTx( connection, bookmark ); - verify( connection ).writeAndFlush( any(), any(), eq( PullAllMessage.PULL_ALL ), any() ); + verifyBeginTx( connection, bookmark ); verify( connection, never() ).write( any(), any(), any(), any() ); } @@ -243,11 +242,11 @@ private static Connection connectionWithBegin( Consumer beginBe Connection connection = connectionMock(); doAnswer( invocation -> - { - ResponseHandler beginHandler = invocation.getArgument( 3 ); - beginBehaviour.accept( beginHandler ); - return null; - } ).when( connection ).writeAndFlush( argThat( runMessageWithQueryMatcher( "BEGIN" ) ), any(), any(), any() ); + { + ResponseHandler beginHandler = invocation.getArgument( 1 ); + beginBehaviour.accept( beginHandler ); + return null; + } ).when( connection ).writeAndFlush( argThat( beginMessage() ), any() ); return connection; } diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/connection/ChannelPipelineBuilderImplTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/connection/ChannelPipelineBuilderImplTest.java index 234998903e..ced7c805aa 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/connection/ChannelPipelineBuilderImplTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/connection/ChannelPipelineBuilderImplTest.java @@ -31,7 +31,7 @@ import org.neo4j.driver.internal.async.inbound.InboundMessageHandler; import org.neo4j.driver.internal.async.inbound.MessageDecoder; import org.neo4j.driver.internal.async.outbound.OutboundMessageHandler; -import org.neo4j.driver.internal.messaging.v1.MessageFormatV1; +import org.neo4j.driver.internal.messaging.v3.MessageFormatV3; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.junit.MatcherAssert.assertThat; @@ -46,7 +46,7 @@ void shouldBuildPipeline() EmbeddedChannel channel = new EmbeddedChannel(); ChannelAttributes.setMessageDispatcher( channel, new InboundMessageDispatcher( channel, DEV_NULL_LOGGING ) ); - new ChannelPipelineBuilderImpl().build( new MessageFormatV1(), channel.pipeline(), DEV_NULL_LOGGING ); + new ChannelPipelineBuilderImpl().build( new MessageFormatV3(), channel.pipeline(), DEV_NULL_LOGGING ); Iterator> iterator = channel.pipeline().iterator(); assertThat( iterator.next().getValue(), instanceOf( ChunkDecoder.class ) ); diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/connection/HandshakeCompletedListenerTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/connection/HandshakeCompletedListenerTest.java index 2f92487920..8a8dff21cd 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/connection/HandshakeCompletedListenerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/connection/HandshakeCompletedListenerTest.java @@ -25,22 +25,14 @@ import java.io.IOException; import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import org.neo4j.driver.AuthToken; import org.neo4j.driver.AuthTokens; -import org.neo4j.driver.Value; import org.neo4j.driver.internal.async.inbound.InboundMessageDispatcher; import org.neo4j.driver.internal.cluster.RoutingContext; import org.neo4j.driver.internal.handlers.HelloResponseHandler; -import org.neo4j.driver.internal.handlers.InitResponseHandler; import org.neo4j.driver.internal.messaging.BoltProtocolVersion; import org.neo4j.driver.internal.messaging.Message; import org.neo4j.driver.internal.messaging.request.HelloMessage; -import org.neo4j.driver.internal.messaging.request.InitMessage; -import org.neo4j.driver.internal.messaging.v1.BoltProtocolV1; -import org.neo4j.driver.internal.messaging.v2.BoltProtocolV2; import org.neo4j.driver.internal.messaging.v3.BoltProtocolV3; import org.neo4j.driver.internal.security.InternalAuthToken; import org.neo4j.driver.internal.spi.ResponseHandler; @@ -51,7 +43,6 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import static org.neo4j.driver.Values.value; import static org.neo4j.driver.internal.async.connection.ChannelAttributes.setMessageDispatcher; import static org.neo4j.driver.internal.async.connection.ChannelAttributes.setProtocolVersion; import static org.neo4j.driver.util.TestUtil.await; @@ -85,18 +76,6 @@ void shouldFailConnectionInitializedPromiseWhenHandshakeFails() assertEquals( cause, error ); } - @Test - void shouldWriteInitializationMessageInBoltV1WhenHandshakeCompleted() - { - testWritingOfInitializationMessage( BoltProtocolV1.VERSION, new InitMessage( USER_AGENT, authToken().toMap() ), InitResponseHandler.class ); - } - - @Test - void shouldWriteInitializationMessageInBoltV2WhenHandshakeCompleted() - { - testWritingOfInitializationMessage( BoltProtocolV2.VERSION, new InitMessage( USER_AGENT, authToken().toMap() ), InitResponseHandler.class ); - } - @Test void shouldWriteInitializationMessageInBoltV3WhenHandshakeCompleted() { diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/inbound/InboundMessageHandlerTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/inbound/InboundMessageHandlerTest.java index e7e574e2b6..8fcebf0dc9 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/inbound/InboundMessageHandlerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/inbound/InboundMessageHandlerTest.java @@ -29,19 +29,19 @@ import java.util.HashMap; import java.util.Map; +import org.neo4j.driver.Value; +import org.neo4j.driver.exceptions.Neo4jException; import org.neo4j.driver.internal.async.connection.ChannelAttributes; -import org.neo4j.driver.internal.util.messaging.KnowledgeableMessageFormat; import org.neo4j.driver.internal.messaging.MessageFormat; import org.neo4j.driver.internal.messaging.MessageFormat.Reader; import org.neo4j.driver.internal.messaging.response.FailureMessage; import org.neo4j.driver.internal.messaging.response.IgnoredMessage; import org.neo4j.driver.internal.messaging.response.RecordMessage; import org.neo4j.driver.internal.messaging.response.SuccessMessage; -import org.neo4j.driver.internal.messaging.v1.MessageFormatV1; +import org.neo4j.driver.internal.messaging.v3.MessageFormatV3; import org.neo4j.driver.internal.spi.ResponseHandler; import org.neo4j.driver.internal.util.io.MessageToByteBufWriter; -import org.neo4j.driver.Value; -import org.neo4j.driver.exceptions.Neo4jException; +import org.neo4j.driver.internal.util.messaging.KnowledgeableMessageFormat; import static org.hamcrest.Matchers.startsWith; import static org.hamcrest.junit.MatcherAssert.assertThat; @@ -52,9 +52,9 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.neo4j.driver.Values.value; import static org.neo4j.driver.internal.logging.DevNullLogging.DEV_NULL_LOGGING; import static org.neo4j.driver.internal.messaging.request.ResetMessage.RESET; -import static org.neo4j.driver.Values.value; class InboundMessageHandlerTest { @@ -70,7 +70,7 @@ void setUp() writer = new MessageToByteBufWriter( new KnowledgeableMessageFormat() ); ChannelAttributes.setMessageDispatcher( channel, messageDispatcher ); - InboundMessageHandler handler = new InboundMessageHandler( new MessageFormatV1(), DEV_NULL_LOGGING ); + InboundMessageHandler handler = new InboundMessageHandler( new MessageFormatV3(), DEV_NULL_LOGGING ); channel.pipeline().addFirst( handler ); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/outbound/OutboundMessageHandlerTest.java b/driver/src/test/java/org/neo4j/driver/internal/async/outbound/OutboundMessageHandlerTest.java index 73e378832a..a39190267c 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/outbound/OutboundMessageHandlerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/outbound/OutboundMessageHandlerTest.java @@ -28,27 +28,26 @@ import java.util.HashMap; import java.util.Map; +import org.neo4j.driver.Query; +import org.neo4j.driver.Value; +import org.neo4j.driver.Values; import org.neo4j.driver.internal.async.connection.ChannelAttributes; import org.neo4j.driver.internal.async.inbound.InboundMessageDispatcher; import org.neo4j.driver.internal.messaging.Message; import org.neo4j.driver.internal.messaging.MessageFormat; -import org.neo4j.driver.internal.messaging.request.RunMessage; -import org.neo4j.driver.internal.messaging.v1.MessageFormatV1; +import org.neo4j.driver.internal.messaging.v3.MessageFormatV3; import org.neo4j.driver.internal.packstream.PackOutput; -import org.neo4j.driver.Value; -import static org.hamcrest.junit.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.neo4j.driver.Values.value; import static org.neo4j.driver.internal.logging.DevNullLogging.DEV_NULL_LOGGING; import static org.neo4j.driver.internal.messaging.MessageFormat.Writer; import static org.neo4j.driver.internal.messaging.request.PullAllMessage.PULL_ALL; -import static org.neo4j.driver.Values.value; import static org.neo4j.driver.util.TestUtil.assertByteBufContains; class OutboundMessageHandlerTest @@ -91,13 +90,13 @@ void shouldOutputByteBufAsWrittenByWriterAndMessageBoundary() @Test void shouldSupportByteArraysByDefault() { - OutboundMessageHandler handler = newHandler( new MessageFormatV1() ); + OutboundMessageHandler handler = newHandler( new MessageFormatV3() ); channel.pipeline().addLast( handler ); Map params = new HashMap<>(); params.put( "array", value( new byte[]{1, 2, 3} ) ); - assertTrue( channel.writeOutbound( new RunMessage( "RETURN 1", params ) ) ); + assertTrue( channel.writeOutbound( new Query( "RETURN 1", Values.value( params ) ) ) ); assertTrue( channel.finish() ); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/handlers/HelloResponseHandlerTest.java b/driver/src/test/java/org/neo4j/driver/internal/handlers/HelloResponseHandlerTest.java index ef4a373943..b3e2a5eb23 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/handlers/HelloResponseHandlerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/handlers/HelloResponseHandlerTest.java @@ -36,8 +36,8 @@ import org.neo4j.driver.internal.async.inbound.ChannelErrorHandler; import org.neo4j.driver.internal.async.inbound.InboundMessageDispatcher; import org.neo4j.driver.internal.async.outbound.OutboundMessageHandler; -import org.neo4j.driver.internal.messaging.v1.MessageFormatV1; import org.neo4j.driver.internal.messaging.v3.BoltProtocolV3; +import org.neo4j.driver.internal.messaging.v3.MessageFormatV3; import org.neo4j.driver.internal.messaging.v4.BoltProtocolV4; import org.neo4j.driver.internal.messaging.v41.BoltProtocolV41; import org.neo4j.driver.internal.util.ServerVersion; @@ -63,7 +63,7 @@ void setUp() { setMessageDispatcher( channel, new InboundMessageDispatcher( channel, DEV_NULL_LOGGING ) ); ChannelPipeline pipeline = channel.pipeline(); - pipeline.addLast( NAME, new OutboundMessageHandler( new MessageFormatV1(), DEV_NULL_LOGGING ) ); + pipeline.addLast( NAME, new OutboundMessageHandler( new MessageFormatV3(), DEV_NULL_LOGGING ) ); pipeline.addLast( new ChannelErrorHandler( DEV_NULL_LOGGING ) ); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/handlers/InitResponseHandlerTest.java b/driver/src/test/java/org/neo4j/driver/internal/handlers/InitResponseHandlerTest.java index 00a350cea2..c3e54e3208 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/handlers/InitResponseHandlerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/handlers/InitResponseHandlerTest.java @@ -29,14 +29,14 @@ import java.util.Map; import java.util.concurrent.TimeUnit; +import org.neo4j.driver.Query; import org.neo4j.driver.Value; import org.neo4j.driver.Values; import org.neo4j.driver.exceptions.UntrustedServerException; import org.neo4j.driver.internal.async.inbound.ChannelErrorHandler; import org.neo4j.driver.internal.async.inbound.InboundMessageDispatcher; import org.neo4j.driver.internal.async.outbound.OutboundMessageHandler; -import org.neo4j.driver.internal.messaging.request.RunMessage; -import org.neo4j.driver.internal.messaging.v1.MessageFormatV1; +import org.neo4j.driver.internal.messaging.v3.MessageFormatV3; import static java.util.Collections.singletonMap; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -58,7 +58,7 @@ void setUp() { setMessageDispatcher( channel, new InboundMessageDispatcher( channel, DEV_NULL_LOGGING ) ); ChannelPipeline pipeline = channel.pipeline(); - pipeline.addLast( NAME, new OutboundMessageHandler( new MessageFormatV1(), DEV_NULL_LOGGING ) ); + pipeline.addLast( NAME, new OutboundMessageHandler( new MessageFormatV3(), DEV_NULL_LOGGING ) ); pipeline.addLast( new ChannelErrorHandler( DEV_NULL_LOGGING ) ); } @@ -100,7 +100,7 @@ void shouldAllowByteArrays() handler.onSuccess( metadata ); Map params = singletonMap( "array", value( new byte[]{1, 2, 3} ) ); - assertTrue( channel.writeOutbound( new RunMessage( "RETURN 1", params ) ) ); + assertTrue( channel.writeOutbound( new Query( "RETURN 1", Values.value( params ) ) ) ); assertTrue( channel.finish() ); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/handlers/LegacyPullAllResponseHandlerTest.java b/driver/src/test/java/org/neo4j/driver/internal/handlers/LegacyPullAllResponseHandlerTest.java index 2c9bd0716f..9c5505b280 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/handlers/LegacyPullAllResponseHandlerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/handlers/LegacyPullAllResponseHandlerTest.java @@ -24,8 +24,9 @@ import java.util.concurrent.CompletableFuture; import java.util.function.Function; -import org.neo4j.driver.Record; import org.neo4j.driver.Query; +import org.neo4j.driver.Record; +import org.neo4j.driver.internal.messaging.v3.BoltProtocolV3; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.summary.ResultSummary; @@ -43,7 +44,6 @@ import static org.mockito.Mockito.verify; import static org.neo4j.driver.Values.value; import static org.neo4j.driver.Values.values; -import static org.neo4j.driver.internal.messaging.v1.BoltProtocolV1.METADATA_EXTRACTOR; import static org.neo4j.driver.util.TestUtil.await; class LegacyPullAllResponseHandlerTest extends PullAllResponseHandlerTestBase @@ -237,8 +237,9 @@ void shouldEnableAutoReadOnConnectionWhenSummaryRequestedButNotAvailable() throw protected LegacyPullAllResponseHandler newHandler(Query query, List queryKeys, Connection connection ) { - RunResponseHandler runResponseHandler = new RunResponseHandler( new CompletableFuture<>(), METADATA_EXTRACTOR ); + RunResponseHandler runResponseHandler = new RunResponseHandler( new CompletableFuture<>(), BoltProtocolV3.METADATA_EXTRACTOR ); runResponseHandler.onSuccess( singletonMap( "fields", value( queryKeys ) ) ); - return new LegacyPullAllResponseHandler(query, runResponseHandler, connection, METADATA_EXTRACTOR, mock( PullResponseCompletionListener.class ) ); + return new LegacyPullAllResponseHandler( query, runResponseHandler, connection, BoltProtocolV3.METADATA_EXTRACTOR, + mock( PullResponseCompletionListener.class ) ); } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/handlers/RunResponseHandlerTest.java b/driver/src/test/java/org/neo4j/driver/internal/handlers/RunResponseHandlerTest.java index d76bfcd780..3c635bd865 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/handlers/RunResponseHandlerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/handlers/RunResponseHandlerTest.java @@ -25,7 +25,6 @@ import java.util.Map; import java.util.concurrent.CompletableFuture; -import org.neo4j.driver.internal.messaging.v1.BoltProtocolV1; import org.neo4j.driver.internal.messaging.v3.BoltProtocolV3; import org.neo4j.driver.internal.util.MetadataExtractor; @@ -115,11 +114,6 @@ void shouldReturnKeysWhenSucceeded() assertEquals( keyIndex, handler.queryKeys().keyIndex() ); } - @Test - void shouldReturnResultAvailableAfterWhenSucceededV1() - { - testResultAvailableAfterOnSuccess( "result_available_after", BoltProtocolV1.METADATA_EXTRACTOR ); - } @Test void shouldReturnResultAvailableAfterWhenSucceededV3() @@ -138,12 +132,12 @@ private static void testResultAvailableAfterOnSuccess( String key, MetadataExtra private static RunResponseHandler newHandler() { - return newHandler( BoltProtocolV1.METADATA_EXTRACTOR ); + return newHandler( BoltProtocolV3.METADATA_EXTRACTOR ); } private static RunResponseHandler newHandler( CompletableFuture runCompletedFuture ) { - return newHandler( runCompletedFuture, BoltProtocolV1.METADATA_EXTRACTOR ); + return newHandler( runCompletedFuture, BoltProtocolV3.METADATA_EXTRACTOR ); } private static RunResponseHandler newHandler( MetadataExtractor metadataExtractor ) diff --git a/driver/src/test/java/org/neo4j/driver/internal/handlers/SessionPullResponseCompletionListenerTest.java b/driver/src/test/java/org/neo4j/driver/internal/handlers/SessionPullResponseCompletionListenerTest.java index d82bf6318d..40b869eef2 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/handlers/SessionPullResponseCompletionListenerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/handlers/SessionPullResponseCompletionListenerTest.java @@ -27,6 +27,7 @@ import org.neo4j.driver.internal.BookmarkHolder; import org.neo4j.driver.internal.InternalBookmark; import org.neo4j.driver.internal.handlers.pulln.BasicPullResponseHandler; +import org.neo4j.driver.internal.messaging.v3.BoltProtocolV3; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.spi.ResponseHandler; @@ -36,7 +37,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.neo4j.driver.Values.value; -import static org.neo4j.driver.internal.messaging.v1.BoltProtocolV1.METADATA_EXTRACTOR; import static org.neo4j.driver.util.TestUtil.anyServerVersion; class SessionPullResponseCompletionListenerTest @@ -81,9 +81,9 @@ void shouldUpdateBookmarksOnSuccess() private static ResponseHandler newHandler( Connection connection, PullResponseCompletionListener listener ) { - RunResponseHandler runHandler = new RunResponseHandler( new CompletableFuture<>(), METADATA_EXTRACTOR ); + RunResponseHandler runHandler = new RunResponseHandler( new CompletableFuture<>(), BoltProtocolV3.METADATA_EXTRACTOR ); BasicPullResponseHandler handler = - new BasicPullResponseHandler( new Query( "RETURN 1" ), runHandler, connection, METADATA_EXTRACTOR, listener ); + new BasicPullResponseHandler( new Query( "RETURN 1" ), runHandler, connection, BoltProtocolV3.METADATA_EXTRACTOR, listener ); handler.installRecordConsumer( ( record, throwable ) -> {} ); handler.installSummaryConsumer( ( resultSummary, throwable ) -> {} ); return handler; diff --git a/driver/src/test/java/org/neo4j/driver/internal/handlers/TransactionPullResponseCompletionListenerTest.java b/driver/src/test/java/org/neo4j/driver/internal/handlers/TransactionPullResponseCompletionListenerTest.java index 910c146e89..018cd03e77 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/handlers/TransactionPullResponseCompletionListenerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/handlers/TransactionPullResponseCompletionListenerTest.java @@ -37,7 +37,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.neo4j.driver.internal.messaging.v1.BoltProtocolV1.METADATA_EXTRACTOR; +import static org.neo4j.driver.internal.messaging.v3.BoltProtocolV3.METADATA_EXTRACTOR; import static org.neo4j.driver.util.TestUtil.anyServerVersion; class TransactionPullResponseCompletionListenerTest diff --git a/driver/src/test/java/org/neo4j/driver/internal/handlers/pulln/AutoPullResponseHandlerTest.java b/driver/src/test/java/org/neo4j/driver/internal/handlers/pulln/AutoPullResponseHandlerTest.java index e44051280c..f81480ab43 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/handlers/pulln/AutoPullResponseHandlerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/handlers/pulln/AutoPullResponseHandlerTest.java @@ -33,6 +33,7 @@ import org.neo4j.driver.internal.handlers.PullResponseCompletionListener; import org.neo4j.driver.internal.handlers.RunResponseHandler; import org.neo4j.driver.internal.messaging.request.PullMessage; +import org.neo4j.driver.internal.messaging.v3.BoltProtocolV3; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.value.BooleanValue; @@ -46,17 +47,17 @@ import static org.neo4j.driver.Values.value; import static org.neo4j.driver.Values.values; import static org.neo4j.driver.internal.handlers.pulln.FetchSizeUtil.DEFAULT_FETCH_SIZE; -import static org.neo4j.driver.internal.messaging.v1.BoltProtocolV1.METADATA_EXTRACTOR; class AutoPullResponseHandlerTest extends PullAllResponseHandlerTestBase { @Override protected AutoPullResponseHandler newHandler( Query query, List queryKeys, Connection connection ) { - RunResponseHandler runResponseHandler = new RunResponseHandler( new CompletableFuture<>(), METADATA_EXTRACTOR ); + RunResponseHandler runResponseHandler = new RunResponseHandler( new CompletableFuture<>(), BoltProtocolV3.METADATA_EXTRACTOR ); runResponseHandler.onSuccess( singletonMap( "fields", value( queryKeys ) ) ); AutoPullResponseHandler handler = - new AutoPullResponseHandler( query, runResponseHandler, connection, METADATA_EXTRACTOR, mock( PullResponseCompletionListener.class ), + new AutoPullResponseHandler( query, runResponseHandler, connection, BoltProtocolV3.METADATA_EXTRACTOR, + mock( PullResponseCompletionListener.class ), DEFAULT_FETCH_SIZE ); handler.prePopulateRecords(); return handler; @@ -64,10 +65,11 @@ protected AutoPullResponseHandler newHandler( Query query, List queryKey protected AutoPullResponseHandler newHandler( Query query, Connection connection, long fetchSize ) { - RunResponseHandler runResponseHandler = new RunResponseHandler( new CompletableFuture<>(), METADATA_EXTRACTOR ); + RunResponseHandler runResponseHandler = new RunResponseHandler( new CompletableFuture<>(), BoltProtocolV3.METADATA_EXTRACTOR ); runResponseHandler.onSuccess( emptyMap() ); AutoPullResponseHandler handler = - new AutoPullResponseHandler( query, runResponseHandler, connection, METADATA_EXTRACTOR, mock( PullResponseCompletionListener.class ), + new AutoPullResponseHandler( query, runResponseHandler, connection, BoltProtocolV3.METADATA_EXTRACTOR, + mock( PullResponseCompletionListener.class ), fetchSize ); handler.prePopulateRecords(); return handler; diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/BoltProtocolTest.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/BoltProtocolTest.java index 45049b754c..1bf8666736 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/BoltProtocolTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/BoltProtocolTest.java @@ -21,10 +21,8 @@ import io.netty.channel.embedded.EmbeddedChannel; import org.junit.jupiter.api.Test; -import org.neo4j.driver.internal.messaging.v1.BoltProtocolV1; -import org.neo4j.driver.internal.messaging.v2.BoltProtocolV2; -import org.neo4j.driver.internal.messaging.v3.BoltProtocolV3; import org.neo4j.driver.exceptions.ClientException; +import org.neo4j.driver.internal.messaging.v3.BoltProtocolV3; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.instanceOf; @@ -38,8 +36,6 @@ class BoltProtocolTest void shouldCreateProtocolForKnownVersions() { assertAll( - () -> assertThat( BoltProtocol.forVersion( BoltProtocolV1.VERSION ), instanceOf( BoltProtocolV1.class ) ), - () -> assertThat( BoltProtocol.forVersion( BoltProtocolV2.VERSION ), instanceOf( BoltProtocolV2.class ) ), () -> assertThat( BoltProtocol.forVersion( BoltProtocolV3.VERSION ), instanceOf( BoltProtocolV3.class ) ) ); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/MessageFormatTest.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/MessageFormatTest.java index d891291d16..15da3b75c6 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/MessageFormatTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/MessageFormatTest.java @@ -29,21 +29,22 @@ import java.util.List; import java.util.Map; +import org.neo4j.driver.Value; +import org.neo4j.driver.exceptions.ClientException; import org.neo4j.driver.internal.async.connection.BoltProtocolUtil; import org.neo4j.driver.internal.async.connection.ChannelPipelineBuilderImpl; import org.neo4j.driver.internal.async.inbound.InboundMessageDispatcher; import org.neo4j.driver.internal.async.outbound.ChunkAwareByteBufOutput; +import org.neo4j.driver.internal.messaging.common.CommonValueUnpacker; import org.neo4j.driver.internal.messaging.request.InitMessage; import org.neo4j.driver.internal.messaging.response.FailureMessage; import org.neo4j.driver.internal.messaging.response.IgnoredMessage; import org.neo4j.driver.internal.messaging.response.RecordMessage; import org.neo4j.driver.internal.messaging.response.SuccessMessage; +import org.neo4j.driver.internal.messaging.v3.MessageFormatV3; +import org.neo4j.driver.internal.packstream.PackStream; import org.neo4j.driver.internal.util.messaging.KnowledgeableMessageFormat; import org.neo4j.driver.internal.util.messaging.MemorizingInboundMessageDispatcher; -import org.neo4j.driver.internal.messaging.v1.MessageFormatV1; -import org.neo4j.driver.internal.packstream.PackStream; -import org.neo4j.driver.Value; -import org.neo4j.driver.exceptions.ClientException; import static java.util.Arrays.asList; import static java.util.Collections.singletonMap; @@ -54,6 +55,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.neo4j.driver.Values.parameters; +import static org.neo4j.driver.Values.value; import static org.neo4j.driver.internal.async.connection.ChannelAttributes.messageDispatcher; import static org.neo4j.driver.internal.async.connection.ChannelAttributes.setMessageDispatcher; import static org.neo4j.driver.internal.logging.DevNullLogging.DEV_NULL_LOGGING; @@ -63,12 +66,10 @@ import static org.neo4j.driver.internal.util.ValueFactory.filledNodeValue; import static org.neo4j.driver.internal.util.ValueFactory.filledPathValue; import static org.neo4j.driver.internal.util.ValueFactory.filledRelationshipValue; -import static org.neo4j.driver.Values.parameters; -import static org.neo4j.driver.Values.value; class MessageFormatTest { - public MessageFormat format = new MessageFormatV1(); + public MessageFormat format = new MessageFormatV3(); @Test void shouldUnpackAllResponses() throws Throwable @@ -100,31 +101,6 @@ void shouldUnpackNodeRelationshipAndPath() throws Throwable } - @Test - void shouldErrorPackingNode() throws Throwable - { - // Given - Value value = filledNodeValue(); - expectIOExceptionWithMessage( value, "Unknown type: NODE" ); - } - - @Test - void shouldErrorPackingRelationship() throws Throwable - { - // Given - Value value = filledRelationshipValue(); - expectIOExceptionWithMessage( value, "Unknown type: RELATIONSHIP" ); - } - - @Test - void shouldErrorPackingPath() throws Throwable - { - // Given - Value value = filledPathValue(); - expectIOExceptionWithMessage( value, "Unknown type: PATH" ); - } - - @Test void shouldGiveHelpfulErrorOnMalformedNodeStruct() throws Throwable { @@ -136,7 +112,7 @@ void shouldGiveHelpfulErrorOnMalformedNodeStruct() throws Throwable packer.packStructHeader( 1, RecordMessage.SIGNATURE ); packer.packListHeader( 1 ); - packer.packStructHeader( 0, MessageFormatV1.NODE ); + packer.packStructHeader( 0, CommonValueUnpacker.NODE ); output.stop(); BoltProtocolUtil.writeMessageBoundary( buf ); diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v1/BoltProtocolV1Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v1/BoltProtocolV1Test.java deleted file mode 100644 index 8685a07e66..0000000000 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/v1/BoltProtocolV1Test.java +++ /dev/null @@ -1,389 +0,0 @@ -/* - * Copyright (c) 2002-2020 "Neo4j," - * Neo4j Sweden AB [http://neo4j.com] - * - * This file is part of Neo4j. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.neo4j.driver.internal.messaging.v1; - -import io.netty.channel.ChannelPromise; -import io.netty.channel.embedded.EmbeddedChannel; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; - -import java.time.Duration; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; - -import org.neo4j.driver.AuthTokens; -import org.neo4j.driver.Bookmark; -import org.neo4j.driver.Logging; -import org.neo4j.driver.Query; -import org.neo4j.driver.TransactionConfig; -import org.neo4j.driver.Value; -import org.neo4j.driver.exceptions.ClientException; -import org.neo4j.driver.internal.BookmarkHolder; -import org.neo4j.driver.internal.InternalBookmark; -import org.neo4j.driver.internal.async.UnmanagedTransaction; -import org.neo4j.driver.internal.async.connection.ChannelAttributes; -import org.neo4j.driver.internal.async.inbound.InboundMessageDispatcher; -import org.neo4j.driver.internal.cluster.RoutingContext; -import org.neo4j.driver.internal.cursor.AsyncResultCursor; -import org.neo4j.driver.internal.handlers.BeginTxResponseHandler; -import org.neo4j.driver.internal.handlers.CommitTxResponseHandler; -import org.neo4j.driver.internal.handlers.NoOpResponseHandler; -import org.neo4j.driver.internal.handlers.PullAllResponseHandler; -import org.neo4j.driver.internal.handlers.RollbackTxResponseHandler; -import org.neo4j.driver.internal.handlers.RunResponseHandler; -import org.neo4j.driver.internal.messaging.BoltProtocol; -import org.neo4j.driver.internal.messaging.MessageFormat; -import org.neo4j.driver.internal.messaging.request.InitMessage; -import org.neo4j.driver.internal.messaging.request.PullAllMessage; -import org.neo4j.driver.internal.messaging.request.RunMessage; -import org.neo4j.driver.internal.security.InternalAuthToken; -import org.neo4j.driver.internal.spi.Connection; -import org.neo4j.driver.internal.spi.ResponseHandler; -import org.neo4j.driver.internal.util.Futures; - -import static java.util.Collections.emptyMap; -import static java.util.Collections.singletonMap; -import static org.hamcrest.Matchers.hasSize; -import static org.hamcrest.Matchers.instanceOf; -import static org.hamcrest.Matchers.startsWith; -import static org.hamcrest.junit.MatcherAssert.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.neo4j.driver.Values.value; -import static org.neo4j.driver.internal.DatabaseNameUtil.defaultDatabase; -import static org.neo4j.driver.internal.handlers.pulln.FetchSizeUtil.UNLIMITED_FETCH_SIZE; -import static org.neo4j.driver.internal.messaging.v1.BoltProtocolV1.SingleBookmarkHelper.asBeginTransactionParameters; -import static org.neo4j.driver.internal.util.Futures.blockingGet; -import static org.neo4j.driver.util.TestUtil.await; -import static org.neo4j.driver.util.TestUtil.connectionMock; - -public class BoltProtocolV1Test -{ - private static final String QUERY_TEXT = "RETURN $x"; - private static final Map PARAMS = singletonMap( "x", value( 42 ) ); - private static final Query QUERY = new Query( QUERY_TEXT, value( PARAMS ) ); - - private final BoltProtocol protocol = createProtocol(); - private final EmbeddedChannel channel = new EmbeddedChannel(); - private final InboundMessageDispatcher messageDispatcher = new InboundMessageDispatcher( channel, Logging.none() ); - - @BeforeEach - void beforeEach() - { - ChannelAttributes.setMessageDispatcher( channel, messageDispatcher ); - } - - @AfterEach - void afterEach() - { - channel.finishAndReleaseAll(); - } - - @Test - void shouldCreateMessageFormat() - { - assertThat( protocol.createMessageFormat(), instanceOf( expectedMessageFormatType() ) ); - } - - @Test - void shouldInitializeChannel() - { - ChannelPromise promise = channel.newPromise(); - - protocol.initializeChannel( "MyDriver/5.3", dummyAuthToken(), RoutingContext.EMPTY, promise ); - - assertThat( channel.outboundMessages(), hasSize( 1 ) ); - assertThat( channel.outboundMessages().poll(), instanceOf( InitMessage.class ) ); - assertEquals( 1, messageDispatcher.queuedHandlersCount() ); - assertFalse( promise.isDone() ); - - messageDispatcher.handleSuccessMessage( singletonMap( "server", value( "Neo4j/3.1.0" ) ) ); - - assertTrue( promise.isDone() ); - assertTrue( promise.isSuccess() ); - } - - @Test - void shouldFailToInitializeChannelWhenErrorIsReceived() - { - ChannelPromise promise = channel.newPromise(); - - protocol.initializeChannel( "MyDriver/3.1", dummyAuthToken(), RoutingContext.EMPTY, promise ); - - assertThat( channel.outboundMessages(), hasSize( 1 ) ); - assertThat( channel.outboundMessages().poll(), instanceOf( InitMessage.class ) ); - assertEquals( 1, messageDispatcher.queuedHandlersCount() ); - assertFalse( promise.isDone() ); - - messageDispatcher.handleFailureMessage( "Neo.TransientError.General.DatabaseUnavailable", "Oh no!" ); - - assertTrue( promise.isDone() ); - assertFalse( promise.isSuccess() ); - } - - @Test - void shouldBeginTransactionWithoutBookmark() - { - Connection connection = connectionMock( protocol ); - - CompletionStage stage = protocol.beginTransaction( connection, InternalBookmark.empty(), TransactionConfig.empty() ); - - verify( connection ).write( - new RunMessage( "BEGIN" ), NoOpResponseHandler.INSTANCE, - PullAllMessage.PULL_ALL, NoOpResponseHandler.INSTANCE ); - - assertNull( blockingGet( stage ) ); - } - - @Test - void shouldBeginTransactionWithBookmarks() - { - Connection connection = connectionMock( protocol ); - Bookmark bookmark = InternalBookmark.parse( "neo4j:bookmark:v1:tx100" ); - - CompletionStage stage = protocol.beginTransaction( connection, bookmark, TransactionConfig.empty() ); - - verify( connection ).writeAndFlush( - eq( new RunMessage( "BEGIN", asBeginTransactionParameters( bookmark ) ) ), eq( NoOpResponseHandler.INSTANCE ), - eq( PullAllMessage.PULL_ALL ), any( BeginTxResponseHandler.class ) ); - - assertNull( Futures.blockingGet( stage ) ); - } - - @Test - void shouldCommitTransaction() - { - String bookmarkString = "neo4j:bookmark:v1:tx1909"; - - Connection connection = mock( Connection.class ); - when( connection.protocol() ).thenReturn( protocol ); - doAnswer( invocation -> - { - ResponseHandler commitHandler = invocation.getArgument( 3 ); - commitHandler.onSuccess( singletonMap( "bookmark", value( bookmarkString ) ) ); - return null; - } ).when( connection ).writeAndFlush( eq( new RunMessage( "COMMIT" ) ), any(), any(), any() ); - - CompletionStage stage = protocol.commitTransaction( connection ); - - verify( connection ).writeAndFlush( - eq( new RunMessage( "COMMIT" ) ), eq( NoOpResponseHandler.INSTANCE ), - eq( PullAllMessage.PULL_ALL ), any( CommitTxResponseHandler.class ) ); - - assertEquals( InternalBookmark.parse( bookmarkString ), await( stage ) ); - } - - @Test - void shouldRollbackTransaction() - { - Connection connection = connectionMock( protocol ); - - CompletionStage stage = protocol.rollbackTransaction( connection ); - - verify( connection ).writeAndFlush( - eq( new RunMessage( "ROLLBACK" ) ), eq( NoOpResponseHandler.INSTANCE ), - eq( PullAllMessage.PULL_ALL ), any( RollbackTxResponseHandler.class ) ); - - assertNull( Futures.blockingGet( stage ) ); - } - - @Test - void shouldRunInAutoCommitTransactionWithoutWaitingForRunResponse() throws Exception - { - testRunWithoutWaitingForRunResponse( true ); - } - - @Test - void shouldRunInAutoCommitTransactionAndWaitForSuccessRunResponse() throws Exception - { - testRunWithWaitingForResponse( true, true ); - } - - @Test - void shouldRunInAutoCommitTransactionAndWaitForFailureRunResponse() throws Exception - { - testRunWithWaitingForResponse( false, true ); - } - - @Test - void shouldRunInTransactionWithoutWaitingForRunResponse() throws Exception - { - testRunWithoutWaitingForRunResponse( false ); - } - - @Test - void shouldRunInTransactionAndWaitForSuccessRunResponse() throws Exception - { - testRunWithWaitingForResponse( true, false ); - } - - @Test - void shouldRunInTransactionAndWaitForFailureRunResponse() throws Exception - { - testRunWithWaitingForResponse( false, false ); - } - - @Test - void shouldNotSupportTransactionConfigInBeginTransaction() - { - TransactionConfig config = TransactionConfig.builder() - .withTimeout( Duration.ofSeconds( 5 ) ) - .withMetadata( singletonMap( "key", "value" ) ) - .build(); - - CompletionStage txStage = protocol.beginTransaction( connectionMock( protocol ), InternalBookmark.empty(), config ); - - ClientException e = assertThrows( ClientException.class, () -> await( txStage ) ); - assertThat( e.getMessage(), startsWith( "Driver is connected to the database that does not support transaction configuration" ) ); - } - - @Test - void shouldNotSupportTransactionConfigForAutoCommitTransactions() - { - TransactionConfig config = TransactionConfig.builder() - .withTimeout( Duration.ofSeconds( 42 ) ) - .withMetadata( singletonMap( "hello", "world" ) ) - .build(); - - ClientException e = assertThrows( ClientException.class, - () -> protocol.runInAutoCommitTransaction( connectionMock( protocol ), new Query( "RETURN 1" ), BookmarkHolder.NO_OP, config, true, - UNLIMITED_FETCH_SIZE ) ); - assertThat( e.getMessage(), startsWith( "Driver is connected to the database that does not support transaction configuration" ) ); - } - - @Test - void shouldNotSupportDatabaseNameInBeginTransaction() - { - CompletionStage txStage = protocol.beginTransaction( connectionMock( "foo", protocol ), InternalBookmark.empty(), TransactionConfig.empty() ); - - ClientException e = assertThrows( ClientException.class, () -> await( txStage ) ); - assertThat( e.getMessage(), startsWith( "Database name parameter for selecting database is not supported" ) ); - } - - @Test - void shouldNotSupportDatabaseNameForAutoCommitTransactions() - { - ClientException e = assertThrows( ClientException.class, - () -> protocol.runInAutoCommitTransaction( connectionMock( "foo", protocol ), - new Query( "RETURN 1" ), BookmarkHolder.NO_OP, TransactionConfig.empty(), true, UNLIMITED_FETCH_SIZE ) ); - assertThat( e.getMessage(), startsWith( "Database name parameter for selecting database is not supported" ) ); - } - - protected BoltProtocol createProtocol() - { - return BoltProtocolV1.INSTANCE; - } - - protected Class expectedMessageFormatType() - { - return MessageFormatV1.class; - } - - private void testRunWithoutWaitingForRunResponse( boolean autoCommitTx ) throws Exception - { - Connection connection = mock( Connection.class ); - when( connection.databaseName() ).thenReturn( defaultDatabase() ); - - CompletionStage cursorStage; - if ( autoCommitTx ) - { - cursorStage = protocol - .runInAutoCommitTransaction( connection, QUERY, BookmarkHolder.NO_OP, TransactionConfig.empty(), false, UNLIMITED_FETCH_SIZE ) - .asyncResult(); - } - else - { - cursorStage = protocol - .runInUnmanagedTransaction( connection, QUERY, mock( UnmanagedTransaction.class ), false, UNLIMITED_FETCH_SIZE ) - .asyncResult(); - } - CompletableFuture cursorFuture = cursorStage.toCompletableFuture(); - - assertTrue( cursorFuture.isDone() ); - assertNotNull( cursorFuture.get() ); - verifyRunInvoked( connection ); - } - - private void testRunWithWaitingForResponse( boolean success, boolean session ) throws Exception - { - Connection connection = mock( Connection.class ); - when( connection.databaseName() ).thenReturn( defaultDatabase() ); - - CompletionStage cursorStage; - if ( session ) - { - cursorStage = protocol.runInAutoCommitTransaction( connection, QUERY, BookmarkHolder.NO_OP, TransactionConfig.empty(), true, UNLIMITED_FETCH_SIZE ) - .asyncResult(); - } - else - { - cursorStage = protocol.runInUnmanagedTransaction( connection, QUERY, mock( UnmanagedTransaction.class ), true, UNLIMITED_FETCH_SIZE ) - .asyncResult(); - } - CompletableFuture cursorFuture = cursorStage.toCompletableFuture(); - - assertFalse( cursorFuture.isDone() ); - ResponseHandler runResponseHandler = verifyRunInvoked( connection ); - - if ( success ) - { - runResponseHandler.onSuccess( emptyMap() ); - } - else - { - runResponseHandler.onFailure( new RuntimeException() ); - } - - assertTrue( cursorFuture.isDone() ); - assertNotNull( cursorFuture.get() ); - } - - private static ResponseHandler verifyRunInvoked( Connection connection ) - { - ArgumentCaptor runHandlerCaptor = ArgumentCaptor.forClass( ResponseHandler.class ); - ArgumentCaptor pullAllHandlerCaptor = ArgumentCaptor.forClass( ResponseHandler.class ); - - verify( connection ).write( eq( new RunMessage( QUERY_TEXT, PARAMS ) ), runHandlerCaptor.capture() ); - verify( connection ).writeAndFlush( eq( PullAllMessage.PULL_ALL ), pullAllHandlerCaptor.capture() ); - - assertThat( runHandlerCaptor.getValue(), instanceOf( RunResponseHandler.class ) ); - assertThat( pullAllHandlerCaptor.getValue(), instanceOf( PullAllResponseHandler.class ) ); - - return runHandlerCaptor.getValue(); - } - - private static InternalAuthToken dummyAuthToken() - { - return (InternalAuthToken) AuthTokens.basic( "hello", "world" ); - } -} diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v1/MessageReaderV1Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v1/MessageReaderV1Test.java deleted file mode 100644 index 7e7c3632d7..0000000000 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/v1/MessageReaderV1Test.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) 2002-2020 "Neo4j," - * Neo4j Sweden AB [http://neo4j.com] - * - * This file is part of Neo4j. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.neo4j.driver.internal.messaging.v1; - -import org.junit.jupiter.api.Test; - -import java.io.IOException; -import java.time.LocalDateTime; - -import org.neo4j.driver.internal.util.messaging.AbstractMessageReaderTestBase; -import org.neo4j.driver.internal.messaging.response.RecordMessage; -import org.neo4j.driver.internal.packstream.PackInput; -import org.neo4j.driver.Value; - -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.neo4j.driver.internal.messaging.MessageFormat.Reader; -import static org.neo4j.driver.Values.point; -import static org.neo4j.driver.Values.value; - -class MessageReaderV1Test extends AbstractMessageReaderTestBase -{ - @Test - void shouldFailToReadMessageWithTemporalValue() - { - Value[] fields = {value( LocalDateTime.now() )}; - - assertThrows( IOException.class, () -> testMessageReading( new RecordMessage( fields ) ) ); - } - - @Test - void shouldFailToReadMessageWithSpatialValue() - { - Value[] fields = {point( 42, 1, 2 )}; - - assertThrows( IOException.class, () -> testMessageReading( new RecordMessage( fields ) ) ); - } - - @Override - protected Reader newReader( PackInput input ) - { - return new MessageReaderV1( input ); - } -} diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v1/MessageWriterV1Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v1/MessageWriterV1Test.java deleted file mode 100644 index 17a9a3c13c..0000000000 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/v1/MessageWriterV1Test.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (c) 2002-2020 "Neo4j," - * Neo4j Sweden AB [http://neo4j.com] - * - * This file is part of Neo4j. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.neo4j.driver.internal.messaging.v1; - -import java.time.LocalDateTime; -import java.util.Collections; -import java.util.stream.Stream; - -import org.neo4j.driver.internal.util.messaging.AbstractMessageWriterTestBase; -import org.neo4j.driver.internal.messaging.Message; -import org.neo4j.driver.internal.messaging.MessageFormat.Writer; -import org.neo4j.driver.internal.messaging.request.HelloMessage; -import org.neo4j.driver.internal.messaging.request.InitMessage; -import org.neo4j.driver.internal.messaging.request.RunMessage; -import org.neo4j.driver.internal.packstream.PackOutput; - -import static java.util.Collections.emptyMap; -import static java.util.Collections.singletonMap; -import static org.neo4j.driver.internal.messaging.request.CommitMessage.COMMIT; -import static org.neo4j.driver.internal.messaging.request.DiscardAllMessage.DISCARD_ALL; -import static org.neo4j.driver.internal.messaging.request.GoodbyeMessage.GOODBYE; -import static org.neo4j.driver.internal.messaging.request.PullAllMessage.PULL_ALL; -import static org.neo4j.driver.internal.messaging.request.ResetMessage.RESET; -import static org.neo4j.driver.internal.messaging.request.RollbackMessage.ROLLBACK; -import static org.neo4j.driver.Values.point; -import static org.neo4j.driver.Values.value; - -class MessageWriterV1Test extends AbstractMessageWriterTestBase -{ - @Override - protected Writer newWriter( PackOutput output ) - { - return new MessageWriterV1( output ); - } - - @Override - protected Stream supportedMessages() - { - return Stream.of( - new InitMessage( "MyDriver/1.2.3", singletonMap( "password", value( "hello" ) ) ), - new RunMessage( "RETURN 1", singletonMap( "key", value( 42 ) ) ), - PULL_ALL, - DISCARD_ALL, - RESET - ); - } - - @Override - protected Stream unsupportedMessages() - { - return Stream.of( - // Bolt V1 messages with Bolt V2 structs - new RunMessage( "RETURN $now", singletonMap( "now", value( LocalDateTime.now() ) ) ), - new RunMessage( "RETURN $here", singletonMap( "now", point( 42, 1, 1 ) ) ), - - // Bolt V3 messages - new HelloMessage( "Driver/2.3.4", emptyMap(), emptyMap() ), - GOODBYE, - COMMIT, - ROLLBACK - ); - } -} diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v2/BoltProtocolV2Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v2/BoltProtocolV2Test.java deleted file mode 100644 index 7cb68fcadf..0000000000 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/v2/BoltProtocolV2Test.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2002-2020 "Neo4j," - * Neo4j Sweden AB [http://neo4j.com] - * - * This file is part of Neo4j. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.neo4j.driver.internal.messaging.v2; - -import org.neo4j.driver.internal.messaging.BoltProtocol; -import org.neo4j.driver.internal.messaging.MessageFormat; -import org.neo4j.driver.internal.messaging.v1.BoltProtocolV1Test; - -class BoltProtocolV2Test extends BoltProtocolV1Test -{ - @Override - protected BoltProtocol createProtocol() - { - return BoltProtocolV2.INSTANCE; - } - - @Override - protected Class expectedMessageFormatType() - { - return MessageFormatV2.class; - } -} diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v2/MessageFormatV2Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v2/MessageFormatV2Test.java deleted file mode 100644 index 56049fe4f3..0000000000 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/v2/MessageFormatV2Test.java +++ /dev/null @@ -1,422 +0,0 @@ -/* - * Copyright (c) 2002-2020 "Neo4j," - * Neo4j Sweden AB [http://neo4j.com] - * - * This file is part of Neo4j. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.neo4j.driver.internal.messaging.v2; - -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; -import org.junit.jupiter.api.Test; - -import java.io.IOException; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.time.OffsetTime; -import java.time.ZoneId; -import java.time.ZoneOffset; -import java.time.ZonedDateTime; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import org.neo4j.driver.Value; -import org.neo4j.driver.Values; -import org.neo4j.driver.internal.InternalPoint2D; -import org.neo4j.driver.internal.InternalPoint3D; -import org.neo4j.driver.internal.async.inbound.ByteBufInput; -import org.neo4j.driver.internal.messaging.MessageFormat; -import org.neo4j.driver.internal.messaging.ResponseMessageHandler; -import org.neo4j.driver.internal.messaging.request.RunMessage; -import org.neo4j.driver.internal.messaging.response.RecordMessage; -import org.neo4j.driver.internal.util.ThrowingConsumer; -import org.neo4j.driver.internal.util.io.ByteBufOutput; -import org.neo4j.driver.types.IsoDuration; -import org.neo4j.driver.types.Point; - -import static java.time.Month.APRIL; -import static java.time.Month.AUGUST; -import static java.time.Month.DECEMBER; -import static java.time.ZoneOffset.UTC; -import static java.util.Arrays.asList; -import static java.util.Collections.singletonMap; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.mock; -import static org.neo4j.driver.Values.point; -import static org.neo4j.driver.Values.value; -import static org.neo4j.driver.internal.packstream.PackStream.FLOAT_64; -import static org.neo4j.driver.internal.packstream.PackStream.INT_16; -import static org.neo4j.driver.internal.packstream.PackStream.INT_32; -import static org.neo4j.driver.internal.packstream.PackStream.INT_64; -import static org.neo4j.driver.internal.packstream.PackStream.Packer; -import static org.neo4j.driver.internal.packstream.PackStream.STRING_8; -import static org.neo4j.driver.util.TestUtil.assertByteBufContains; - -class MessageFormatV2Test -{ - private final MessageFormatV2 messageFormat = new MessageFormatV2(); - - @Test - void shouldWritePoint2D() throws Exception - { - ByteBuf buf = Unpooled.buffer(); - MessageFormat.Writer writer = newWriter( buf ); - - writer.write( new RunMessage( "RETURN $point", singletonMap( "point", point( 42, 12.99, -180.0 ) ) ) ); - - // point should be encoded as (byte SRID, FLOAT_64 header byte + double X, FLOAT_64 header byte + double Y) - int index = buf.readableBytes() - Double.BYTES - Byte.BYTES - Double.BYTES - Byte.BYTES - Byte.BYTES; - ByteBuf tailSlice = buf.slice( index, buf.readableBytes() - index ); - - assertByteBufContains( tailSlice, (byte) 42, FLOAT_64, 12.99, FLOAT_64, -180.0 ); - } - - @Test - void shouldWritePoint3D() throws Exception - { - ByteBuf buf = Unpooled.buffer(); - MessageFormat.Writer writer = newWriter( buf ); - - writer.write( new RunMessage( "RETURN $point", singletonMap( "point", point( 42, 0.51, 2.99, 100.123 ) ) ) ); - - // point should be encoded as (byte SRID, FLOAT_64 header byte + double X, FLOAT_64 header byte + double Y, FLOAT_64 header byte + double Z) - int index = buf.readableBytes() - Double.BYTES - Byte.BYTES - Double.BYTES - Byte.BYTES - Double.BYTES - Byte.BYTES - Byte.BYTES; - ByteBuf tailSlice = buf.slice( index, buf.readableBytes() - index ); - - assertByteBufContains( tailSlice, (byte) 42, FLOAT_64, 0.51, FLOAT_64, 2.99, FLOAT_64, 100.123 ); - } - - @Test - void shouldReadPoint2D() throws Exception - { - Point point = new InternalPoint2D( 42, 120.65, -99.2 ); - - Object unpacked = packAndUnpackValue( packer -> - { - packer.packStructHeader( 3, (byte) 'X' ); - packer.pack( point.srid() ); - packer.pack( point.x() ); - packer.pack( point.y() ); - } ); - - assertEquals( point, unpacked ); - } - - @Test - void shouldReadPoint3D() throws Exception - { - Point point = new InternalPoint3D( 42, 85.391, 98.8, 11.1 ); - - Object unpacked = packAndUnpackValue( packer -> - { - packer.packStructHeader( 4, (byte) 'Y' ); - packer.pack( point.srid() ); - packer.pack( point.x() ); - packer.pack( point.y() ); - packer.pack( point.z() ); - } ); - - assertEquals( point, unpacked ); - } - - @Test - void shouldWriteDate() throws Exception - { - LocalDate date = LocalDate.ofEpochDay( 2147483650L ); - ByteBuf buf = Unpooled.buffer(); - MessageFormat.Writer writer = newWriter( buf ); - - writer.write( new RunMessage( "RETURN $date", singletonMap( "date", value( date ) ) ) ); - - int index = buf.readableBytes() - Long.BYTES - Byte.BYTES; - ByteBuf tailSlice = buf.slice( index, buf.readableBytes() - index ); - - assertByteBufContains( tailSlice, INT_64, date.toEpochDay() ); - } - - @Test - void shouldReadDate() throws Exception - { - LocalDate date = LocalDate.of( 2012, AUGUST, 3 ); - - Object unpacked = packAndUnpackValue( packer -> - { - packer.packStructHeader( 1, (byte) 'D' ); - packer.pack( date.toEpochDay() ); - } ); - - assertEquals( date, unpacked ); - } - - @Test - void shouldWriteTime() throws Exception - { - OffsetTime time = OffsetTime.of( 4, 16, 20, 999, ZoneOffset.MIN ); - ByteBuf buf = Unpooled.buffer(); - MessageFormat.Writer writer = newWriter( buf ); - - writer.write( new RunMessage( "RETURN $time", singletonMap( "time", value( time ) ) ) ); - - int index = buf.readableBytes() - Long.BYTES - Byte.BYTES - Integer.BYTES - Byte.BYTES; - ByteBuf tailSlice = buf.slice( index, buf.readableBytes() - index ); - - assertByteBufContains( tailSlice, INT_64, time.toLocalTime().toNanoOfDay(), INT_32, time.getOffset().getTotalSeconds() ); - } - - @Test - void shouldReadTime() throws Exception - { - OffsetTime time = OffsetTime.of( 23, 59, 59, 999, ZoneOffset.MAX ); - - Object unpacked = packAndUnpackValue( packer -> - { - packer.packStructHeader( 2, (byte) 'T' ); - packer.pack( time.toLocalTime().toNanoOfDay() ); - packer.pack( time.getOffset().getTotalSeconds() ); - } ); - - assertEquals( time, unpacked ); - } - - @Test - void shouldWriteLocalTime() throws Exception - { - LocalTime time = LocalTime.of( 12, 9, 18, 999_888 ); - ByteBuf buf = Unpooled.buffer(); - MessageFormat.Writer writer = newWriter( buf ); - - writer.write( new RunMessage( "RETURN $time", singletonMap( "time", value( time ) ) ) ); - - int index = buf.readableBytes() - Long.BYTES - Byte.BYTES; - ByteBuf tailSlice = buf.slice( index, buf.readableBytes() - index ); - - assertByteBufContains( tailSlice, INT_64, time.toNanoOfDay() ); - } - - @Test - void shouldReadLocalTime() throws Exception - { - LocalTime time = LocalTime.of( 12, 25 ); - - Object unpacked = packAndUnpackValue( packer -> - { - packer.packStructHeader( 1, (byte) 't' ); - packer.pack( time.toNanoOfDay() ); - } ); - - assertEquals( time, unpacked ); - } - - @Test - void shouldWriteLocalDateTime() throws Exception - { - LocalDateTime dateTime = LocalDateTime.of( 2049, DECEMBER, 12, 17, 25, 49, 199 ); - ByteBuf buf = Unpooled.buffer(); - MessageFormat.Writer writer = newWriter( buf ); - - writer.write( new RunMessage( "RETURN $dateTime", singletonMap( "dateTime", value( dateTime ) ) ) ); - - int index = buf.readableBytes() - Long.BYTES - Byte.BYTES - Short.BYTES - Byte.BYTES; - ByteBuf tailSlice = buf.slice( index, buf.readableBytes() - index ); - - assertByteBufContains( tailSlice, INT_64, dateTime.toEpochSecond( UTC ), INT_16, (short) dateTime.getNano() ); - } - - @Test - void shouldReadLocalDateTime() throws Exception - { - LocalDateTime dateTime = LocalDateTime.of( 1999, APRIL, 3, 19, 5, 5, 100_200_300 ); - - Object unpacked = packAndUnpackValue( packer -> - { - packer.packStructHeader( 2, (byte) 'd' ); - packer.pack( dateTime.toEpochSecond( UTC ) ); - packer.pack( dateTime.getNano() ); - } ); - - assertEquals( dateTime, unpacked ); - } - - @Test - void shouldWriteZonedDateTimeWithOffset() throws Exception - { - ZoneOffset zoneOffset = ZoneOffset.ofHoursMinutes( 9, 30 ); - ZonedDateTime dateTime = ZonedDateTime.of( 2000, 1, 10, 12, 2, 49, 300, zoneOffset ); - ByteBuf buf = Unpooled.buffer(); - MessageFormat.Writer writer = newWriter( buf ); - - writer.write( new RunMessage( "RETURN $dateTime", singletonMap( "dateTime", value( dateTime ) ) ) ); - - int index = buf.readableBytes() - Integer.BYTES - Byte.BYTES - Short.BYTES - Byte.BYTES - Integer.BYTES - Byte.BYTES; - ByteBuf tailSlice = buf.slice( index, buf.readableBytes() - index ); - - assertByteBufContains( tailSlice, - INT_32, (int) localEpochSecondOf( dateTime ), - INT_16, (short) dateTime.getNano(), - INT_32, zoneOffset.getTotalSeconds() ); - } - - @Test - void shouldReadZonedDateTimeWithOffset() throws Exception - { - ZoneOffset zoneOffset = ZoneOffset.ofHoursMinutes( -7, -15 ); - ZonedDateTime dateTime = ZonedDateTime.of( 1823, 1, 12, 23, 59, 59, 999_999_999, zoneOffset ); - - Object unpacked = packAndUnpackValue( packer -> - { - packer.packStructHeader( 3, (byte) 'F' ); - packer.pack( localEpochSecondOf( dateTime ) ); - packer.pack( dateTime.toInstant().getNano() ); - packer.pack( zoneOffset.getTotalSeconds() ); - } ); - - assertEquals( dateTime, unpacked ); - } - - @Test - void shouldWriteZonedDateTimeWithZoneId() throws Exception - { - String zoneName = "Europe/Stockholm"; - byte[] zoneNameBytes = zoneName.getBytes(); - ZonedDateTime dateTime = ZonedDateTime.of( 2000, 1, 10, 12, 2, 49, 300, ZoneId.of( zoneName ) ); - - ByteBuf buf = Unpooled.buffer(); - MessageFormat.Writer writer = newWriter( buf ); - - writer.write( new RunMessage( "RETURN $dateTime", singletonMap( "dateTime", value( dateTime ) ) ) ); - - int index = buf.readableBytes() - zoneNameBytes.length - Byte.BYTES - Byte.BYTES - Short.BYTES - Byte.BYTES - Integer.BYTES - Byte.BYTES; - ByteBuf tailSlice = buf.slice( index, buf.readableBytes() - index ); - - List expectedBuf = new ArrayList<>( asList( - INT_32, (int) localEpochSecondOf( dateTime ), - INT_16, (short) dateTime.toInstant().getNano(), - STRING_8, (byte) zoneNameBytes.length ) ); - - for ( byte b : zoneNameBytes ) - { - expectedBuf.add( b ); - } - - assertByteBufContains( tailSlice, expectedBuf.toArray( new Number[0] ) ); - } - - @Test - void shouldReadZonedDateTimeWithZoneId() throws Exception - { - String zoneName = "Europe/Stockholm"; - ZonedDateTime dateTime = ZonedDateTime.of( 1823, 1, 12, 23, 59, 59, 999_999_999, ZoneId.of( zoneName ) ); - - Object unpacked = packAndUnpackValue( packer -> - { - packer.packStructHeader( 3, (byte) 'f' ); - packer.pack( localEpochSecondOf( dateTime ) ); - packer.pack( dateTime.toInstant().getNano() ); - packer.pack( zoneName ); - } ); - - assertEquals( dateTime, unpacked ); - } - - @Test - void shouldWriteDuration() throws Exception - { - Value durationValue = Values.isoDuration( Long.MAX_VALUE - 1, Integer.MAX_VALUE - 1, Short.MAX_VALUE - 1, Byte.MAX_VALUE - 1 ); - IsoDuration duration = durationValue.asIsoDuration(); - - ByteBuf buf = Unpooled.buffer(); - MessageFormat.Writer writer = newWriter( buf ); - - writer.write( new RunMessage( "RETURN $duration", singletonMap( "duration", durationValue ) ) ); - - int index = buf.readableBytes() - Long.BYTES - Byte.BYTES - Integer.BYTES - Byte.BYTES - Short.BYTES - Byte.BYTES - Byte.BYTES; - ByteBuf tailSlice = buf.slice( index, buf.readableBytes() - index ); - - assertByteBufContains( tailSlice, - INT_64, duration.months(), INT_32, (int) duration.days(), INT_16, (short) duration.seconds(), (byte) duration.nanoseconds() ); - } - - @Test - void shouldReadDuration() throws Exception - { - Value durationValue = Values.isoDuration( 17, 22, 99, 15 ); - IsoDuration duration = durationValue.asIsoDuration(); - - Object unpacked = packAndUnpackValue( packer -> - { - packer.packStructHeader( 4, (byte) 'E' ); - packer.pack( duration.months() ); - packer.pack( duration.days() ); - packer.pack( duration.seconds() ); - packer.pack( duration.nanoseconds() ); - } ); - - assertEquals( duration, unpacked ); - } - - private Object packAndUnpackValue( ThrowingConsumer packAction ) throws Exception - { - ByteBuf buf = Unpooled.buffer(); - try - { - Packer packer = new Packer( new ByteBufOutput( buf ) ); - packer.packStructHeader( 1, RecordMessage.SIGNATURE ); - packer.packListHeader( 1 ); - packAction.accept( packer ); - - ByteBufInput input = new ByteBufInput(); - input.start( buf ); - MessageFormat.Reader reader = messageFormat.newReader( input ); - - List values = new ArrayList<>(); - ResponseMessageHandler responseHandler = recordMemorizingHandler( values ); - reader.read( responseHandler ); - input.stop(); - - assertEquals( 1, values.size() ); - return values.get( 0 ).asObject(); - } - finally - { - buf.release(); - } - } - - private static ResponseMessageHandler recordMemorizingHandler( List values ) throws IOException - { - ResponseMessageHandler responseHandler = mock( ResponseMessageHandler.class ); - doAnswer( invocation -> - { - Value[] arg = invocation.getArgument( 0 ); - Collections.addAll( values, arg ); - return null; - } ).when( responseHandler ).handleRecordMessage( any() ); - return responseHandler; - } - - private MessageFormat.Writer newWriter( ByteBuf buf ) - { - return messageFormat.newWriter( new ByteBufOutput( buf ) ); - } - - private static long localEpochSecondOf( ZonedDateTime dateTime ) - { - return dateTime.toLocalDateTime().toEpochSecond( UTC ); - } -} diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v2/MessageReaderV2Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v2/MessageReaderV2Test.java deleted file mode 100644 index 94ebc57720..0000000000 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/v2/MessageReaderV2Test.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2002-2020 "Neo4j," - * Neo4j Sweden AB [http://neo4j.com] - * - * This file is part of Neo4j. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.neo4j.driver.internal.messaging.v2; - -import org.junit.jupiter.api.Test; - -import java.time.LocalDateTime; -import java.time.OffsetTime; -import java.time.ZonedDateTime; - -import org.neo4j.driver.internal.util.messaging.AbstractMessageReaderTestBase; -import org.neo4j.driver.internal.messaging.MessageFormat; -import org.neo4j.driver.internal.messaging.ResponseMessageHandler; -import org.neo4j.driver.internal.messaging.response.RecordMessage; -import org.neo4j.driver.internal.packstream.PackInput; -import org.neo4j.driver.Value; - -import static org.mockito.Mockito.verify; -import static org.neo4j.driver.Values.point; -import static org.neo4j.driver.Values.value; - -class MessageReaderV2Test extends AbstractMessageReaderTestBase -{ - @Test - void shouldReadMessageWithTemporalValues() throws Exception - { - Value[] fields = {value( LocalDateTime.now() ), value( OffsetTime.now() ), value( ZonedDateTime.now() )}; - - ResponseMessageHandler handler = testMessageReading( new RecordMessage( fields ) ); - - verify( handler ).handleRecordMessage( fields ); - } - - @Test - void shouldReadMessageWithSpatialValues() throws Exception - { - Value[] fields = {point( 42, 1.1, 2.2 ), point( 4242, 3.3, 4.4 ), point( 24, 5.5, 6.6, 7.7 ), point( 2424, 8.8, 9.9, 10.1 )}; - - ResponseMessageHandler handler = testMessageReading( new RecordMessage( fields ) ); - - verify( handler ).handleRecordMessage( fields ); - } - - @Override - protected MessageFormat.Reader newReader( PackInput input ) - { - return new MessageReaderV2( input ); - } -} diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v2/MessageWriterV2Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v2/MessageWriterV2Test.java deleted file mode 100644 index 77d1995e56..0000000000 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/v2/MessageWriterV2Test.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) 2002-2020 "Neo4j," - * Neo4j Sweden AB [http://neo4j.com] - * - * This file is part of Neo4j. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.neo4j.driver.internal.messaging.v2; - -import java.time.LocalDateTime; -import java.util.Collections; -import java.util.stream.Stream; - -import org.neo4j.driver.internal.util.messaging.AbstractMessageWriterTestBase; -import org.neo4j.driver.internal.messaging.Message; -import org.neo4j.driver.internal.messaging.MessageFormat.Writer; -import org.neo4j.driver.internal.messaging.request.HelloMessage; -import org.neo4j.driver.internal.messaging.request.InitMessage; -import org.neo4j.driver.internal.messaging.request.RunMessage; -import org.neo4j.driver.internal.packstream.PackOutput; - -import static java.util.Collections.emptyMap; -import static java.util.Collections.singletonMap; -import static org.neo4j.driver.internal.messaging.request.CommitMessage.COMMIT; -import static org.neo4j.driver.internal.messaging.request.DiscardAllMessage.DISCARD_ALL; -import static org.neo4j.driver.internal.messaging.request.GoodbyeMessage.GOODBYE; -import static org.neo4j.driver.internal.messaging.request.PullAllMessage.PULL_ALL; -import static org.neo4j.driver.internal.messaging.request.ResetMessage.RESET; -import static org.neo4j.driver.internal.messaging.request.RollbackMessage.ROLLBACK; -import static org.neo4j.driver.Values.point; -import static org.neo4j.driver.Values.value; - -class MessageWriterV2Test extends AbstractMessageWriterTestBase -{ - @Override - protected Writer newWriter( PackOutput output ) - { - return new MessageWriterV2( output ); - } - - @Override - protected Stream supportedMessages() - { - return Stream.of( - new InitMessage( "MyDriver/1.2.3", singletonMap( "password", value( "hello" ) ) ), - new RunMessage( "RETURN 1", singletonMap( "key", value( 42 ) ) ), - new RunMessage( "RETURN $now", singletonMap( "now", value( LocalDateTime.now() ) ) ), // RUN with temporal value - new RunMessage( "RETURN $here", singletonMap( "now", point( 42, 1, 1 ) ) ), // RUN with spatial value - PULL_ALL, - DISCARD_ALL, - RESET - ); - } - - @Override - protected Stream unsupportedMessages() - { - return Stream.of( - new HelloMessage( "JavaDriver/1.1.0", emptyMap(), emptyMap() ), - GOODBYE, - COMMIT, - ROLLBACK - ); - } -} diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v3/BoltProtocolV3Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v3/BoltProtocolV3Test.java index 96dda7eecc..7ed7341139 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/v3/BoltProtocolV3Test.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v3/BoltProtocolV3Test.java @@ -33,7 +33,6 @@ import java.util.concurrent.CompletionStage; import org.neo4j.driver.AccessMode; -import org.neo4j.driver.AuthToken; import org.neo4j.driver.AuthTokens; import org.neo4j.driver.Bookmark; import org.neo4j.driver.Logging; @@ -340,14 +339,33 @@ void databaseNameForAutoCommitTransactions() testDatabaseNameSupport( true ); } + @Test + void shouldNotSupportDatabaseNameInBeginTransaction() + { + CompletionStage txStage = protocol.beginTransaction( connectionMock( "foo", protocol ), InternalBookmark.empty(), TransactionConfig.empty() ); + + ClientException e = assertThrows( ClientException.class, () -> await( txStage ) ); + assertThat( e.getMessage(), startsWith( "Database name parameter for selecting database is not supported" ) ); + } + + @Test + void shouldNotSupportDatabaseNameForAutoCommitTransactions() + { + ClientException e = assertThrows( ClientException.class, + () -> protocol.runInAutoCommitTransaction( connectionMock( "foo", protocol ), + new Query( "RETURN 1" ), BookmarkHolder.NO_OP, TransactionConfig.empty(), + true, UNLIMITED_FETCH_SIZE ) ); + assertThat( e.getMessage(), startsWith( "Database name parameter for selecting database is not supported" ) ); + } + protected void testDatabaseNameSupport( boolean autoCommitTx ) { ClientException e; if ( autoCommitTx ) { e = assertThrows( ClientException.class, - () -> protocol.runInAutoCommitTransaction( connectionMock( "foo", protocol ), new Query( "RETURN 1" ), BookmarkHolder.NO_OP, - TransactionConfig.empty(), true, UNLIMITED_FETCH_SIZE ) ); + () -> protocol.runInAutoCommitTransaction( connectionMock( "foo", protocol ), new Query( "RETURN 1" ), BookmarkHolder.NO_OP, + TransactionConfig.empty(), true, UNLIMITED_FETCH_SIZE ) ); } else { diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v3/MessageFormatV3Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v3/MessageFormatV3Test.java index f46b68a8e2..b0e8f13221 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/v3/MessageFormatV3Test.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v3/MessageFormatV3Test.java @@ -21,7 +21,7 @@ import org.junit.jupiter.api.Test; import org.neo4j.driver.internal.messaging.MessageFormat; -import org.neo4j.driver.internal.messaging.v2.MessageReaderV2; +import org.neo4j.driver.internal.messaging.common.CommonMessageReader; import org.neo4j.driver.internal.packstream.PackInput; import org.neo4j.driver.internal.packstream.PackOutput; @@ -29,14 +29,19 @@ import static org.hamcrest.Matchers.instanceOf; import static org.mockito.Mockito.mock; +/** + * The MessageFormat under tests is the one provided by the {@link BoltProtocolV3} and not an specific class implementation. + *

+ * It's done on this way to make easy to replace the implementation and still getting the same behaviour. + */ class MessageFormatV3Test { + private static MessageFormat messageFormat = BoltProtocolV3.INSTANCE.createMessageFormat(); + @Test void shouldCreateCorrectWriter() { - MessageFormatV3 format = new MessageFormatV3(); - - MessageFormat.Writer writer = format.newWriter( mock( PackOutput.class ) ); + MessageFormat.Writer writer = messageFormat.newWriter( mock( PackOutput.class ) ); assertThat( writer, instanceOf( MessageWriterV3.class ) ); } @@ -44,10 +49,8 @@ void shouldCreateCorrectWriter() @Test void shouldCreateCorrectReader() { - MessageFormatV3 format = new MessageFormatV3(); - - MessageFormat.Reader reader = format.newReader( mock( PackInput.class ) ); + MessageFormat.Reader reader = messageFormat.newReader( mock( PackInput.class ) ); - assertThat( reader, instanceOf( MessageReaderV2.class ) ); + assertThat( reader, instanceOf( CommonMessageReader.class ) ); } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v3/MessageReaderV3Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v3/MessageReaderV3Test.java new file mode 100644 index 0000000000..fc0a0bd1f8 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v3/MessageReaderV3Test.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2002-2020 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.messaging.v3; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.HashMap; +import java.util.stream.Stream; + +import org.neo4j.driver.Value; +import org.neo4j.driver.Values; +import org.neo4j.driver.internal.InternalPoint2D; +import org.neo4j.driver.internal.InternalPoint3D; +import org.neo4j.driver.internal.messaging.Message; +import org.neo4j.driver.internal.messaging.MessageFormat; +import org.neo4j.driver.internal.messaging.request.DiscardAllMessage; +import org.neo4j.driver.internal.messaging.request.RunMessage; +import org.neo4j.driver.internal.messaging.response.FailureMessage; +import org.neo4j.driver.internal.messaging.response.IgnoredMessage; +import org.neo4j.driver.internal.messaging.response.RecordMessage; +import org.neo4j.driver.internal.messaging.response.SuccessMessage; +import org.neo4j.driver.internal.packstream.PackInput; +import org.neo4j.driver.internal.util.messaging.AbstractMessageReaderTestBase; + +import static java.util.Arrays.asList; +import static java.util.Calendar.APRIL; +import static java.util.Calendar.AUGUST; +import static org.neo4j.driver.Values.parameters; +import static org.neo4j.driver.Values.value; +import static org.neo4j.driver.internal.util.ValueFactory.emptyNodeValue; +import static org.neo4j.driver.internal.util.ValueFactory.emptyPathValue; +import static org.neo4j.driver.internal.util.ValueFactory.emptyRelationshipValue; +import static org.neo4j.driver.internal.util.ValueFactory.filledNodeValue; +import static org.neo4j.driver.internal.util.ValueFactory.filledPathValue; +import static org.neo4j.driver.internal.util.ValueFactory.filledRelationshipValue; + +/** + * The MessageReader under tests is the one provided by the {@link BoltProtocolV3} and not an specific class implementation. + *

+ * It's done on this way to make easy to replace the implementation and still getting the same behaviour. + */ +public class MessageReaderV3Test extends AbstractMessageReaderTestBase +{ + + @Override + protected Stream supportedMessages() + { + return Stream.of( + // V2 Record types + record( value( new InternalPoint2D( 42, 120.65, -99.2 ) ) ), + record( value( new InternalPoint3D( 42, 85.391, 98.8, 11.1 ) ) ), + record( value( LocalDate.of( 2012, AUGUST, 3 ) ) ), + record( value( OffsetTime.of( 23, 59, 59, 999, ZoneOffset.MAX ) ) ), + record( value( LocalTime.of( 12, 25 ) ) ), + record( value( LocalDateTime.of( 1999, APRIL, 3, 19, 5, 5, 100_200_300 ) ) ), + record( value( ZonedDateTime.of( 1823, 1, 12, 23, 59, 59, 999_999_999, ZoneOffset.ofHoursMinutes( -7, -15 ) ) ) ), + record( value( ZonedDateTime.of( 1823, 1, 12, 23, 59, 59, 999_999_999, ZoneId.of( "Europe/Stockholm" ) ) ) ), + record( value( Values.isoDuration( Long.MAX_VALUE - 1, Integer.MAX_VALUE - 1, Short.MAX_VALUE - 1, Byte.MAX_VALUE - 1 ).asIsoDuration() ) ), + record( value( Values.isoDuration( 17, 22, 99, 15 ).asIsoDuration() ) ), + + // Bolt previous versions valid messages + new FailureMessage( "Hello", "World!" ), + IgnoredMessage.IGNORED, + new SuccessMessage( new HashMap<>() ), + record( value( 1337L ) ), + record( value( parameters( "cat", null, "dog", null ) ) ), + record( value( parameters( "k", 12, "a", "banana" ) ) ), + record( value( asList( "k", 12, "a", "banana" ) ) ), + + // V3 Record Types + record( emptyNodeValue() ), + record( filledNodeValue() ), + record( emptyRelationshipValue() ), + record( filledRelationshipValue() ), + record( filledPathValue() ), + record( emptyPathValue() ) + ); + } + + @Override + protected Stream unsupportedMessages() + { + return Stream.of( + DiscardAllMessage.DISCARD_ALL, + new RunMessage( "RETURN 42" ) + ); + } + + @Override + protected MessageFormat.Reader newReader( PackInput input ) + { + return BoltProtocolV3.INSTANCE.createMessageFormat().newReader( input ); + } + + private Message record( Value value ) + { + return new RecordMessage( new Value[]{value} ); + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v3/MessageWriterV3Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v3/MessageWriterV3Test.java index 678aff290a..87d23c7d24 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/v3/MessageWriterV3Test.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v3/MessageWriterV3Test.java @@ -18,6 +18,12 @@ */ package org.neo4j.driver.internal.messaging.v3; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetTime; +import java.time.ZoneId; +import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.Collections; import java.util.stream.Stream; @@ -35,6 +41,7 @@ import org.neo4j.driver.internal.util.messaging.AbstractMessageWriterTestBase; import static java.time.Duration.ofSeconds; +import static java.util.Calendar.DECEMBER; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonMap; import static org.neo4j.driver.AccessMode.READ; @@ -52,29 +59,49 @@ import static org.neo4j.driver.internal.messaging.request.RunWithMetadataMessage.autoCommitTxRunMessage; import static org.neo4j.driver.internal.messaging.request.RunWithMetadataMessage.unmanagedTxRunMessage; +/** + * The MessageWriter under tests is the one provided by the {@link BoltProtocolV3} and not an specific class implementation. + *

+ * It's done on this way to make easy to replace the implementation and still getting the same behaviour. + */ class MessageWriterV3Test extends AbstractMessageWriterTestBase { @Override protected MessageFormat.Writer newWriter( PackOutput output ) { - return new MessageWriterV3( output ); + return BoltProtocolV3.INSTANCE.createMessageFormat().newWriter( output ); } @Override protected Stream supportedMessages() { return Stream.of( + // Bolt V2 Data Types + unmanagedTxRunMessage( new Query( "RETURN $point", singletonMap( "point", point( 42, 12.99, -180.0 ) ) ) ), + unmanagedTxRunMessage( new Query( "RETURN $point", singletonMap( "point", point( 42, 0.51, 2.99, 100.123 ) ) ) ), + unmanagedTxRunMessage( new Query( "RETURN $date", singletonMap( "date", value( LocalDate.ofEpochDay( 2147483650L ) ) ) ) ), + unmanagedTxRunMessage( new Query( "RETURN $time", singletonMap( "time", value( OffsetTime.of( 4, 16, 20, 999, ZoneOffset.MIN ) ) ) ) ), + unmanagedTxRunMessage( new Query( "RETURN $time", singletonMap( "time", value( LocalTime.of( 12, 9, 18, 999_888 ) ) ) ) ), + unmanagedTxRunMessage( + new Query( "RETURN $dateTime", singletonMap( "dateTime", value( LocalDateTime.of( 2049, DECEMBER, 12, 17, 25, 49, 199 ) ) ) ) ), + unmanagedTxRunMessage( new Query( "RETURN $dateTime", singletonMap( "dateTime", value( ZonedDateTime.of( 2000, 1, 10, 12, 2, 49, 300, ZoneOffset + .ofHoursMinutes( 9, 30 ) ) ) ) ) ), + unmanagedTxRunMessage( new Query( "RETURN $dateTime", singletonMap( "dateTime", value( ZonedDateTime.of( 2000, 1, 10, 12, 2, 49, 300, ZoneId.of( + "Europe/Stockholm" ) ) ) ) ) ), + // Bolt V3 messages new HelloMessage( "MyDriver/1.2.3", ((InternalAuthToken) basic( "neo4j", "neo4j" )).toMap(), Collections.emptyMap() ), GOODBYE, - new BeginMessage( InternalBookmark.parse( "neo4j:bookmark:v1:tx123" ), ofSeconds( 5 ), singletonMap( "key", value( 42 ) ), READ, defaultDatabase() ), - new BeginMessage( InternalBookmark.parse( "neo4j:bookmark:v1:tx123" ), ofSeconds( 5 ), singletonMap( "key", value( 42 ) ), WRITE, defaultDatabase() ), + new BeginMessage( InternalBookmark.parse( "neo4j:bookmark:v1:tx123" ), ofSeconds( 5 ), singletonMap( "key", value( 42 ) ), READ, + defaultDatabase() ), + new BeginMessage( InternalBookmark.parse( "neo4j:bookmark:v1:tx123" ), ofSeconds( 5 ), singletonMap( "key", value( 42 ) ), WRITE, + defaultDatabase() ), COMMIT, ROLLBACK, autoCommitTxRunMessage( new Query( "RETURN 1" ), ofSeconds( 5 ), singletonMap( "key", value( 42 ) ), defaultDatabase(), READ, - InternalBookmark.parse( "neo4j:bookmark:v1:tx1" ) ), + InternalBookmark.parse( "neo4j:bookmark:v1:tx1" ) ), autoCommitTxRunMessage( new Query( "RETURN 1" ), ofSeconds( 5 ), singletonMap( "key", value( 42 ) ), defaultDatabase(), WRITE, - InternalBookmark.parse( "neo4j:bookmark:v1:tx1" ) ), + InternalBookmark.parse( "neo4j:bookmark:v1:tx1" ) ), unmanagedTxRunMessage( new Query( "RETURN 1" ) ), PULL_ALL, DISCARD_ALL, @@ -82,9 +109,9 @@ protected Stream supportedMessages() // Bolt V3 messages with struct values autoCommitTxRunMessage( new Query( "RETURN $x", singletonMap( "x", value( ZonedDateTime.now() ) ) ), ofSeconds( 1 ), emptyMap(), - defaultDatabase(), READ, InternalBookmark.empty() ), + defaultDatabase(), READ, InternalBookmark.empty() ), autoCommitTxRunMessage( new Query( "RETURN $x", singletonMap( "x", value( ZonedDateTime.now() ) ) ), ofSeconds( 1 ), emptyMap(), - defaultDatabase(), WRITE, InternalBookmark.empty() ), + defaultDatabase(), WRITE, InternalBookmark.empty() ), unmanagedTxRunMessage( new Query( "RETURN $x", singletonMap( "x", point( 42, 1, 2, 3 ) ) ) ) ); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v4/BoltProtocolV4Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v4/BoltProtocolV4Test.java index 6248b8433b..e64704cfa9 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/v4/BoltProtocolV4Test.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v4/BoltProtocolV4Test.java @@ -18,66 +18,353 @@ */ package org.neo4j.driver.internal.messaging.v4; +import io.netty.channel.ChannelPromise; +import io.netty.channel.embedded.EmbeddedChannel; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; import org.mockito.ArgumentCaptor; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import org.neo4j.driver.AccessMode; +import org.neo4j.driver.AuthTokens; import org.neo4j.driver.Bookmark; +import org.neo4j.driver.Logging; +import org.neo4j.driver.Query; import org.neo4j.driver.TransactionConfig; +import org.neo4j.driver.Value; import org.neo4j.driver.internal.BookmarkHolder; import org.neo4j.driver.internal.DatabaseName; import org.neo4j.driver.internal.DefaultBookmarkHolder; import org.neo4j.driver.internal.InternalBookmark; import org.neo4j.driver.internal.async.UnmanagedTransaction; +import org.neo4j.driver.internal.async.connection.ChannelAttributes; +import org.neo4j.driver.internal.async.inbound.InboundMessageDispatcher; +import org.neo4j.driver.internal.cluster.RoutingContext; import org.neo4j.driver.internal.cursor.AsyncResultCursor; import org.neo4j.driver.internal.cursor.ResultCursorFactory; import org.neo4j.driver.internal.handlers.BeginTxResponseHandler; +import org.neo4j.driver.internal.handlers.CommitTxResponseHandler; import org.neo4j.driver.internal.handlers.NoOpResponseHandler; import org.neo4j.driver.internal.handlers.PullAllResponseHandler; +import org.neo4j.driver.internal.handlers.RollbackTxResponseHandler; import org.neo4j.driver.internal.handlers.RunResponseHandler; import org.neo4j.driver.internal.messaging.BoltProtocol; import org.neo4j.driver.internal.messaging.MessageFormat; import org.neo4j.driver.internal.messaging.request.BeginMessage; +import org.neo4j.driver.internal.messaging.request.CommitMessage; +import org.neo4j.driver.internal.messaging.request.GoodbyeMessage; +import org.neo4j.driver.internal.messaging.request.HelloMessage; import org.neo4j.driver.internal.messaging.request.PullMessage; +import org.neo4j.driver.internal.messaging.request.RollbackMessage; import org.neo4j.driver.internal.messaging.request.RunWithMetadataMessage; -import org.neo4j.driver.internal.messaging.v3.BoltProtocolV3Test; +import org.neo4j.driver.internal.security.InternalAuthToken; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.spi.ResponseHandler; +import static java.time.Duration.ofSeconds; import static java.util.Collections.emptyMap; +import static java.util.Collections.singletonMap; +import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.junit.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.neo4j.driver.AccessMode.WRITE; +import static org.neo4j.driver.Values.value; import static org.neo4j.driver.internal.DatabaseNameUtil.database; import static org.neo4j.driver.internal.DatabaseNameUtil.defaultDatabase; import static org.neo4j.driver.internal.handlers.pulln.FetchSizeUtil.UNLIMITED_FETCH_SIZE; +import static org.neo4j.driver.util.TestUtil.anyServerVersion; import static org.neo4j.driver.util.TestUtil.await; import static org.neo4j.driver.util.TestUtil.connectionMock; -public class BoltProtocolV4Test extends BoltProtocolV3Test +public final class BoltProtocolV4Test { - @Override - protected BoltProtocol createProtocol() + + protected static final String QUERY_TEXT = "RETURN $x"; + protected static final Map PARAMS = singletonMap( "x", value( 42 ) ); + protected static final Query QUERY = new Query( QUERY_TEXT, value( PARAMS ) ); + + protected final BoltProtocol protocol = createProtocol(); + private final EmbeddedChannel channel = new EmbeddedChannel(); + private final InboundMessageDispatcher messageDispatcher = new InboundMessageDispatcher( channel, Logging.none() ); + + private final TransactionConfig txConfig = TransactionConfig.builder() + .withTimeout( ofSeconds( 12 ) ) + .withMetadata( singletonMap( "key", value( 42 ) ) ) + .build(); + + @BeforeEach + void beforeEach() + { + ChannelAttributes.setMessageDispatcher( channel, messageDispatcher ); + } + + @AfterEach + void afterEach() + { + channel.finishAndReleaseAll(); + } + + @Test + void shouldCreateMessageFormat() + { + assertThat( protocol.createMessageFormat(), instanceOf( expectedMessageFormatType() ) ); + } + + @Test + void shouldInitializeChannel() + { + ChannelPromise promise = channel.newPromise(); + + protocol.initializeChannel( "MyDriver/0.0.1", dummyAuthToken(), RoutingContext.EMPTY, promise ); + + assertThat( channel.outboundMessages(), hasSize( 1 ) ); + assertThat( channel.outboundMessages().poll(), instanceOf( HelloMessage.class ) ); + assertEquals( 1, messageDispatcher.queuedHandlersCount() ); + assertFalse( promise.isDone() ); + + Map metadata = new HashMap<>(); + metadata.put( "server", value( anyServerVersion().toString() ) ); + metadata.put( "connection_id", value( "bolt-42" ) ); + + messageDispatcher.handleSuccessMessage( metadata ); + + assertTrue( promise.isDone() ); + assertTrue( promise.isSuccess() ); + } + + @Test + void shouldPrepareToCloseChannel() + { + protocol.prepareToCloseChannel( channel ); + + assertThat( channel.outboundMessages(), hasSize( 1 ) ); + assertThat( channel.outboundMessages().poll(), instanceOf( GoodbyeMessage.class ) ); + assertEquals( 1, messageDispatcher.queuedHandlersCount() ); + } + + @Test + void shouldFailToInitializeChannelWhenErrorIsReceived() + { + ChannelPromise promise = channel.newPromise(); + + protocol.initializeChannel( "MyDriver/2.2.1", dummyAuthToken(), RoutingContext.EMPTY, promise ); + + assertThat( channel.outboundMessages(), hasSize( 1 ) ); + assertThat( channel.outboundMessages().poll(), instanceOf( HelloMessage.class ) ); + assertEquals( 1, messageDispatcher.queuedHandlersCount() ); + assertFalse( promise.isDone() ); + + messageDispatcher.handleFailureMessage( "Neo.TransientError.General.DatabaseUnavailable", "Error!" ); + + assertTrue( promise.isDone() ); + assertFalse( promise.isSuccess() ); + } + + @Test + void shouldBeginTransactionWithoutBookmark() + { + Connection connection = connectionMock( protocol ); + + CompletionStage stage = protocol.beginTransaction( connection, InternalBookmark.empty(), TransactionConfig.empty() ); + + verify( connection ) + .write( new BeginMessage( InternalBookmark.empty(), TransactionConfig.empty(), defaultDatabase(), WRITE ), NoOpResponseHandler.INSTANCE ); + assertNull( await( stage ) ); + } + + @Test + void shouldBeginTransactionWithBookmarks() + { + Connection connection = connectionMock( protocol ); + Bookmark bookmark = InternalBookmark.parse( "neo4j:bookmark:v1:tx100" ); + + CompletionStage stage = protocol.beginTransaction( connection, bookmark, TransactionConfig.empty() ); + + verify( connection ) + .writeAndFlush( eq( new BeginMessage( bookmark, TransactionConfig.empty(), defaultDatabase(), WRITE ) ), any( BeginTxResponseHandler.class ) ); + assertNull( await( stage ) ); + } + + @Test + void shouldBeginTransactionWithConfig() + { + Connection connection = connectionMock( protocol ); + + CompletionStage stage = protocol.beginTransaction( connection, InternalBookmark.empty(), txConfig ); + + verify( connection ).write( new BeginMessage( InternalBookmark.empty(), txConfig, defaultDatabase(), WRITE ), NoOpResponseHandler.INSTANCE ); + assertNull( await( stage ) ); + } + + @Test + void shouldBeginTransactionWithBookmarksAndConfig() + { + Connection connection = connectionMock( protocol ); + Bookmark bookmark = InternalBookmark.parse( "neo4j:bookmark:v1:tx4242" ); + + CompletionStage stage = protocol.beginTransaction( connection, bookmark, txConfig ); + + verify( connection ).writeAndFlush( eq( new BeginMessage( bookmark, txConfig, defaultDatabase(), WRITE ) ), any( BeginTxResponseHandler.class ) ); + assertNull( await( stage ) ); + } + + @Test + void shouldCommitTransaction() + { + String bookmarkString = "neo4j:bookmark:v1:tx4242"; + + Connection connection = connectionMock( protocol ); + when( connection.protocol() ).thenReturn( protocol ); + doAnswer( invocation -> + { + ResponseHandler commitHandler = invocation.getArgument( 1 ); + commitHandler.onSuccess( singletonMap( "bookmark", value( bookmarkString ) ) ); + return null; + } ).when( connection ).writeAndFlush( eq( CommitMessage.COMMIT ), any() ); + + CompletionStage stage = protocol.commitTransaction( connection ); + + verify( connection ).writeAndFlush( eq( CommitMessage.COMMIT ), any( CommitTxResponseHandler.class ) ); + assertEquals( InternalBookmark.parse( bookmarkString ), await( stage ) ); + } + + @Test + void shouldRollbackTransaction() + { + Connection connection = connectionMock( protocol ); + + CompletionStage stage = protocol.rollbackTransaction( connection ); + + verify( connection ).writeAndFlush( eq( RollbackMessage.ROLLBACK ), any( RollbackTxResponseHandler.class ) ); + assertNull( await( stage ) ); + } + + @ParameterizedTest + @EnumSource( AccessMode.class ) + void shouldRunInAutoCommitTransactionWithoutWaitingForRunResponse( AccessMode mode ) throws Exception + { + testRunWithoutWaitingForRunResponse( true, TransactionConfig.empty(), mode ); + } + + @ParameterizedTest + @EnumSource( AccessMode.class ) + void shouldRunInAutoCommitWithConfigTransactionWithoutWaitingForRunResponse( AccessMode mode ) throws Exception + { + testRunWithoutWaitingForRunResponse( true, txConfig, mode ); + } + + @ParameterizedTest + @EnumSource( AccessMode.class ) + void shouldRunInAutoCommitTransactionAndWaitForSuccessRunResponse( AccessMode mode ) throws Exception + { + testSuccessfulRunInAutoCommitTxWithWaitingForResponse( InternalBookmark.empty(), TransactionConfig.empty(), mode ); + } + + @ParameterizedTest + @EnumSource( AccessMode.class ) + void shouldRunInAutoCommitTransactionWithBookmarkAndConfigAndWaitForSuccessRunResponse( AccessMode mode ) throws Exception + { + testSuccessfulRunInAutoCommitTxWithWaitingForResponse( InternalBookmark.parse( "neo4j:bookmark:v1:tx65" ), txConfig, mode ); + } + + @ParameterizedTest + @EnumSource( AccessMode.class ) + void shouldRunInAutoCommitTransactionAndWaitForFailureRunResponse( AccessMode mode ) throws Exception + { + testFailedRunInAutoCommitTxWithWaitingForResponse( InternalBookmark.empty(), TransactionConfig.empty(), mode ); + } + + @ParameterizedTest + @EnumSource( AccessMode.class ) + void shouldRunInAutoCommitTransactionWithBookmarkAndConfigAndWaitForFailureRunResponse( AccessMode mode ) throws Exception + { + testFailedRunInAutoCommitTxWithWaitingForResponse( InternalBookmark.parse( "neo4j:bookmark:v1:tx163" ), txConfig, mode ); + } + + @ParameterizedTest + @EnumSource( AccessMode.class ) + void shouldRunInUnmanagedTransactionWithoutWaitingForRunResponse( AccessMode mode ) throws Exception + { + testRunWithoutWaitingForRunResponse( false, TransactionConfig.empty(), mode ); + } + + @ParameterizedTest + @EnumSource( AccessMode.class ) + void shouldRunInUnmanagedTransactionAndWaitForSuccessRunResponse( AccessMode mode ) throws Exception + { + testRunInUnmanagedTransactionAndWaitForRunResponse( true, mode ); + } + + @ParameterizedTest + @EnumSource( AccessMode.class ) + void shouldRunInUnmanagedTransactionAndWaitForFailureRunResponse( AccessMode mode ) throws Exception + { + testRunInUnmanagedTransactionAndWaitForRunResponse( false, mode ); + } + + @Test + void databaseNameInBeginTransaction() + { + testDatabaseNameSupport( false ); + } + + @Test + void databaseNameForAutoCommitTransactions() + { + testDatabaseNameSupport( true ); + } + + @Test + void shouldSupportDatabaseNameInBeginTransaction() + { + CompletionStage txStage = protocol.beginTransaction( connectionMock( "foo", protocol ), InternalBookmark.empty(), TransactionConfig.empty() ); + + assertDoesNotThrow( () -> await( txStage ) ); + } + + @Test + void shouldNotSupportDatabaseNameForAutoCommitTransactions() + { + assertDoesNotThrow( + () -> protocol.runInAutoCommitTransaction( connectionMock( "foo", protocol ), + new Query( "RETURN 1" ), BookmarkHolder.NO_OP, TransactionConfig.empty(), true, + UNLIMITED_FETCH_SIZE ) ); + } + + private BoltProtocol createProtocol() { return BoltProtocolV4.INSTANCE; } - @Override - protected Class expectedMessageFormatType() + private Class expectedMessageFormatType() { return MessageFormatV4.class; } - @Override + private static InternalAuthToken dummyAuthToken() + { + return (InternalAuthToken) AuthTokens.basic( "hello", "world" ); + } + protected void testFailedRunInAutoCommitTxWithWaitingForResponse( Bookmark bookmark, TransactionConfig config, AccessMode mode ) throws Exception { // Given @@ -101,7 +388,6 @@ protected void testFailedRunInAutoCommitTxWithWaitingForResponse( Bookmark bookm assertNotNull( cursorFuture.get() ); } - @Override protected void testSuccessfulRunInAutoCommitTxWithWaitingForResponse( Bookmark bookmark, TransactionConfig config, AccessMode mode ) throws Exception { // Given @@ -125,7 +411,6 @@ protected void testSuccessfulRunInAutoCommitTxWithWaitingForResponse( Bookmark b assertNotNull( cursorFuture.get() ); } - @Override protected void testRunInUnmanagedTransactionAndWaitForRunResponse(boolean success, AccessMode mode ) throws Exception { // Given @@ -154,7 +439,6 @@ protected void testRunInUnmanagedTransactionAndWaitForRunResponse(boolean succes assertNotNull( cursorFuture.get() ); } - @Override protected void testRunWithoutWaitingForRunResponse( boolean autoCommitTx, TransactionConfig config, AccessMode mode ) throws Exception { // Given @@ -190,7 +474,6 @@ protected void testRunWithoutWaitingForRunResponse( boolean autoCommitTx, Transa } } - @Override protected void testDatabaseNameSupport( boolean autoCommitTx ) { Connection connection = connectionMock( "foo", protocol ); diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v4/MessageFormatV4Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v4/MessageFormatV4Test.java index b77d70b5de..42ca0246a6 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/v4/MessageFormatV4Test.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v4/MessageFormatV4Test.java @@ -21,7 +21,7 @@ import org.junit.jupiter.api.Test; import org.neo4j.driver.internal.messaging.MessageFormat; -import org.neo4j.driver.internal.messaging.v2.MessageReaderV2; +import org.neo4j.driver.internal.messaging.common.CommonMessageReader; import org.neo4j.driver.internal.packstream.PackInput; import org.neo4j.driver.internal.packstream.PackOutput; @@ -29,13 +29,18 @@ import static org.hamcrest.Matchers.instanceOf; import static org.mockito.Mockito.mock; +/** + * The MessageFormat under tests is the one provided by the {@link BoltProtocolV4} and not an specific class implementation. + *

+ * It's done on this way to make easy to replace the implementation and still getting the same behaviour. + */ class MessageFormatV4Test { + private static final MessageFormat format = BoltProtocolV4.INSTANCE.createMessageFormat(); + @Test void shouldCreateCorrectWriter() { - MessageFormatV4 format = new MessageFormatV4(); - MessageFormat.Writer writer = format.newWriter( mock( PackOutput.class ) ); assertThat( writer, instanceOf( MessageWriterV4.class ) ); @@ -44,10 +49,8 @@ void shouldCreateCorrectWriter() @Test void shouldCreateCorrectReader() { - MessageFormatV4 format = new MessageFormatV4(); - MessageFormat.Reader reader = format.newReader( mock( PackInput.class ) ); - assertThat( reader, instanceOf( MessageReaderV2.class ) ); + assertThat( reader, instanceOf( CommonMessageReader.class ) ); } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v4/MessageReaderV4Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v4/MessageReaderV4Test.java new file mode 100644 index 0000000000..eadacd3f92 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v4/MessageReaderV4Test.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2002-2020 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.messaging.v4; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.HashMap; +import java.util.stream.Stream; + +import org.neo4j.driver.Value; +import org.neo4j.driver.Values; +import org.neo4j.driver.internal.InternalPoint2D; +import org.neo4j.driver.internal.InternalPoint3D; +import org.neo4j.driver.internal.messaging.Message; +import org.neo4j.driver.internal.messaging.MessageFormat; +import org.neo4j.driver.internal.messaging.request.DiscardAllMessage; +import org.neo4j.driver.internal.messaging.request.RunMessage; +import org.neo4j.driver.internal.messaging.response.FailureMessage; +import org.neo4j.driver.internal.messaging.response.IgnoredMessage; +import org.neo4j.driver.internal.messaging.response.RecordMessage; +import org.neo4j.driver.internal.messaging.response.SuccessMessage; +import org.neo4j.driver.internal.packstream.PackInput; +import org.neo4j.driver.internal.util.messaging.AbstractMessageReaderTestBase; + +import static java.util.Arrays.asList; +import static java.util.Calendar.APRIL; +import static java.util.Calendar.AUGUST; +import static org.neo4j.driver.Values.parameters; +import static org.neo4j.driver.Values.value; +import static org.neo4j.driver.internal.util.ValueFactory.emptyNodeValue; +import static org.neo4j.driver.internal.util.ValueFactory.emptyPathValue; +import static org.neo4j.driver.internal.util.ValueFactory.emptyRelationshipValue; +import static org.neo4j.driver.internal.util.ValueFactory.filledNodeValue; +import static org.neo4j.driver.internal.util.ValueFactory.filledPathValue; +import static org.neo4j.driver.internal.util.ValueFactory.filledRelationshipValue; + +/** + * The MessageReader under tests is the one provided by the {@link BoltProtocolV4} and not an specific class implementation. + *

+ * It's done on this way to make easy to replace the implementation and still getting the same behaviour. + */ +public class MessageReaderV4Test extends AbstractMessageReaderTestBase +{ + + @Override + protected Stream supportedMessages() + { + return Stream.of( + // V2 Record types + record( value( new InternalPoint2D( 42, 120.65, -99.2 ) ) ), + record( value( new InternalPoint3D( 42, 85.391, 98.8, 11.1 ) ) ), + record( value( LocalDate.of( 2012, AUGUST, 3 ) ) ), + record( value( OffsetTime.of( 23, 59, 59, 999, ZoneOffset.MAX ) ) ), + record( value( LocalTime.of( 12, 25 ) ) ), + record( value( LocalDateTime.of( 1999, APRIL, 3, 19, 5, 5, 100_200_300 ) ) ), + record( value( ZonedDateTime.of( 1823, 1, 12, 23, 59, 59, 999_999_999, ZoneOffset.ofHoursMinutes( -7, -15 ) ) ) ), + record( value( ZonedDateTime.of( 1823, 1, 12, 23, 59, 59, 999_999_999, ZoneId.of( "Europe/Stockholm" ) ) ) ), + record( value( Values.isoDuration( Long.MAX_VALUE - 1, Integer.MAX_VALUE - 1, Short.MAX_VALUE - 1, Byte.MAX_VALUE - 1 ).asIsoDuration() ) ), + record( value( Values.isoDuration( 17, 22, 99, 15 ).asIsoDuration() ) ), + + // Bolt previous versions valid messages + new FailureMessage( "Hello", "World!" ), + IgnoredMessage.IGNORED, + new SuccessMessage( new HashMap<>() ), + record( value( 1337L ) ), + record( value( parameters( "cat", null, "dog", null ) ) ), + record( value( parameters( "k", 12, "a", "banana" ) ) ), + record( value( asList( "k", 12, "a", "banana" ) ) ), + + // V3 Record Types + record( emptyNodeValue() ), + record( filledNodeValue() ), + record( emptyRelationshipValue() ), + record( filledRelationshipValue() ), + record( filledPathValue() ), + record( emptyPathValue() ) + ); + } + + @Override + protected Stream unsupportedMessages() + { + return Stream.of( + DiscardAllMessage.DISCARD_ALL, + new RunMessage( "RETURN 42" ) + ); + } + + @Override + protected MessageFormat.Reader newReader( PackInput input ) + { + return BoltProtocolV4.INSTANCE.createMessageFormat().newReader( input ); + } + + private Message record( Value value ) + { + return new RecordMessage( new Value[]{value} ); + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v4/MessageWriterV4Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v4/MessageWriterV4Test.java index 0df66da9c4..6a7803bc3b 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/v4/MessageWriterV4Test.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v4/MessageWriterV4Test.java @@ -18,6 +18,12 @@ */ package org.neo4j.driver.internal.messaging.v4; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetTime; +import java.time.ZoneId; +import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.Collections; import java.util.stream.Stream; @@ -32,11 +38,13 @@ import org.neo4j.driver.internal.messaging.request.InitMessage; import org.neo4j.driver.internal.messaging.request.PullMessage; import org.neo4j.driver.internal.messaging.request.RunMessage; +import org.neo4j.driver.internal.messaging.v3.BoltProtocolV3; import org.neo4j.driver.internal.packstream.PackOutput; import org.neo4j.driver.internal.security.InternalAuthToken; import org.neo4j.driver.internal.util.messaging.AbstractMessageWriterTestBase; import static java.time.Duration.ofSeconds; +import static java.util.Calendar.DECEMBER; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonMap; import static org.neo4j.driver.AccessMode.READ; @@ -55,18 +63,35 @@ import static org.neo4j.driver.internal.messaging.request.RunWithMetadataMessage.autoCommitTxRunMessage; import static org.neo4j.driver.internal.messaging.request.RunWithMetadataMessage.unmanagedTxRunMessage; +/** + * The MessageWriter under tests is the one provided by the {@link BoltProtocolV3} and not an specific class implementation. + *

+ * It's done on this way to make easy to replace the implementation and still getting the same behaviour. + */ class MessageWriterV4Test extends AbstractMessageWriterTestBase { @Override protected MessageFormat.Writer newWriter( PackOutput output ) { - return new MessageWriterV4( output ); + return BoltProtocolV4.INSTANCE.createMessageFormat().newWriter( output ); } @Override protected Stream supportedMessages() { return Stream.of( + // Bolt V2 Data Types + unmanagedTxRunMessage( new Query( "RETURN $point", singletonMap( "point", point( 42, 12.99, -180.0 ) ) ) ), + unmanagedTxRunMessage( new Query( "RETURN $point", singletonMap( "point", point( 42, 0.51, 2.99, 100.123 ) ) ) ), + unmanagedTxRunMessage( new Query( "RETURN $date", singletonMap( "date", value( LocalDate.ofEpochDay( 2147483650L ) ) ) ) ), + unmanagedTxRunMessage( new Query( "RETURN $time", singletonMap( "time", value( OffsetTime.of( 4, 16, 20, 999, ZoneOffset.MIN ) ) ) ) ), + unmanagedTxRunMessage( new Query( "RETURN $time", singletonMap( "time", value( LocalTime.of( 12, 9, 18, 999_888 ) ) ) ) ), + unmanagedTxRunMessage( + new Query( "RETURN $dateTime", singletonMap( "dateTime", value( LocalDateTime.of( 2049, DECEMBER, 12, 17, 25, 49, 199 ) ) ) ) ), + unmanagedTxRunMessage( new Query( "RETURN $dateTime", singletonMap( "dateTime", value( ZonedDateTime.of( 2000, 1, 10, 12, 2, 49, 300, ZoneOffset + .ofHoursMinutes( 9, 30 ) ) ) ) ) ), + unmanagedTxRunMessage( new Query( "RETURN $dateTime", singletonMap( "dateTime", value( ZonedDateTime.of( 2000, 1, 10, 12, 2, 49, 300, ZoneId.of( + "Europe/Stockholm" ) ) ) ) ) ), // New Bolt V4 messages new PullMessage( 100, 200 ), @@ -75,23 +100,25 @@ protected Stream supportedMessages() // Bolt V3 messages new HelloMessage( "MyDriver/1.2.3", ((InternalAuthToken) basic( "neo4j", "neo4j" )).toMap(), Collections.emptyMap() ), GOODBYE, - new BeginMessage( InternalBookmark.parse( "neo4j:bookmark:v1:tx123" ), ofSeconds( 5 ), singletonMap( "key", value( 42 ) ), READ, defaultDatabase() ), - new BeginMessage( InternalBookmark.parse( "neo4j:bookmark:v1:tx123" ), ofSeconds( 5 ), singletonMap( "key", value( 42 ) ), WRITE, database( "foo" ) ), + new BeginMessage( InternalBookmark.parse( "neo4j:bookmark:v1:tx123" ), ofSeconds( 5 ), singletonMap( "key", value( 42 ) ), READ, + defaultDatabase() ), + new BeginMessage( InternalBookmark.parse( "neo4j:bookmark:v1:tx123" ), ofSeconds( 5 ), singletonMap( "key", value( 42 ) ), WRITE, + database( "foo" ) ), COMMIT, ROLLBACK, RESET, autoCommitTxRunMessage( new Query( "RETURN 1" ), ofSeconds( 5 ), singletonMap( "key", value( 42 ) ), defaultDatabase(), READ, - InternalBookmark.parse( "neo4j:bookmark:v1:tx1" ) ), + InternalBookmark.parse( "neo4j:bookmark:v1:tx1" ) ), autoCommitTxRunMessage( new Query( "RETURN 1" ), ofSeconds( 5 ), singletonMap( "key", value( 42 ) ), database( "foo" ), WRITE, - InternalBookmark.parse( "neo4j:bookmark:v1:tx1" ) ), + InternalBookmark.parse( "neo4j:bookmark:v1:tx1" ) ), unmanagedTxRunMessage( new Query( "RETURN 1" ) ), // Bolt V3 messages with struct values autoCommitTxRunMessage( new Query( "RETURN $x", singletonMap( "x", value( ZonedDateTime.now() ) ) ), ofSeconds( 1 ), emptyMap(), - defaultDatabase(), READ, InternalBookmark.empty() ), + defaultDatabase(), READ, InternalBookmark.empty() ), autoCommitTxRunMessage( new Query( "RETURN $x", singletonMap( "x", value( ZonedDateTime.now() ) ) ), ofSeconds( 1 ), emptyMap(), database( "foo" ), - WRITE, InternalBookmark.empty() ), + WRITE, InternalBookmark.empty() ), unmanagedTxRunMessage( new Query( "RETURN $x", singletonMap( "x", point( 42, 1, 2, 3 ) ) ) ) ); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v41/BoltProtocolV41Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v41/BoltProtocolV41Test.java index c4992bf35c..0e31633629 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/v41/BoltProtocolV41Test.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v41/BoltProtocolV41Test.java @@ -18,14 +18,520 @@ */ package org.neo4j.driver.internal.messaging.v41; +import io.netty.channel.ChannelPromise; +import io.netty.channel.embedded.EmbeddedChannel; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.mockito.ArgumentCaptor; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +import org.neo4j.driver.AccessMode; +import org.neo4j.driver.AuthTokens; +import org.neo4j.driver.Bookmark; +import org.neo4j.driver.Logging; +import org.neo4j.driver.Query; +import org.neo4j.driver.TransactionConfig; +import org.neo4j.driver.Value; +import org.neo4j.driver.internal.BookmarkHolder; +import org.neo4j.driver.internal.DatabaseName; +import org.neo4j.driver.internal.DefaultBookmarkHolder; +import org.neo4j.driver.internal.InternalBookmark; +import org.neo4j.driver.internal.async.UnmanagedTransaction; +import org.neo4j.driver.internal.async.connection.ChannelAttributes; +import org.neo4j.driver.internal.async.inbound.InboundMessageDispatcher; +import org.neo4j.driver.internal.cluster.RoutingContext; +import org.neo4j.driver.internal.cursor.AsyncResultCursor; +import org.neo4j.driver.internal.cursor.ResultCursorFactory; +import org.neo4j.driver.internal.handlers.BeginTxResponseHandler; +import org.neo4j.driver.internal.handlers.CommitTxResponseHandler; +import org.neo4j.driver.internal.handlers.NoOpResponseHandler; +import org.neo4j.driver.internal.handlers.PullAllResponseHandler; +import org.neo4j.driver.internal.handlers.RollbackTxResponseHandler; +import org.neo4j.driver.internal.handlers.RunResponseHandler; import org.neo4j.driver.internal.messaging.BoltProtocol; -import org.neo4j.driver.internal.messaging.v4.BoltProtocolV4Test; +import org.neo4j.driver.internal.messaging.MessageFormat; +import org.neo4j.driver.internal.messaging.request.BeginMessage; +import org.neo4j.driver.internal.messaging.request.CommitMessage; +import org.neo4j.driver.internal.messaging.request.GoodbyeMessage; +import org.neo4j.driver.internal.messaging.request.HelloMessage; +import org.neo4j.driver.internal.messaging.request.PullMessage; +import org.neo4j.driver.internal.messaging.request.RollbackMessage; +import org.neo4j.driver.internal.messaging.request.RunWithMetadataMessage; +import org.neo4j.driver.internal.messaging.v4.MessageFormatV4; +import org.neo4j.driver.internal.security.InternalAuthToken; +import org.neo4j.driver.internal.spi.Connection; +import org.neo4j.driver.internal.spi.ResponseHandler; + +import static java.time.Duration.ofSeconds; +import static java.util.Collections.emptyMap; +import static java.util.Collections.singletonMap; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.junit.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.neo4j.driver.AccessMode.WRITE; +import static org.neo4j.driver.Values.value; +import static org.neo4j.driver.internal.DatabaseNameUtil.database; +import static org.neo4j.driver.internal.DatabaseNameUtil.defaultDatabase; +import static org.neo4j.driver.internal.handlers.pulln.FetchSizeUtil.UNLIMITED_FETCH_SIZE; +import static org.neo4j.driver.util.TestUtil.anyServerVersion; +import static org.neo4j.driver.util.TestUtil.await; +import static org.neo4j.driver.util.TestUtil.connectionMock; -public class BoltProtocolV41Test extends BoltProtocolV4Test +public final class BoltProtocolV41Test { - @Override - protected BoltProtocol createProtocol() + protected static final String QUERY_TEXT = "RETURN $x"; + protected static final Map PARAMS = singletonMap( "x", value( 42 ) ); + protected static final Query QUERY = new Query( QUERY_TEXT, value( PARAMS ) ); + + protected final BoltProtocol protocol = createProtocol(); + private final EmbeddedChannel channel = new EmbeddedChannel(); + private final InboundMessageDispatcher messageDispatcher = new InboundMessageDispatcher( channel, Logging.none() ); + + private final TransactionConfig txConfig = TransactionConfig.builder() + .withTimeout( ofSeconds( 12 ) ) + .withMetadata( singletonMap( "key", value( 42 ) ) ) + .build(); + + private BoltProtocol createProtocol() { return BoltProtocolV41.INSTANCE; } + + @BeforeEach + void beforeEach() + { + ChannelAttributes.setMessageDispatcher( channel, messageDispatcher ); + } + + @AfterEach + void afterEach() + { + channel.finishAndReleaseAll(); + } + + @Test + void shouldCreateMessageFormat() + { + assertThat( protocol.createMessageFormat(), instanceOf( expectedMessageFormatType() ) ); + } + + @Test + void shouldInitializeChannel() + { + ChannelPromise promise = channel.newPromise(); + + protocol.initializeChannel( "MyDriver/0.0.1", dummyAuthToken(), RoutingContext.EMPTY, promise ); + + assertThat( channel.outboundMessages(), hasSize( 1 ) ); + assertThat( channel.outboundMessages().poll(), instanceOf( HelloMessage.class ) ); + assertEquals( 1, messageDispatcher.queuedHandlersCount() ); + assertFalse( promise.isDone() ); + + Map metadata = new HashMap<>(); + metadata.put( "server", value( anyServerVersion().toString() ) ); + metadata.put( "connection_id", value( "bolt-42" ) ); + + messageDispatcher.handleSuccessMessage( metadata ); + + assertTrue( promise.isDone() ); + assertTrue( promise.isSuccess() ); + } + + @Test + void shouldPrepareToCloseChannel() + { + protocol.prepareToCloseChannel( channel ); + + assertThat( channel.outboundMessages(), hasSize( 1 ) ); + assertThat( channel.outboundMessages().poll(), instanceOf( GoodbyeMessage.class ) ); + assertEquals( 1, messageDispatcher.queuedHandlersCount() ); + } + + @Test + void shouldFailToInitializeChannelWhenErrorIsReceived() + { + ChannelPromise promise = channel.newPromise(); + + protocol.initializeChannel( "MyDriver/2.2.1", dummyAuthToken(), RoutingContext.EMPTY, promise ); + + assertThat( channel.outboundMessages(), hasSize( 1 ) ); + assertThat( channel.outboundMessages().poll(), instanceOf( HelloMessage.class ) ); + assertEquals( 1, messageDispatcher.queuedHandlersCount() ); + assertFalse( promise.isDone() ); + + messageDispatcher.handleFailureMessage( "Neo.TransientError.General.DatabaseUnavailable", "Error!" ); + + assertTrue( promise.isDone() ); + assertFalse( promise.isSuccess() ); + } + + @Test + void shouldBeginTransactionWithoutBookmark() + { + Connection connection = connectionMock( protocol ); + + CompletionStage stage = protocol.beginTransaction( connection, InternalBookmark.empty(), TransactionConfig.empty() ); + + verify( connection ) + .write( new BeginMessage( InternalBookmark.empty(), TransactionConfig.empty(), defaultDatabase(), WRITE ), NoOpResponseHandler.INSTANCE ); + assertNull( await( stage ) ); + } + + @Test + void shouldBeginTransactionWithBookmarks() + { + Connection connection = connectionMock( protocol ); + Bookmark bookmark = InternalBookmark.parse( "neo4j:bookmark:v1:tx100" ); + + CompletionStage stage = protocol.beginTransaction( connection, bookmark, TransactionConfig.empty() ); + + verify( connection ) + .writeAndFlush( eq( new BeginMessage( bookmark, TransactionConfig.empty(), defaultDatabase(), WRITE ) ), any( BeginTxResponseHandler.class ) ); + assertNull( await( stage ) ); + } + + @Test + void shouldBeginTransactionWithConfig() + { + Connection connection = connectionMock( protocol ); + + CompletionStage stage = protocol.beginTransaction( connection, InternalBookmark.empty(), txConfig ); + + verify( connection ).write( new BeginMessage( InternalBookmark.empty(), txConfig, defaultDatabase(), WRITE ), NoOpResponseHandler.INSTANCE ); + assertNull( await( stage ) ); + } + + @Test + void shouldBeginTransactionWithBookmarksAndConfig() + { + Connection connection = connectionMock( protocol ); + Bookmark bookmark = InternalBookmark.parse( "neo4j:bookmark:v1:tx4242" ); + + CompletionStage stage = protocol.beginTransaction( connection, bookmark, txConfig ); + + verify( connection ).writeAndFlush( eq( new BeginMessage( bookmark, txConfig, defaultDatabase(), WRITE ) ), any( BeginTxResponseHandler.class ) ); + assertNull( await( stage ) ); + } + + @Test + void shouldCommitTransaction() + { + String bookmarkString = "neo4j:bookmark:v1:tx4242"; + + Connection connection = connectionMock( protocol ); + when( connection.protocol() ).thenReturn( protocol ); + doAnswer( invocation -> + { + ResponseHandler commitHandler = invocation.getArgument( 1 ); + commitHandler.onSuccess( singletonMap( "bookmark", value( bookmarkString ) ) ); + return null; + } ).when( connection ).writeAndFlush( eq( CommitMessage.COMMIT ), any() ); + + CompletionStage stage = protocol.commitTransaction( connection ); + + verify( connection ).writeAndFlush( eq( CommitMessage.COMMIT ), any( CommitTxResponseHandler.class ) ); + assertEquals( InternalBookmark.parse( bookmarkString ), await( stage ) ); + } + + @Test + void shouldRollbackTransaction() + { + Connection connection = connectionMock( protocol ); + + CompletionStage stage = protocol.rollbackTransaction( connection ); + + verify( connection ).writeAndFlush( eq( RollbackMessage.ROLLBACK ), any( RollbackTxResponseHandler.class ) ); + assertNull( await( stage ) ); + } + + @ParameterizedTest + @EnumSource( AccessMode.class ) + void shouldRunInAutoCommitTransactionWithoutWaitingForRunResponse( AccessMode mode ) throws Exception + { + testRunWithoutWaitingForRunResponse( true, TransactionConfig.empty(), mode ); + } + + @ParameterizedTest + @EnumSource( AccessMode.class ) + void shouldRunInAutoCommitWithConfigTransactionWithoutWaitingForRunResponse( AccessMode mode ) throws Exception + { + testRunWithoutWaitingForRunResponse( true, txConfig, mode ); + } + + @ParameterizedTest + @EnumSource( AccessMode.class ) + void shouldRunInAutoCommitTransactionAndWaitForSuccessRunResponse( AccessMode mode ) throws Exception + { + testSuccessfulRunInAutoCommitTxWithWaitingForResponse( InternalBookmark.empty(), TransactionConfig.empty(), mode ); + } + + @ParameterizedTest + @EnumSource( AccessMode.class ) + void shouldRunInAutoCommitTransactionWithBookmarkAndConfigAndWaitForSuccessRunResponse( AccessMode mode ) throws Exception + { + testSuccessfulRunInAutoCommitTxWithWaitingForResponse( InternalBookmark.parse( "neo4j:bookmark:v1:tx65" ), txConfig, mode ); + } + + @ParameterizedTest + @EnumSource( AccessMode.class ) + void shouldRunInAutoCommitTransactionAndWaitForFailureRunResponse( AccessMode mode ) throws Exception + { + testFailedRunInAutoCommitTxWithWaitingForResponse( InternalBookmark.empty(), TransactionConfig.empty(), mode ); + } + + @ParameterizedTest + @EnumSource( AccessMode.class ) + void shouldRunInAutoCommitTransactionWithBookmarkAndConfigAndWaitForFailureRunResponse( AccessMode mode ) throws Exception + { + testFailedRunInAutoCommitTxWithWaitingForResponse( InternalBookmark.parse( "neo4j:bookmark:v1:tx163" ), txConfig, mode ); + } + + @ParameterizedTest + @EnumSource( AccessMode.class ) + void shouldRunInUnmanagedTransactionWithoutWaitingForRunResponse( AccessMode mode ) throws Exception + { + testRunWithoutWaitingForRunResponse( false, TransactionConfig.empty(), mode ); + } + + @ParameterizedTest + @EnumSource( AccessMode.class ) + void shouldRunInUnmanagedTransactionAndWaitForSuccessRunResponse( AccessMode mode ) throws Exception + { + testRunInUnmanagedTransactionAndWaitForRunResponse( true, mode ); + } + + @ParameterizedTest + @EnumSource( AccessMode.class ) + void shouldRunInUnmanagedTransactionAndWaitForFailureRunResponse( AccessMode mode ) throws Exception + { + testRunInUnmanagedTransactionAndWaitForRunResponse( false, mode ); + } + + @Test + void databaseNameInBeginTransaction() + { + testDatabaseNameSupport( false ); + } + + @Test + void databaseNameForAutoCommitTransactions() + { + testDatabaseNameSupport( true ); + } + + @Test + void shouldSupportDatabaseNameInBeginTransaction() + { + CompletionStage txStage = protocol.beginTransaction( connectionMock( "foo", protocol ), InternalBookmark.empty(), TransactionConfig.empty() ); + + assertDoesNotThrow( () -> await( txStage ) ); + } + + @Test + void shouldNotSupportDatabaseNameForAutoCommitTransactions() + { + assertDoesNotThrow( + () -> protocol.runInAutoCommitTransaction( connectionMock( "foo", protocol ), + new Query( "RETURN 1" ), BookmarkHolder.NO_OP, TransactionConfig.empty(), true, + UNLIMITED_FETCH_SIZE ) ); + } + + private Class expectedMessageFormatType() + { + return MessageFormatV4.class; + } + + private void testFailedRunInAutoCommitTxWithWaitingForResponse( Bookmark bookmark, TransactionConfig config, AccessMode mode ) throws Exception + { + // Given + Connection connection = connectionMock( mode, protocol ); + BookmarkHolder bookmarkHolder = new DefaultBookmarkHolder( bookmark ); + + CompletableFuture cursorFuture = + protocol.runInAutoCommitTransaction( connection, QUERY, bookmarkHolder, config, true, UNLIMITED_FETCH_SIZE ) + .asyncResult() + .toCompletableFuture(); + + ResponseHandler runHandler = verifySessionRunInvoked( connection, bookmark, config, mode, defaultDatabase() ); + assertFalse( cursorFuture.isDone() ); + + // When I response to Run message with a failure + runHandler.onFailure( new RuntimeException() ); + + // Then + assertEquals( bookmark, bookmarkHolder.getBookmark() ); + assertTrue( cursorFuture.isDone() ); + assertNotNull( cursorFuture.get() ); + } + + private void testSuccessfulRunInAutoCommitTxWithWaitingForResponse( Bookmark bookmark, TransactionConfig config, AccessMode mode ) throws Exception + { + // Given + Connection connection = connectionMock( mode, protocol ); + BookmarkHolder bookmarkHolder = new DefaultBookmarkHolder( bookmark ); + + CompletableFuture cursorFuture = + protocol.runInAutoCommitTransaction( connection, QUERY, bookmarkHolder, config, true, UNLIMITED_FETCH_SIZE ) + .asyncResult() + .toCompletableFuture(); + + ResponseHandler runHandler = verifySessionRunInvoked( connection, bookmark, config, mode, defaultDatabase() ); + assertFalse( cursorFuture.isDone() ); + + // When I response to the run message + runHandler.onSuccess( emptyMap() ); + + // Then + assertEquals( bookmark, bookmarkHolder.getBookmark() ); + assertTrue( cursorFuture.isDone() ); + assertNotNull( cursorFuture.get() ); + } + + private void testRunInUnmanagedTransactionAndWaitForRunResponse( boolean success, AccessMode mode ) throws Exception + { + // Given + Connection connection = connectionMock( mode, protocol ); + + CompletableFuture cursorFuture = + protocol.runInUnmanagedTransaction( connection, QUERY, mock( UnmanagedTransaction.class ), true, UNLIMITED_FETCH_SIZE ) + .asyncResult() + .toCompletableFuture(); + + ResponseHandler runHandler = verifyTxRunInvoked( connection ); + assertFalse( cursorFuture.isDone() ); + + if ( success ) + { + runHandler.onSuccess( emptyMap() ); + } + else + { + // When responded with a failure + runHandler.onFailure( new RuntimeException() ); + } + + // Then + assertTrue( cursorFuture.isDone() ); + assertNotNull( cursorFuture.get() ); + } + + private void testRunWithoutWaitingForRunResponse( boolean autoCommitTx, TransactionConfig config, AccessMode mode ) throws Exception + { + // Given + Connection connection = connectionMock( mode, protocol ); + Bookmark initialBookmark = InternalBookmark.parse( "neo4j:bookmark:v1:tx987" ); + + CompletionStage cursorStage; + if ( autoCommitTx ) + { + BookmarkHolder bookmarkHolder = new DefaultBookmarkHolder( initialBookmark ); + cursorStage = protocol.runInAutoCommitTransaction( connection, QUERY, bookmarkHolder, config, false, UNLIMITED_FETCH_SIZE ) + .asyncResult(); + } + else + { + cursorStage = protocol.runInUnmanagedTransaction( connection, QUERY, mock( UnmanagedTransaction.class ), false, UNLIMITED_FETCH_SIZE ) + .asyncResult(); + } + + // When I complete it immediately without waiting for any responses to run message + CompletableFuture cursorFuture = cursorStage.toCompletableFuture(); + assertTrue( cursorFuture.isDone() ); + assertNotNull( cursorFuture.get() ); + + // Then + if ( autoCommitTx ) + { + verifySessionRunInvoked( connection, initialBookmark, config, mode, defaultDatabase() ); + } + else + { + verifyTxRunInvoked( connection ); + } + } + + private void testDatabaseNameSupport( boolean autoCommitTx ) + { + Connection connection = connectionMock( "foo", protocol ); + if ( autoCommitTx ) + { + ResultCursorFactory factory = + protocol.runInAutoCommitTransaction( connection, QUERY, BookmarkHolder.NO_OP, TransactionConfig.empty(), false, UNLIMITED_FETCH_SIZE ); + await( factory.asyncResult() ); + verifySessionRunInvoked( connection, InternalBookmark.empty(), TransactionConfig.empty(), AccessMode.WRITE, database( "foo" ) ); + } + else + { + CompletionStage txStage = protocol.beginTransaction( connection, InternalBookmark.empty(), TransactionConfig.empty() ); + await( txStage ); + verifyBeginInvoked( connection, InternalBookmark.empty(), TransactionConfig.empty(), AccessMode.WRITE, database( "foo" ) ); + } + } + + private ResponseHandler verifyTxRunInvoked( Connection connection ) + { + return verifyRunInvoked( connection, RunWithMetadataMessage.unmanagedTxRunMessage( QUERY ) ); + } + + private ResponseHandler verifySessionRunInvoked( Connection connection, Bookmark bookmark, TransactionConfig config, AccessMode mode, + DatabaseName databaseName ) + { + RunWithMetadataMessage runMessage = RunWithMetadataMessage.autoCommitTxRunMessage( QUERY, config, databaseName, mode, bookmark ); + return verifyRunInvoked( connection, runMessage ); + } + + private ResponseHandler verifyRunInvoked( Connection connection, RunWithMetadataMessage runMessage ) + { + ArgumentCaptor runHandlerCaptor = ArgumentCaptor.forClass( ResponseHandler.class ); + ArgumentCaptor pullHandlerCaptor = ArgumentCaptor.forClass( ResponseHandler.class ); + + verify( connection ).write( eq( runMessage ), runHandlerCaptor.capture() ); + verify( connection ).writeAndFlush( any( PullMessage.class ), pullHandlerCaptor.capture() ); + + assertThat( runHandlerCaptor.getValue(), instanceOf( RunResponseHandler.class ) ); + assertThat( pullHandlerCaptor.getValue(), instanceOf( PullAllResponseHandler.class ) ); + + return runHandlerCaptor.getValue(); + } + + private void verifyBeginInvoked( Connection connection, Bookmark bookmark, TransactionConfig config, AccessMode mode, DatabaseName databaseName ) + { + ArgumentCaptor beginHandlerCaptor = ArgumentCaptor.forClass( ResponseHandler.class ); + BeginMessage beginMessage = new BeginMessage( bookmark, config, databaseName, mode ); + + if ( bookmark.isEmpty() ) + { + verify( connection ).write( eq( beginMessage ), eq( NoOpResponseHandler.INSTANCE ) ); + } + else + { + verify( connection ).write( eq( beginMessage ), beginHandlerCaptor.capture() ); + assertThat( beginHandlerCaptor.getValue(), instanceOf( BeginTxResponseHandler.class ) ); + } + } + + private static InternalAuthToken dummyAuthToken() + { + return (InternalAuthToken) AuthTokens.basic( "hello", "world" ); + } } + diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v41/MessageFormatV41Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v41/MessageFormatV41Test.java new file mode 100644 index 0000000000..0fcd85bb3e --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v41/MessageFormatV41Test.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2002-2020 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.messaging.v41; + +import org.junit.jupiter.api.Test; + +import org.neo4j.driver.internal.messaging.MessageFormat; +import org.neo4j.driver.internal.messaging.common.CommonMessageReader; +import org.neo4j.driver.internal.messaging.v3.BoltProtocolV3; +import org.neo4j.driver.internal.messaging.v4.MessageWriterV4; +import org.neo4j.driver.internal.packstream.PackInput; +import org.neo4j.driver.internal.packstream.PackOutput; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; +import static org.mockito.Mockito.mock; + +/** + * The MessageFormat under tests is the one provided by the {@link BoltProtocolV3} and not an specific class implementation. + *

+ * It's done on this way to make easy to replace the implementation and still getting the same behaviour. + */ +class MessageFormatV41Test +{ + private static final MessageFormat format = BoltProtocolV41.INSTANCE.createMessageFormat(); + + @Test + void shouldCreateCorrectWriter() + { + MessageFormat.Writer writer = format.newWriter( mock( PackOutput.class ) ); + + assertThat( writer, instanceOf( MessageWriterV4.class ) ); + } + + @Test + void shouldCreateCorrectReader() + { + MessageFormat.Reader reader = format.newReader( mock( PackInput.class ) ); + + assertThat( reader, instanceOf( CommonMessageReader.class ) ); + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v41/MessageReaderV41Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v41/MessageReaderV41Test.java new file mode 100644 index 0000000000..914e70a5e0 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v41/MessageReaderV41Test.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2002-2020 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.messaging.v41; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.HashMap; +import java.util.stream.Stream; + +import org.neo4j.driver.Value; +import org.neo4j.driver.Values; +import org.neo4j.driver.internal.InternalPoint2D; +import org.neo4j.driver.internal.InternalPoint3D; +import org.neo4j.driver.internal.messaging.Message; +import org.neo4j.driver.internal.messaging.MessageFormat; +import org.neo4j.driver.internal.messaging.request.DiscardAllMessage; +import org.neo4j.driver.internal.messaging.request.RunMessage; +import org.neo4j.driver.internal.messaging.response.FailureMessage; +import org.neo4j.driver.internal.messaging.response.IgnoredMessage; +import org.neo4j.driver.internal.messaging.response.RecordMessage; +import org.neo4j.driver.internal.messaging.response.SuccessMessage; +import org.neo4j.driver.internal.packstream.PackInput; +import org.neo4j.driver.internal.util.messaging.AbstractMessageReaderTestBase; + +import static java.util.Arrays.asList; +import static java.util.Calendar.APRIL; +import static java.util.Calendar.AUGUST; +import static org.neo4j.driver.Values.parameters; +import static org.neo4j.driver.Values.value; +import static org.neo4j.driver.internal.util.ValueFactory.emptyNodeValue; +import static org.neo4j.driver.internal.util.ValueFactory.emptyPathValue; +import static org.neo4j.driver.internal.util.ValueFactory.emptyRelationshipValue; +import static org.neo4j.driver.internal.util.ValueFactory.filledNodeValue; +import static org.neo4j.driver.internal.util.ValueFactory.filledPathValue; +import static org.neo4j.driver.internal.util.ValueFactory.filledRelationshipValue; + +/** + * The MessageReader under tests is the one provided by the {@link BoltProtocolV41} and not an specific class implementation. + *

+ * It's done on this way to make easy to replace the implementation and still getting the same behaviour. + */ +public class MessageReaderV41Test extends AbstractMessageReaderTestBase +{ + + @Override + protected Stream supportedMessages() + { + return Stream.of( + // V2 Record types + record( value( new InternalPoint2D( 42, 120.65, -99.2 ) ) ), + record( value( new InternalPoint3D( 42, 85.391, 98.8, 11.1 ) ) ), + record( value( LocalDate.of( 2012, AUGUST, 3 ) ) ), + record( value( OffsetTime.of( 23, 59, 59, 999, ZoneOffset.MAX ) ) ), + record( value( LocalTime.of( 12, 25 ) ) ), + record( value( LocalDateTime.of( 1999, APRIL, 3, 19, 5, 5, 100_200_300 ) ) ), + record( value( ZonedDateTime.of( 1823, 1, 12, 23, 59, 59, 999_999_999, ZoneOffset.ofHoursMinutes( -7, -15 ) ) ) ), + record( value( ZonedDateTime.of( 1823, 1, 12, 23, 59, 59, 999_999_999, ZoneId.of( "Europe/Stockholm" ) ) ) ), + record( value( Values.isoDuration( Long.MAX_VALUE - 1, Integer.MAX_VALUE - 1, Short.MAX_VALUE - 1, Byte.MAX_VALUE - 1 ).asIsoDuration() ) ), + record( value( Values.isoDuration( 17, 22, 99, 15 ).asIsoDuration() ) ), + + // Bolt previous versions valid messages + new FailureMessage( "Hello", "World!" ), + IgnoredMessage.IGNORED, + new SuccessMessage( new HashMap<>() ), + record( value( 1337L ) ), + record( value( parameters( "cat", null, "dog", null ) ) ), + record( value( parameters( "k", 12, "a", "banana" ) ) ), + record( value( asList( "k", 12, "a", "banana" ) ) ), + + // V3 Record Types + record( emptyNodeValue() ), + record( filledNodeValue() ), + record( emptyRelationshipValue() ), + record( filledRelationshipValue() ), + record( filledPathValue() ), + record( emptyPathValue() ) + ); + } + + @Override + protected Stream unsupportedMessages() + { + return Stream.of( + DiscardAllMessage.DISCARD_ALL, + new RunMessage( "RETURN 42" ) + ); + } + + @Override + protected MessageFormat.Reader newReader( PackInput input ) + { + return BoltProtocolV41.INSTANCE.createMessageFormat().newReader( input ); + } + + private Message record( Value value ) + { + return new RecordMessage( new Value[]{value} ); + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v41/MessageWriterV41Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v41/MessageWriterV41Test.java new file mode 100644 index 0000000000..dbe6e58a74 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v41/MessageWriterV41Test.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2002-2020 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.messaging.v41; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.Collections; +import java.util.stream.Stream; + +import org.neo4j.driver.Query; +import org.neo4j.driver.internal.InternalBookmark; +import org.neo4j.driver.internal.messaging.Message; +import org.neo4j.driver.internal.messaging.MessageFormat; +import org.neo4j.driver.internal.messaging.request.BeginMessage; +import org.neo4j.driver.internal.messaging.request.DiscardMessage; +import org.neo4j.driver.internal.messaging.request.HelloMessage; +import org.neo4j.driver.internal.messaging.request.InitMessage; +import org.neo4j.driver.internal.messaging.request.PullMessage; +import org.neo4j.driver.internal.messaging.request.RunMessage; +import org.neo4j.driver.internal.packstream.PackOutput; +import org.neo4j.driver.internal.security.InternalAuthToken; +import org.neo4j.driver.internal.util.messaging.AbstractMessageWriterTestBase; + +import static java.time.Duration.ofSeconds; +import static java.util.Calendar.DECEMBER; +import static java.util.Collections.emptyMap; +import static java.util.Collections.singletonMap; +import static org.neo4j.driver.AccessMode.READ; +import static org.neo4j.driver.AccessMode.WRITE; +import static org.neo4j.driver.AuthTokens.basic; +import static org.neo4j.driver.Values.point; +import static org.neo4j.driver.Values.value; +import static org.neo4j.driver.internal.DatabaseNameUtil.database; +import static org.neo4j.driver.internal.DatabaseNameUtil.defaultDatabase; +import static org.neo4j.driver.internal.messaging.request.CommitMessage.COMMIT; +import static org.neo4j.driver.internal.messaging.request.DiscardAllMessage.DISCARD_ALL; +import static org.neo4j.driver.internal.messaging.request.GoodbyeMessage.GOODBYE; +import static org.neo4j.driver.internal.messaging.request.PullAllMessage.PULL_ALL; +import static org.neo4j.driver.internal.messaging.request.ResetMessage.RESET; +import static org.neo4j.driver.internal.messaging.request.RollbackMessage.ROLLBACK; +import static org.neo4j.driver.internal.messaging.request.RunWithMetadataMessage.autoCommitTxRunMessage; +import static org.neo4j.driver.internal.messaging.request.RunWithMetadataMessage.unmanagedTxRunMessage; + +/** + * The MessageWriter under tests is the one provided by the {@link BoltProtocolV41} and not an specific class implementation. + *

+ * It's done on this way to make easy to replace the implementation and still getting the same behaviour. + */ +class MessageWriterV41Test extends AbstractMessageWriterTestBase +{ + @Override + protected MessageFormat.Writer newWriter( PackOutput output ) + { + return BoltProtocolV41.INSTANCE.createMessageFormat().newWriter( output ); + } + + @Override + protected Stream supportedMessages() + { + return Stream.of( + // Bolt V2 Data Types + unmanagedTxRunMessage( new Query( "RETURN $point", singletonMap( "point", point( 42, 12.99, -180.0 ) ) ) ), + unmanagedTxRunMessage( new Query( "RETURN $point", singletonMap( "point", point( 42, 0.51, 2.99, 100.123 ) ) ) ), + unmanagedTxRunMessage( new Query( "RETURN $date", singletonMap( "date", value( LocalDate.ofEpochDay( 2147483650L ) ) ) ) ), + unmanagedTxRunMessage( new Query( "RETURN $time", singletonMap( "time", value( OffsetTime.of( 4, 16, 20, 999, ZoneOffset.MIN ) ) ) ) ), + unmanagedTxRunMessage( new Query( "RETURN $time", singletonMap( "time", value( LocalTime.of( 12, 9, 18, 999_888 ) ) ) ) ), + unmanagedTxRunMessage( + new Query( "RETURN $dateTime", singletonMap( "dateTime", value( LocalDateTime.of( 2049, DECEMBER, 12, 17, 25, 49, 199 ) ) ) ) ), + unmanagedTxRunMessage( new Query( "RETURN $dateTime", singletonMap( "dateTime", value( ZonedDateTime.of( 2000, 1, 10, 12, 2, 49, 300, ZoneOffset + .ofHoursMinutes( 9, 30 ) ) ) ) ) ), + unmanagedTxRunMessage( new Query( "RETURN $dateTime", singletonMap( "dateTime", value( ZonedDateTime.of( 2000, 1, 10, 12, 2, 49, 300, ZoneId.of( + "Europe/Stockholm" ) ) ) ) ) ), + + // New Bolt V4 messages + new PullMessage( 100, 200 ), + new DiscardMessage( 300, 400 ), + + // Bolt V3 messages + new HelloMessage( "MyDriver/1.2.3", ((InternalAuthToken) basic( "neo4j", "neo4j" )).toMap(), Collections.emptyMap() ), + GOODBYE, + new BeginMessage( InternalBookmark.parse( "neo4j:bookmark:v1:tx123" ), ofSeconds( 5 ), singletonMap( "key", value( 42 ) ), READ, + defaultDatabase() ), + new BeginMessage( InternalBookmark.parse( "neo4j:bookmark:v1:tx123" ), ofSeconds( 5 ), singletonMap( "key", value( 42 ) ), WRITE, + database( "foo" ) ), + COMMIT, + ROLLBACK, + + RESET, + autoCommitTxRunMessage( new Query( "RETURN 1" ), ofSeconds( 5 ), singletonMap( "key", value( 42 ) ), defaultDatabase(), READ, + InternalBookmark.parse( "neo4j:bookmark:v1:tx1" ) ), + autoCommitTxRunMessage( new Query( "RETURN 1" ), ofSeconds( 5 ), singletonMap( "key", value( 42 ) ), database( "foo" ), WRITE, + InternalBookmark.parse( "neo4j:bookmark:v1:tx1" ) ), + unmanagedTxRunMessage( new Query( "RETURN 1" ) ), + + // Bolt V3 messages with struct values + autoCommitTxRunMessage( new Query( "RETURN $x", singletonMap( "x", value( ZonedDateTime.now() ) ) ), ofSeconds( 1 ), emptyMap(), + defaultDatabase(), READ, InternalBookmark.empty() ), + autoCommitTxRunMessage( new Query( "RETURN $x", singletonMap( "x", value( ZonedDateTime.now() ) ) ), ofSeconds( 1 ), emptyMap(), + database( "foo" ), + WRITE, InternalBookmark.empty() ), + unmanagedTxRunMessage( new Query( "RETURN $x", singletonMap( "x", point( 42, 1, 2, 3 ) ) ) ) + ); + } + + @Override + protected Stream unsupportedMessages() + { + return Stream.of( + // Bolt V1, V2 and V3 messages + new InitMessage( "Apa", emptyMap() ), + new RunMessage( "RETURN 1" ), + PULL_ALL, + DISCARD_ALL + ); + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v42/BoltProtocolV42Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v42/BoltProtocolV42Test.java index db6892c15d..fd6d73a12e 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/messaging/v42/BoltProtocolV42Test.java +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v42/BoltProtocolV42Test.java @@ -18,14 +18,519 @@ */ package org.neo4j.driver.internal.messaging.v42; +import io.netty.channel.ChannelPromise; +import io.netty.channel.embedded.EmbeddedChannel; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.mockito.ArgumentCaptor; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +import org.neo4j.driver.AccessMode; +import org.neo4j.driver.AuthTokens; +import org.neo4j.driver.Bookmark; +import org.neo4j.driver.Logging; +import org.neo4j.driver.Query; +import org.neo4j.driver.TransactionConfig; +import org.neo4j.driver.Value; +import org.neo4j.driver.internal.BookmarkHolder; +import org.neo4j.driver.internal.DatabaseName; +import org.neo4j.driver.internal.DefaultBookmarkHolder; +import org.neo4j.driver.internal.InternalBookmark; +import org.neo4j.driver.internal.async.UnmanagedTransaction; +import org.neo4j.driver.internal.async.connection.ChannelAttributes; +import org.neo4j.driver.internal.async.inbound.InboundMessageDispatcher; +import org.neo4j.driver.internal.cluster.RoutingContext; +import org.neo4j.driver.internal.cursor.AsyncResultCursor; +import org.neo4j.driver.internal.cursor.ResultCursorFactory; +import org.neo4j.driver.internal.handlers.BeginTxResponseHandler; +import org.neo4j.driver.internal.handlers.CommitTxResponseHandler; +import org.neo4j.driver.internal.handlers.NoOpResponseHandler; +import org.neo4j.driver.internal.handlers.PullAllResponseHandler; +import org.neo4j.driver.internal.handlers.RollbackTxResponseHandler; +import org.neo4j.driver.internal.handlers.RunResponseHandler; import org.neo4j.driver.internal.messaging.BoltProtocol; -import org.neo4j.driver.internal.messaging.v41.BoltProtocolV41Test; +import org.neo4j.driver.internal.messaging.MessageFormat; +import org.neo4j.driver.internal.messaging.request.BeginMessage; +import org.neo4j.driver.internal.messaging.request.CommitMessage; +import org.neo4j.driver.internal.messaging.request.GoodbyeMessage; +import org.neo4j.driver.internal.messaging.request.HelloMessage; +import org.neo4j.driver.internal.messaging.request.PullMessage; +import org.neo4j.driver.internal.messaging.request.RollbackMessage; +import org.neo4j.driver.internal.messaging.request.RunWithMetadataMessage; +import org.neo4j.driver.internal.messaging.v4.MessageFormatV4; +import org.neo4j.driver.internal.security.InternalAuthToken; +import org.neo4j.driver.internal.spi.Connection; +import org.neo4j.driver.internal.spi.ResponseHandler; + +import static java.time.Duration.ofSeconds; +import static java.util.Collections.emptyMap; +import static java.util.Collections.singletonMap; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.junit.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.neo4j.driver.AccessMode.WRITE; +import static org.neo4j.driver.Values.value; +import static org.neo4j.driver.internal.DatabaseNameUtil.database; +import static org.neo4j.driver.internal.DatabaseNameUtil.defaultDatabase; +import static org.neo4j.driver.internal.handlers.pulln.FetchSizeUtil.UNLIMITED_FETCH_SIZE; +import static org.neo4j.driver.util.TestUtil.anyServerVersion; +import static org.neo4j.driver.util.TestUtil.await; +import static org.neo4j.driver.util.TestUtil.connectionMock; -public class BoltProtocolV42Test extends BoltProtocolV41Test +public final class BoltProtocolV42Test { - @Override + protected static final String QUERY_TEXT = "RETURN $x"; + protected static final Map PARAMS = singletonMap( "x", value( 42 ) ); + protected static final Query QUERY = new Query( QUERY_TEXT, value( PARAMS ) ); + + protected final BoltProtocol protocol = createProtocol(); + private final EmbeddedChannel channel = new EmbeddedChannel(); + private final InboundMessageDispatcher messageDispatcher = new InboundMessageDispatcher( channel, Logging.none() ); + + private final TransactionConfig txConfig = TransactionConfig.builder() + .withTimeout( ofSeconds( 12 ) ) + .withMetadata( singletonMap( "key", value( 42 ) ) ) + .build(); + protected BoltProtocol createProtocol() { return BoltProtocolV42.INSTANCE; } + + @BeforeEach + void beforeEach() + { + ChannelAttributes.setMessageDispatcher( channel, messageDispatcher ); + } + + @AfterEach + void afterEach() + { + channel.finishAndReleaseAll(); + } + + @Test + void shouldCreateMessageFormat() + { + assertThat( protocol.createMessageFormat(), instanceOf( expectedMessageFormatType() ) ); + } + + @Test + void shouldInitializeChannel() + { + ChannelPromise promise = channel.newPromise(); + + protocol.initializeChannel( "MyDriver/0.0.1", dummyAuthToken(), RoutingContext.EMPTY, promise ); + + assertThat( channel.outboundMessages(), hasSize( 1 ) ); + assertThat( channel.outboundMessages().poll(), instanceOf( HelloMessage.class ) ); + assertEquals( 1, messageDispatcher.queuedHandlersCount() ); + assertFalse( promise.isDone() ); + + Map metadata = new HashMap<>(); + metadata.put( "server", value( anyServerVersion().toString() ) ); + metadata.put( "connection_id", value( "bolt-42" ) ); + + messageDispatcher.handleSuccessMessage( metadata ); + + assertTrue( promise.isDone() ); + assertTrue( promise.isSuccess() ); + } + + @Test + void shouldPrepareToCloseChannel() + { + protocol.prepareToCloseChannel( channel ); + + assertThat( channel.outboundMessages(), hasSize( 1 ) ); + assertThat( channel.outboundMessages().poll(), instanceOf( GoodbyeMessage.class ) ); + assertEquals( 1, messageDispatcher.queuedHandlersCount() ); + } + + @Test + void shouldFailToInitializeChannelWhenErrorIsReceived() + { + ChannelPromise promise = channel.newPromise(); + + protocol.initializeChannel( "MyDriver/2.2.1", dummyAuthToken(), RoutingContext.EMPTY, promise ); + + assertThat( channel.outboundMessages(), hasSize( 1 ) ); + assertThat( channel.outboundMessages().poll(), instanceOf( HelloMessage.class ) ); + assertEquals( 1, messageDispatcher.queuedHandlersCount() ); + assertFalse( promise.isDone() ); + + messageDispatcher.handleFailureMessage( "Neo.TransientError.General.DatabaseUnavailable", "Error!" ); + + assertTrue( promise.isDone() ); + assertFalse( promise.isSuccess() ); + } + + @Test + void shouldBeginTransactionWithoutBookmark() + { + Connection connection = connectionMock( protocol ); + + CompletionStage stage = protocol.beginTransaction( connection, InternalBookmark.empty(), TransactionConfig.empty() ); + + verify( connection ) + .write( new BeginMessage( InternalBookmark.empty(), TransactionConfig.empty(), defaultDatabase(), WRITE ), NoOpResponseHandler.INSTANCE ); + assertNull( await( stage ) ); + } + + @Test + void shouldBeginTransactionWithBookmarks() + { + Connection connection = connectionMock( protocol ); + Bookmark bookmark = InternalBookmark.parse( "neo4j:bookmark:v1:tx100" ); + + CompletionStage stage = protocol.beginTransaction( connection, bookmark, TransactionConfig.empty() ); + + verify( connection ) + .writeAndFlush( eq( new BeginMessage( bookmark, TransactionConfig.empty(), defaultDatabase(), WRITE ) ), any( BeginTxResponseHandler.class ) ); + assertNull( await( stage ) ); + } + + @Test + void shouldBeginTransactionWithConfig() + { + Connection connection = connectionMock( protocol ); + + CompletionStage stage = protocol.beginTransaction( connection, InternalBookmark.empty(), txConfig ); + + verify( connection ).write( new BeginMessage( InternalBookmark.empty(), txConfig, defaultDatabase(), WRITE ), NoOpResponseHandler.INSTANCE ); + assertNull( await( stage ) ); + } + + @Test + void shouldBeginTransactionWithBookmarksAndConfig() + { + Connection connection = connectionMock( protocol ); + Bookmark bookmark = InternalBookmark.parse( "neo4j:bookmark:v1:tx4242" ); + + CompletionStage stage = protocol.beginTransaction( connection, bookmark, txConfig ); + + verify( connection ).writeAndFlush( eq( new BeginMessage( bookmark, txConfig, defaultDatabase(), WRITE ) ), any( BeginTxResponseHandler.class ) ); + assertNull( await( stage ) ); + } + + @Test + void shouldCommitTransaction() + { + String bookmarkString = "neo4j:bookmark:v1:tx4242"; + + Connection connection = connectionMock( protocol ); + when( connection.protocol() ).thenReturn( protocol ); + doAnswer( invocation -> + { + ResponseHandler commitHandler = invocation.getArgument( 1 ); + commitHandler.onSuccess( singletonMap( "bookmark", value( bookmarkString ) ) ); + return null; + } ).when( connection ).writeAndFlush( eq( CommitMessage.COMMIT ), any() ); + + CompletionStage stage = protocol.commitTransaction( connection ); + + verify( connection ).writeAndFlush( eq( CommitMessage.COMMIT ), any( CommitTxResponseHandler.class ) ); + assertEquals( InternalBookmark.parse( bookmarkString ), await( stage ) ); + } + + @Test + void shouldRollbackTransaction() + { + Connection connection = connectionMock( protocol ); + + CompletionStage stage = protocol.rollbackTransaction( connection ); + + verify( connection ).writeAndFlush( eq( RollbackMessage.ROLLBACK ), any( RollbackTxResponseHandler.class ) ); + assertNull( await( stage ) ); + } + + @ParameterizedTest + @EnumSource( AccessMode.class ) + void shouldRunInAutoCommitTransactionWithoutWaitingForRunResponse( AccessMode mode ) throws Exception + { + testRunWithoutWaitingForRunResponse( true, TransactionConfig.empty(), mode ); + } + + @ParameterizedTest + @EnumSource( AccessMode.class ) + void shouldRunInAutoCommitWithConfigTransactionWithoutWaitingForRunResponse( AccessMode mode ) throws Exception + { + testRunWithoutWaitingForRunResponse( true, txConfig, mode ); + } + + @ParameterizedTest + @EnumSource( AccessMode.class ) + void shouldRunInAutoCommitTransactionAndWaitForSuccessRunResponse( AccessMode mode ) throws Exception + { + testSuccessfulRunInAutoCommitTxWithWaitingForResponse( InternalBookmark.empty(), TransactionConfig.empty(), mode ); + } + + @ParameterizedTest + @EnumSource( AccessMode.class ) + void shouldRunInAutoCommitTransactionWithBookmarkAndConfigAndWaitForSuccessRunResponse( AccessMode mode ) throws Exception + { + testSuccessfulRunInAutoCommitTxWithWaitingForResponse( InternalBookmark.parse( "neo4j:bookmark:v1:tx65" ), txConfig, mode ); + } + + @ParameterizedTest + @EnumSource( AccessMode.class ) + void shouldRunInAutoCommitTransactionAndWaitForFailureRunResponse( AccessMode mode ) throws Exception + { + testFailedRunInAutoCommitTxWithWaitingForResponse( InternalBookmark.empty(), TransactionConfig.empty(), mode ); + } + + @ParameterizedTest + @EnumSource( AccessMode.class ) + void shouldRunInAutoCommitTransactionWithBookmarkAndConfigAndWaitForFailureRunResponse( AccessMode mode ) throws Exception + { + testFailedRunInAutoCommitTxWithWaitingForResponse( InternalBookmark.parse( "neo4j:bookmark:v1:tx163" ), txConfig, mode ); + } + + @ParameterizedTest + @EnumSource( AccessMode.class ) + void shouldRunInUnmanagedTransactionWithoutWaitingForRunResponse( AccessMode mode ) throws Exception + { + testRunWithoutWaitingForRunResponse( false, TransactionConfig.empty(), mode ); + } + + @ParameterizedTest + @EnumSource( AccessMode.class ) + void shouldRunInUnmanagedTransactionAndWaitForSuccessRunResponse( AccessMode mode ) throws Exception + { + testRunInUnmanagedTransactionAndWaitForRunResponse( true, mode ); + } + + @ParameterizedTest + @EnumSource( AccessMode.class ) + void shouldRunInUnmanagedTransactionAndWaitForFailureRunResponse( AccessMode mode ) throws Exception + { + testRunInUnmanagedTransactionAndWaitForRunResponse( false, mode ); + } + + @Test + void databaseNameInBeginTransaction() + { + testDatabaseNameSupport( false ); + } + + @Test + void databaseNameForAutoCommitTransactions() + { + testDatabaseNameSupport( true ); + } + + @Test + void shouldSupportDatabaseNameInBeginTransaction() + { + CompletionStage txStage = protocol.beginTransaction( connectionMock( "foo", protocol ), InternalBookmark.empty(), TransactionConfig.empty() ); + + assertDoesNotThrow( () -> await( txStage ) ); + } + + @Test + void shouldNotSupportDatabaseNameForAutoCommitTransactions() + { + assertDoesNotThrow( + () -> protocol.runInAutoCommitTransaction( connectionMock( "foo", protocol ), + new Query( "RETURN 1" ), BookmarkHolder.NO_OP, TransactionConfig.empty(), true, + UNLIMITED_FETCH_SIZE ) ); + } + + private Class expectedMessageFormatType() + { + return MessageFormatV4.class; + } + + private void testFailedRunInAutoCommitTxWithWaitingForResponse( Bookmark bookmark, TransactionConfig config, AccessMode mode ) throws Exception + { + // Given + Connection connection = connectionMock( mode, protocol ); + BookmarkHolder bookmarkHolder = new DefaultBookmarkHolder( bookmark ); + + CompletableFuture cursorFuture = + protocol.runInAutoCommitTransaction( connection, QUERY, bookmarkHolder, config, true, UNLIMITED_FETCH_SIZE ) + .asyncResult() + .toCompletableFuture(); + + ResponseHandler runHandler = verifySessionRunInvoked( connection, bookmark, config, mode, defaultDatabase() ); + assertFalse( cursorFuture.isDone() ); + + // When I response to Run message with a failure + runHandler.onFailure( new RuntimeException() ); + + // Then + assertEquals( bookmark, bookmarkHolder.getBookmark() ); + assertTrue( cursorFuture.isDone() ); + assertNotNull( cursorFuture.get() ); + } + + private void testSuccessfulRunInAutoCommitTxWithWaitingForResponse( Bookmark bookmark, TransactionConfig config, AccessMode mode ) throws Exception + { + // Given + Connection connection = connectionMock( mode, protocol ); + BookmarkHolder bookmarkHolder = new DefaultBookmarkHolder( bookmark ); + + CompletableFuture cursorFuture = + protocol.runInAutoCommitTransaction( connection, QUERY, bookmarkHolder, config, true, UNLIMITED_FETCH_SIZE ) + .asyncResult() + .toCompletableFuture(); + + ResponseHandler runHandler = verifySessionRunInvoked( connection, bookmark, config, mode, defaultDatabase() ); + assertFalse( cursorFuture.isDone() ); + + // When I response to the run message + runHandler.onSuccess( emptyMap() ); + + // Then + assertEquals( bookmark, bookmarkHolder.getBookmark() ); + assertTrue( cursorFuture.isDone() ); + assertNotNull( cursorFuture.get() ); + } + + private void testRunInUnmanagedTransactionAndWaitForRunResponse( boolean success, AccessMode mode ) throws Exception + { + // Given + Connection connection = connectionMock( mode, protocol ); + + CompletableFuture cursorFuture = + protocol.runInUnmanagedTransaction( connection, QUERY, mock( UnmanagedTransaction.class ), true, UNLIMITED_FETCH_SIZE ) + .asyncResult() + .toCompletableFuture(); + + ResponseHandler runHandler = verifyTxRunInvoked( connection ); + assertFalse( cursorFuture.isDone() ); + + if ( success ) + { + runHandler.onSuccess( emptyMap() ); + } + else + { + // When responded with a failure + runHandler.onFailure( new RuntimeException() ); + } + + // Then + assertTrue( cursorFuture.isDone() ); + assertNotNull( cursorFuture.get() ); + } + + private void testRunWithoutWaitingForRunResponse( boolean autoCommitTx, TransactionConfig config, AccessMode mode ) throws Exception + { + // Given + Connection connection = connectionMock( mode, protocol ); + Bookmark initialBookmark = InternalBookmark.parse( "neo4j:bookmark:v1:tx987" ); + + CompletionStage cursorStage; + if ( autoCommitTx ) + { + BookmarkHolder bookmarkHolder = new DefaultBookmarkHolder( initialBookmark ); + cursorStage = protocol.runInAutoCommitTransaction( connection, QUERY, bookmarkHolder, config, false, UNLIMITED_FETCH_SIZE ) + .asyncResult(); + } + else + { + cursorStage = protocol.runInUnmanagedTransaction( connection, QUERY, mock( UnmanagedTransaction.class ), false, UNLIMITED_FETCH_SIZE ) + .asyncResult(); + } + + // When I complete it immediately without waiting for any responses to run message + CompletableFuture cursorFuture = cursorStage.toCompletableFuture(); + assertTrue( cursorFuture.isDone() ); + assertNotNull( cursorFuture.get() ); + + // Then + if ( autoCommitTx ) + { + verifySessionRunInvoked( connection, initialBookmark, config, mode, defaultDatabase() ); + } + else + { + verifyTxRunInvoked( connection ); + } + } + + private void testDatabaseNameSupport( boolean autoCommitTx ) + { + Connection connection = connectionMock( "foo", protocol ); + if ( autoCommitTx ) + { + ResultCursorFactory factory = + protocol.runInAutoCommitTransaction( connection, QUERY, BookmarkHolder.NO_OP, TransactionConfig.empty(), false, UNLIMITED_FETCH_SIZE ); + await( factory.asyncResult() ); + verifySessionRunInvoked( connection, InternalBookmark.empty(), TransactionConfig.empty(), AccessMode.WRITE, database( "foo" ) ); + } + else + { + CompletionStage txStage = protocol.beginTransaction( connection, InternalBookmark.empty(), TransactionConfig.empty() ); + await( txStage ); + verifyBeginInvoked( connection, InternalBookmark.empty(), TransactionConfig.empty(), AccessMode.WRITE, database( "foo" ) ); + } + } + + private ResponseHandler verifyTxRunInvoked( Connection connection ) + { + return verifyRunInvoked( connection, RunWithMetadataMessage.unmanagedTxRunMessage( QUERY ) ); + } + + private ResponseHandler verifySessionRunInvoked( Connection connection, Bookmark bookmark, TransactionConfig config, AccessMode mode, + DatabaseName databaseName ) + { + RunWithMetadataMessage runMessage = RunWithMetadataMessage.autoCommitTxRunMessage( QUERY, config, databaseName, mode, bookmark ); + return verifyRunInvoked( connection, runMessage ); + } + + private ResponseHandler verifyRunInvoked( Connection connection, RunWithMetadataMessage runMessage ) + { + ArgumentCaptor runHandlerCaptor = ArgumentCaptor.forClass( ResponseHandler.class ); + ArgumentCaptor pullHandlerCaptor = ArgumentCaptor.forClass( ResponseHandler.class ); + + verify( connection ).write( eq( runMessage ), runHandlerCaptor.capture() ); + verify( connection ).writeAndFlush( any( PullMessage.class ), pullHandlerCaptor.capture() ); + + assertThat( runHandlerCaptor.getValue(), instanceOf( RunResponseHandler.class ) ); + assertThat( pullHandlerCaptor.getValue(), instanceOf( PullAllResponseHandler.class ) ); + + return runHandlerCaptor.getValue(); + } + + private void verifyBeginInvoked( Connection connection, Bookmark bookmark, TransactionConfig config, AccessMode mode, DatabaseName databaseName ) + { + ArgumentCaptor beginHandlerCaptor = ArgumentCaptor.forClass( ResponseHandler.class ); + BeginMessage beginMessage = new BeginMessage( bookmark, config, databaseName, mode ); + + if ( bookmark.isEmpty() ) + { + verify( connection ).write( eq( beginMessage ), eq( NoOpResponseHandler.INSTANCE ) ); + } + else + { + verify( connection ).write( eq( beginMessage ), beginHandlerCaptor.capture() ); + assertThat( beginHandlerCaptor.getValue(), instanceOf( BeginTxResponseHandler.class ) ); + } + } + + private static InternalAuthToken dummyAuthToken() + { + return (InternalAuthToken) AuthTokens.basic( "hello", "world" ); + } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v42/MessageFormatV42Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v42/MessageFormatV42Test.java new file mode 100644 index 0000000000..18ef4949c2 --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v42/MessageFormatV42Test.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2002-2020 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.messaging.v42; + +import org.junit.jupiter.api.Test; + +import org.neo4j.driver.internal.messaging.MessageFormat; +import org.neo4j.driver.internal.messaging.common.CommonMessageReader; +import org.neo4j.driver.internal.messaging.v4.MessageWriterV4; +import org.neo4j.driver.internal.packstream.PackInput; +import org.neo4j.driver.internal.packstream.PackOutput; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; +import static org.mockito.Mockito.mock; + +/** + * The MessageFormat under tests is the one provided by the {@link BoltProtocolV42} and not an specific class implementation. + *

+ * It's done on this way to make easy to replace the implementation and still getting the same behaviour. + */ +class MessageFormatV42Test +{ + private static final MessageFormat format = BoltProtocolV42.INSTANCE.createMessageFormat(); + + @Test + void shouldCreateCorrectWriter() + { + MessageFormat.Writer writer = format.newWriter( mock( PackOutput.class ) ); + + assertThat( writer, instanceOf( MessageWriterV4.class ) ); + } + + @Test + void shouldCreateCorrectReader() + { + MessageFormat.Reader reader = format.newReader( mock( PackInput.class ) ); + + assertThat( reader, instanceOf( CommonMessageReader.class ) ); + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v42/MessageReaderV42Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v42/MessageReaderV42Test.java new file mode 100644 index 0000000000..0cfb818c4c --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v42/MessageReaderV42Test.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2002-2020 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.messaging.v42; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.HashMap; +import java.util.stream.Stream; + +import org.neo4j.driver.Value; +import org.neo4j.driver.Values; +import org.neo4j.driver.internal.InternalPoint2D; +import org.neo4j.driver.internal.InternalPoint3D; +import org.neo4j.driver.internal.messaging.Message; +import org.neo4j.driver.internal.messaging.MessageFormat; +import org.neo4j.driver.internal.messaging.request.DiscardAllMessage; +import org.neo4j.driver.internal.messaging.request.RunMessage; +import org.neo4j.driver.internal.messaging.response.FailureMessage; +import org.neo4j.driver.internal.messaging.response.IgnoredMessage; +import org.neo4j.driver.internal.messaging.response.RecordMessage; +import org.neo4j.driver.internal.messaging.response.SuccessMessage; +import org.neo4j.driver.internal.packstream.PackInput; +import org.neo4j.driver.internal.util.messaging.AbstractMessageReaderTestBase; + +import static java.util.Arrays.asList; +import static java.util.Calendar.APRIL; +import static java.util.Calendar.AUGUST; +import static org.neo4j.driver.Values.parameters; +import static org.neo4j.driver.Values.value; +import static org.neo4j.driver.internal.util.ValueFactory.emptyNodeValue; +import static org.neo4j.driver.internal.util.ValueFactory.emptyPathValue; +import static org.neo4j.driver.internal.util.ValueFactory.emptyRelationshipValue; +import static org.neo4j.driver.internal.util.ValueFactory.filledNodeValue; +import static org.neo4j.driver.internal.util.ValueFactory.filledPathValue; +import static org.neo4j.driver.internal.util.ValueFactory.filledRelationshipValue; + +/** + * The MessageReader under tests is the one provided by the {@link BoltProtocolV42} and not an specific class implementation. + *

+ * It's done on this way to make easy to replace the implementation and still getting the same behaviour. + */ +public class MessageReaderV42Test extends AbstractMessageReaderTestBase +{ + + @Override + protected Stream supportedMessages() + { + return Stream.of( + // V2 Record types + record( value( new InternalPoint2D( 42, 120.65, -99.2 ) ) ), + record( value( new InternalPoint3D( 42, 85.391, 98.8, 11.1 ) ) ), + record( value( LocalDate.of( 2012, AUGUST, 3 ) ) ), + record( value( OffsetTime.of( 23, 59, 59, 999, ZoneOffset.MAX ) ) ), + record( value( LocalTime.of( 12, 25 ) ) ), + record( value( LocalDateTime.of( 1999, APRIL, 3, 19, 5, 5, 100_200_300 ) ) ), + record( value( ZonedDateTime.of( 1823, 1, 12, 23, 59, 59, 999_999_999, ZoneOffset.ofHoursMinutes( -7, -15 ) ) ) ), + record( value( ZonedDateTime.of( 1823, 1, 12, 23, 59, 59, 999_999_999, ZoneId.of( "Europe/Stockholm" ) ) ) ), + record( value( Values.isoDuration( Long.MAX_VALUE - 1, Integer.MAX_VALUE - 1, Short.MAX_VALUE - 1, Byte.MAX_VALUE - 1 ).asIsoDuration() ) ), + record( value( Values.isoDuration( 17, 22, 99, 15 ).asIsoDuration() ) ), + + // Bolt previous versions valid messages + new FailureMessage( "Hello", "World!" ), + IgnoredMessage.IGNORED, + new SuccessMessage( new HashMap<>() ), + record( value( 1337L ) ), + record( value( parameters( "cat", null, "dog", null ) ) ), + record( value( parameters( "k", 12, "a", "banana" ) ) ), + record( value( asList( "k", 12, "a", "banana" ) ) ), + + // V3 Record Types + record( emptyNodeValue() ), + record( filledNodeValue() ), + record( emptyRelationshipValue() ), + record( filledRelationshipValue() ), + record( filledPathValue() ), + record( emptyPathValue() ) + ); + } + + @Override + protected Stream unsupportedMessages() + { + return Stream.of( + DiscardAllMessage.DISCARD_ALL, + new RunMessage( "RETURN 42" ) + ); + } + + @Override + protected MessageFormat.Reader newReader( PackInput input ) + { + return BoltProtocolV42.INSTANCE.createMessageFormat().newReader( input ); + } + + private Message record( Value value ) + { + return new RecordMessage( new Value[]{value} ); + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/messaging/v42/MessageWriterV42Test.java b/driver/src/test/java/org/neo4j/driver/internal/messaging/v42/MessageWriterV42Test.java new file mode 100644 index 0000000000..02030ebd6a --- /dev/null +++ b/driver/src/test/java/org/neo4j/driver/internal/messaging/v42/MessageWriterV42Test.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2002-2020 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.neo4j.driver.internal.messaging.v42; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.Collections; +import java.util.stream.Stream; + +import org.neo4j.driver.Query; +import org.neo4j.driver.internal.InternalBookmark; +import org.neo4j.driver.internal.messaging.Message; +import org.neo4j.driver.internal.messaging.MessageFormat; +import org.neo4j.driver.internal.messaging.request.BeginMessage; +import org.neo4j.driver.internal.messaging.request.DiscardMessage; +import org.neo4j.driver.internal.messaging.request.HelloMessage; +import org.neo4j.driver.internal.messaging.request.InitMessage; +import org.neo4j.driver.internal.messaging.request.PullMessage; +import org.neo4j.driver.internal.messaging.request.RunMessage; +import org.neo4j.driver.internal.packstream.PackOutput; +import org.neo4j.driver.internal.security.InternalAuthToken; +import org.neo4j.driver.internal.util.messaging.AbstractMessageWriterTestBase; + +import static java.time.Duration.ofSeconds; +import static java.util.Calendar.DECEMBER; +import static java.util.Collections.emptyMap; +import static java.util.Collections.singletonMap; +import static org.neo4j.driver.AccessMode.READ; +import static org.neo4j.driver.AccessMode.WRITE; +import static org.neo4j.driver.AuthTokens.basic; +import static org.neo4j.driver.Values.point; +import static org.neo4j.driver.Values.value; +import static org.neo4j.driver.internal.DatabaseNameUtil.database; +import static org.neo4j.driver.internal.DatabaseNameUtil.defaultDatabase; +import static org.neo4j.driver.internal.messaging.request.CommitMessage.COMMIT; +import static org.neo4j.driver.internal.messaging.request.DiscardAllMessage.DISCARD_ALL; +import static org.neo4j.driver.internal.messaging.request.GoodbyeMessage.GOODBYE; +import static org.neo4j.driver.internal.messaging.request.PullAllMessage.PULL_ALL; +import static org.neo4j.driver.internal.messaging.request.ResetMessage.RESET; +import static org.neo4j.driver.internal.messaging.request.RollbackMessage.ROLLBACK; +import static org.neo4j.driver.internal.messaging.request.RunWithMetadataMessage.autoCommitTxRunMessage; +import static org.neo4j.driver.internal.messaging.request.RunWithMetadataMessage.unmanagedTxRunMessage; + +/** + * The MessageWriter under tests is the one provided by the {@link BoltProtocolV42} and not an specific class implementation. + *

+ * It's done on this way to make easy to replace the implementation and still getting the same behaviour. + */ +class MessageWriterV42Test extends AbstractMessageWriterTestBase +{ + @Override + protected MessageFormat.Writer newWriter( PackOutput output ) + { + return BoltProtocolV42.INSTANCE.createMessageFormat().newWriter( output ); + } + + @Override + protected Stream supportedMessages() + { + return Stream.of( + // Bolt V2 Data Types + unmanagedTxRunMessage( new Query( "RETURN $point", singletonMap( "point", point( 42, 12.99, -180.0 ) ) ) ), + unmanagedTxRunMessage( new Query( "RETURN $point", singletonMap( "point", point( 42, 0.51, 2.99, 100.123 ) ) ) ), + unmanagedTxRunMessage( new Query( "RETURN $date", singletonMap( "date", value( LocalDate.ofEpochDay( 2147483650L ) ) ) ) ), + unmanagedTxRunMessage( new Query( "RETURN $time", singletonMap( "time", value( OffsetTime.of( 4, 16, 20, 999, ZoneOffset.MIN ) ) ) ) ), + unmanagedTxRunMessage( new Query( "RETURN $time", singletonMap( "time", value( LocalTime.of( 12, 9, 18, 999_888 ) ) ) ) ), + unmanagedTxRunMessage( + new Query( "RETURN $dateTime", singletonMap( "dateTime", value( LocalDateTime.of( 2049, DECEMBER, 12, 17, 25, 49, 199 ) ) ) ) ), + unmanagedTxRunMessage( new Query( "RETURN $dateTime", singletonMap( "dateTime", value( ZonedDateTime.of( 2000, 1, 10, 12, 2, 49, 300, ZoneOffset + .ofHoursMinutes( 9, 30 ) ) ) ) ) ), + unmanagedTxRunMessage( new Query( "RETURN $dateTime", singletonMap( "dateTime", value( ZonedDateTime.of( 2000, 1, 10, 12, 2, 49, 300, ZoneId.of( + "Europe/Stockholm" ) ) ) ) ) ), + + // New Bolt V4 messages + new PullMessage( 100, 200 ), + new DiscardMessage( 300, 400 ), + + // Bolt V3 messages + new HelloMessage( "MyDriver/1.2.3", ((InternalAuthToken) basic( "neo4j", "neo4j" )).toMap(), Collections.emptyMap() ), + GOODBYE, + new BeginMessage( InternalBookmark.parse( "neo4j:bookmark:v1:tx123" ), ofSeconds( 5 ), singletonMap( "key", value( 42 ) ), READ, + defaultDatabase() ), + new BeginMessage( InternalBookmark.parse( "neo4j:bookmark:v1:tx123" ), ofSeconds( 5 ), singletonMap( "key", value( 42 ) ), WRITE, + database( "foo" ) ), + COMMIT, + ROLLBACK, + + RESET, + autoCommitTxRunMessage( new Query( "RETURN 1" ), ofSeconds( 5 ), singletonMap( "key", value( 42 ) ), defaultDatabase(), READ, + InternalBookmark.parse( "neo4j:bookmark:v1:tx1" ) ), + autoCommitTxRunMessage( new Query( "RETURN 1" ), ofSeconds( 5 ), singletonMap( "key", value( 42 ) ), database( "foo" ), WRITE, + InternalBookmark.parse( "neo4j:bookmark:v1:tx1" ) ), + unmanagedTxRunMessage( new Query( "RETURN 1" ) ), + + // Bolt V3 messages with struct values + autoCommitTxRunMessage( new Query( "RETURN $x", singletonMap( "x", value( ZonedDateTime.now() ) ) ), ofSeconds( 1 ), emptyMap(), + defaultDatabase(), READ, InternalBookmark.empty() ), + autoCommitTxRunMessage( new Query( "RETURN $x", singletonMap( "x", value( ZonedDateTime.now() ) ) ), ofSeconds( 1 ), emptyMap(), + database( "foo" ), + WRITE, InternalBookmark.empty() ), + unmanagedTxRunMessage( new Query( "RETURN $x", singletonMap( "x", point( 42, 1, 2, 3 ) ) ) ) + ); + } + + @Override + protected Stream unsupportedMessages() + { + return Stream.of( + // Bolt V1, V2 and V3 messages + new InitMessage( "Apa", emptyMap() ), + new RunMessage( "RETURN 1" ), + PULL_ALL, + DISCARD_ALL + ); + } +} diff --git a/driver/src/test/java/org/neo4j/driver/internal/util/messaging/AbstractMessageReaderTestBase.java b/driver/src/test/java/org/neo4j/driver/internal/util/messaging/AbstractMessageReaderTestBase.java index 9e134d8a96..5325bd2957 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/util/messaging/AbstractMessageReaderTestBase.java +++ b/driver/src/test/java/org/neo4j/driver/internal/util/messaging/AbstractMessageReaderTestBase.java @@ -20,75 +20,82 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.DynamicNode; +import org.junit.jupiter.api.TestFactory; import java.io.IOException; -import java.util.Map; +import java.util.stream.Stream; import org.neo4j.driver.internal.async.inbound.ByteBufInput; import org.neo4j.driver.internal.messaging.Message; import org.neo4j.driver.internal.messaging.MessageFormat; import org.neo4j.driver.internal.messaging.ResponseMessageHandler; -import org.neo4j.driver.internal.messaging.request.DiscardAllMessage; -import org.neo4j.driver.internal.messaging.request.RunMessage; import org.neo4j.driver.internal.messaging.response.FailureMessage; import org.neo4j.driver.internal.messaging.response.IgnoredMessage; import org.neo4j.driver.internal.messaging.response.RecordMessage; import org.neo4j.driver.internal.messaging.response.SuccessMessage; import org.neo4j.driver.internal.packstream.PackInput; import org.neo4j.driver.internal.util.io.ByteBufOutput; -import org.neo4j.driver.Value; -import static java.util.Collections.singletonMap; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.DynamicTest.dynamicTest; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import static org.neo4j.driver.Values.value; public abstract class AbstractMessageReaderTestBase { - @Test - void shouldReadSuccessMessage() throws Exception + @TestFactory + Stream shouldReadSupportedMessages() { - Map metadata = singletonMap( "hello", value( "world" ) ); - - ResponseMessageHandler handler = testMessageReading( new SuccessMessage( metadata ) ); - - verify( handler ).handleSuccessMessage( metadata ); + return supportedMessages().map( message -> + dynamicTest( message.toString(), () -> testSupportedMessageReading( message ) ) ); } - @Test - void shouldReadFailureMessage() throws Exception + private void testSupportedMessageReading( Message message ) throws IOException { - ResponseMessageHandler handler = testMessageReading( new FailureMessage( "Hello", "World" ) ); - - verify( handler ).handleFailureMessage( "Hello", "World" ); + ResponseMessageHandler handler = testMessageReading( message ); + + if ( message instanceof SuccessMessage ) + { + SuccessMessage successMessage = (SuccessMessage) message; + verify( handler ).handleSuccessMessage( successMessage.metadata() ); + } + else if ( message instanceof FailureMessage ) + { + FailureMessage failureMessage = (FailureMessage) message; + verify( handler ).handleFailureMessage( failureMessage.code(), failureMessage.message() ); + } + else if ( message instanceof IgnoredMessage ) + { + verify( handler ).handleIgnoredMessage(); + } + else if ( message instanceof RecordMessage ) + { + RecordMessage recordMessage = (RecordMessage) message; + verify( handler ).handleRecordMessage( recordMessage.fields() ); + } + else + { + fail( "Unsupported message type " + message.getClass().getSimpleName() ); + } } - @Test - void shouldReadIgnoredMessage() throws Exception + @TestFactory + Stream shouldFailToReadUnsupportedMessages() { - ResponseMessageHandler handler = testMessageReading( IgnoredMessage.IGNORED ); - - verify( handler ).handleIgnoredMessage(); + return unsupportedMessages().map( message -> + dynamicTest( message.toString(), () -> testUnsupportedMessageReading( message ) ) ); } - @Test - void shouldReadRecordMessage() throws Exception + private void testUnsupportedMessageReading( Message message ) throws IOException { - Value[] fields = {value( 1 ), value( 2 ), value( "42" )}; - - ResponseMessageHandler handler = testMessageReading( new RecordMessage( fields ) ); - - verify( handler ).handleRecordMessage( fields ); + assertThrows( IOException.class, () -> testMessageReading( message ) ); } - @Test - void shouldFailToReadUnknownMessage() - { - assertThrows( IOException.class, () -> testMessageReading( DiscardAllMessage.DISCARD_ALL ) ); - assertThrows( IOException.class, () -> testMessageReading( new RunMessage( "RETURN 42" ) ) ); - } + protected abstract Stream supportedMessages(); + + protected abstract Stream unsupportedMessages(); protected abstract MessageFormat.Reader newReader( PackInput input ); diff --git a/driver/src/test/java/org/neo4j/driver/internal/util/messaging/KnowledgeableMessageFormat.java b/driver/src/test/java/org/neo4j/driver/internal/util/messaging/KnowledgeableMessageFormat.java index 57ff8d1c57..55cf576679 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/util/messaging/KnowledgeableMessageFormat.java +++ b/driver/src/test/java/org/neo4j/driver/internal/util/messaging/KnowledgeableMessageFormat.java @@ -23,6 +23,8 @@ import org.neo4j.driver.internal.messaging.AbstractMessageWriter; import org.neo4j.driver.internal.messaging.MessageEncoder; +import org.neo4j.driver.internal.messaging.common.CommonValuePacker; +import org.neo4j.driver.internal.messaging.common.CommonValueUnpacker; import org.neo4j.driver.internal.messaging.encode.DiscardAllMessageEncoder; import org.neo4j.driver.internal.messaging.encode.InitMessageEncoder; import org.neo4j.driver.internal.messaging.encode.PullAllMessageEncoder; @@ -37,8 +39,7 @@ import org.neo4j.driver.internal.messaging.response.IgnoredMessage; import org.neo4j.driver.internal.messaging.response.RecordMessage; import org.neo4j.driver.internal.messaging.response.SuccessMessage; -import org.neo4j.driver.internal.messaging.v2.MessageFormatV2; -import org.neo4j.driver.internal.messaging.v2.ValuePackerV2; +import org.neo4j.driver.internal.messaging.v3.MessageFormatV3; import org.neo4j.driver.internal.packstream.PackOutput; import org.neo4j.driver.internal.types.TypeConstructor; import org.neo4j.driver.internal.util.Iterables; @@ -49,10 +50,10 @@ import org.neo4j.driver.types.Relationship; /** - * This class provides the missing server side packing methods to serialize Node, Relationship and Path. - * It also allows writing of server side messages like SUCCESS, FAILURE, IGNORED and RECORD. + * This class provides the missing server side packing methods to serialize Node, Relationship and Path. It also allows writing of server side messages like + * SUCCESS, FAILURE, IGNORED and RECORD. */ -public class KnowledgeableMessageFormat extends MessageFormatV2 +public class KnowledgeableMessageFormat extends MessageFormatV3 { @Override public Writer newWriter( PackOutput output ) @@ -85,7 +86,7 @@ static Map buildEncoders() } } - private static class KnowledgeableValuePacker extends ValuePackerV2 + private static class KnowledgeableValuePacker extends CommonValuePacker { KnowledgeableValuePacker( PackOutput output ) { @@ -119,7 +120,7 @@ protected void packInternalValue( InternalValue value ) throws IOException private void packPath( Path path ) throws IOException { - packer.packStructHeader( 3, PATH ); + packer.packStructHeader( 3, CommonValueUnpacker.PATH ); // Unique nodes Map nodeIdx = Iterables.newLinkedHashMapWithSize( path.length() + 1 ); @@ -148,7 +149,7 @@ private void packPath( Path path ) throws IOException packer.packListHeader( relIdx.size() ); for ( Relationship rel : relIdx.keySet() ) { - packer.packStructHeader( 3, UNBOUND_RELATIONSHIP ); + packer.packStructHeader( 3, CommonValueUnpacker.UNBOUND_RELATIONSHIP ); packer.pack( rel.id() ); packer.pack( rel.type() ); packProperties( rel ); @@ -169,7 +170,7 @@ private void packPath( Path path ) throws IOException private void packRelationship( Relationship rel ) throws IOException { - packer.packStructHeader( 5, RELATIONSHIP ); + packer.packStructHeader( 5, CommonValueUnpacker.RELATIONSHIP ); packer.pack( rel.id() ); packer.pack( rel.startNodeId() ); packer.pack( rel.endNodeId() ); @@ -181,7 +182,7 @@ private void packRelationship( Relationship rel ) throws IOException private void packNode( Node node ) throws IOException { - packer.packStructHeader( NODE_FIELDS, NODE ); + packer.packStructHeader( CommonValueUnpacker.NODE_FIELDS, CommonValueUnpacker.NODE ); packer.pack( node.id() ); Iterable labels = node.labels(); diff --git a/driver/src/test/java/org/neo4j/driver/util/TestUtil.java b/driver/src/test/java/org/neo4j/driver/util/TestUtil.java index da0e4bf4c6..3620b4f4bd 100644 --- a/driver/src/test/java/org/neo4j/driver/util/TestUtil.java +++ b/driver/src/test/java/org/neo4j/driver/util/TestUtil.java @@ -43,6 +43,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.function.BooleanSupplier; +import java.util.function.Predicate; import org.neo4j.driver.AccessMode; import org.neo4j.driver.Bookmark; @@ -66,8 +67,6 @@ import org.neo4j.driver.internal.messaging.request.RollbackMessage; import org.neo4j.driver.internal.messaging.request.RunMessage; import org.neo4j.driver.internal.messaging.request.RunWithMetadataMessage; -import org.neo4j.driver.internal.messaging.v1.BoltProtocolV1; -import org.neo4j.driver.internal.messaging.v2.BoltProtocolV2; import org.neo4j.driver.internal.messaging.v3.BoltProtocolV3; import org.neo4j.driver.internal.messaging.v4.BoltProtocolV4; import org.neo4j.driver.internal.messaging.v41.BoltProtocolV41; @@ -314,10 +313,10 @@ public static NetworkSession newSession( ConnectionProvider connectionProvider ) } public static NetworkSession newSession( ConnectionProvider connectionProvider, AccessMode mode, - RetryLogic retryLogic, Bookmark bookmark ) + RetryLogic retryLogic, Bookmark bookmark ) { return new NetworkSession( connectionProvider, retryLogic, defaultDatabase(), mode, new DefaultBookmarkHolder( bookmark ), UNLIMITED_FETCH_SIZE, - DEV_NULL_LOGGING ); + DEV_NULL_LOGGING ); } public static void verifyRunRx( Connection connection, String query ) @@ -371,30 +370,29 @@ public static void verifyBeginTx( Connection connectionMock, Bookmark bookmark ) public static void setupFailingRun( Connection connection, Throwable error ) { doAnswer( invocation -> - { - ResponseHandler runHandler = invocation.getArgument( 1 ); - runHandler.onFailure( error ); - return null; - } ).when( connection ).writeAndFlush( any( RunWithMetadataMessage.class ), any() ); + { + ResponseHandler runHandler = invocation.getArgument( 1 ); + runHandler.onFailure( error ); + return null; + } ).when( connection ).writeAndFlush( any( RunWithMetadataMessage.class ), any() ); doAnswer( invocation -> - { - ResponseHandler pullHandler = invocation.getArgument( 1 ); - pullHandler.onFailure( error ); - return null; - } ).when( connection ).writeAndFlush( any( PullMessage.class ), any() ); - + { + ResponseHandler pullHandler = invocation.getArgument( 1 ); + pullHandler.onFailure( error ); + return null; + } ).when( connection ).writeAndFlush( any( PullMessage.class ), any() ); } public static void setupFailingBegin( Connection connection, Throwable error ) { // with bookmarks doAnswer( invocation -> - { - ResponseHandler handler = invocation.getArgument( 1 ); - handler.onFailure( error ); - return null; - } ).when( connection ).writeAndFlush( any( BeginMessage.class ), any( BeginTxResponseHandler.class ) ); + { + ResponseHandler handler = invocation.getArgument( 1 ); + handler.onFailure( error ); + return null; + } ).when( connection ).writeAndFlush( any( BeginMessage.class ), any( BeginTxResponseHandler.class ) ); } public static void setupFailingCommit( Connection connection ) @@ -499,7 +497,7 @@ public static void setupSuccessfulRunAndPull( Connection connection, String quer public static Connection connectionMock() { - return connectionMock( BoltProtocolV2.INSTANCE ); + return connectionMock( BoltProtocolV42.INSTANCE ); } public static Connection connectionMock( BoltProtocol protocol ) @@ -526,14 +524,8 @@ public static Connection connectionMock( String databaseName, AccessMode mode, B when( connection.mode() ).thenReturn( mode ); when( connection.databaseName() ).thenReturn( database( databaseName ) ); BoltProtocolVersion version = protocol.version(); - if ( version.equals( BoltProtocolV1.VERSION ) || version.equals( BoltProtocolV2.VERSION ) ) - { - setupSuccessfulPullAll( connection, "COMMIT" ); - setupSuccessfulPullAll( connection, "ROLLBACK" ); - setupSuccessfulPullAll( connection, "BEGIN" ); - } - else if ( version.equals( BoltProtocolV3.VERSION ) || version.equals( BoltProtocolV4.VERSION ) || - version.equals( BoltProtocolV41.VERSION ) || version.equals( BoltProtocolV42.VERSION )) + if ( version.equals( BoltProtocolV3.VERSION ) || version.equals( BoltProtocolV4.VERSION ) || + version.equals( BoltProtocolV41.VERSION ) || version.equals( BoltProtocolV42.VERSION ) ) { setupSuccessResponse( connection, CommitMessage.class ); setupSuccessResponse( connection, RollbackMessage.class ); @@ -631,16 +623,26 @@ public static String randomString( int size ) return sb.toString(); } - public static ArgumentMatcher runMessageWithQueryMatcher(String query ) + public static ArgumentMatcher runMessageWithQueryMatcher( String query ) { return message -> message instanceof RunMessage && Objects.equals( query, ((RunMessage) message).query() ); } - public static ArgumentMatcher runWithMetaMessageWithQueryMatcher(String query ) + public static ArgumentMatcher runWithMetaMessageWithQueryMatcher( String query ) { return message -> message instanceof RunWithMetadataMessage && Objects.equals( query, ((RunWithMetadataMessage) message).query() ); } + public static ArgumentMatcher beginMessage() + { + return beginMessageWithPredicate( ignored -> true ); + } + + public static ArgumentMatcher beginMessageWithPredicate( Predicate predicate ) + { + return message -> message instanceof BeginMessage && predicate.test( (BeginMessage) message ); + } + /** * Used in tests that expect a server version but the tests do not depend on server version to behave differently. */