diff --git a/testkit-backend/pom.xml b/testkit-backend/pom.xml
index 28d40371fe..df538d1e08 100644
--- a/testkit-backend/pom.xml
+++ b/testkit-backend/pom.xml
@@ -32,6 +32,24 @@
com.fasterxml.jackson.core
jackson-databind
+
+ org.projectlombok
+ lombok
+ 1.18.12
+ provided
+
+
+
+
+ org.hamcrest
+ hamcrest-junit
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter
+ test
+
@@ -49,7 +67,7 @@
- Runner
+ neo4j.org.testkit.backend.Runner
testkit-backend
diff --git a/testkit-backend/src/main/java/CommandProcessor.java b/testkit-backend/src/main/java/CommandProcessor.java
deleted file mode 100644
index a594d1e764..0000000000
--- a/testkit-backend/src/main/java/CommandProcessor.java
+++ /dev/null
@@ -1,347 +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.
- */
-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/TestkitTypes.java b/testkit-backend/src/main/java/TestkitTypes.java
deleted file mode 100644
index 479f3e4069..0000000000
--- a/testkit-backend/src/main/java/TestkitTypes.java
+++ /dev/null
@@ -1,123 +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.
- */
-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");
- }
-}
diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/CommandProcessor.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/CommandProcessor.java
new file mode 100644
index 0000000000..18371081d4
--- /dev/null
+++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/CommandProcessor.java
@@ -0,0 +1,216 @@
+/*
+ * 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 neo4j.org.testkit.backend;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import neo4j.org.testkit.backend.messages.TestkitModule;
+import neo4j.org.testkit.backend.messages.requests.TestkitRequest;
+import neo4j.org.testkit.backend.messages.responses.DriverError;
+import neo4j.org.testkit.backend.messages.responses.TestkitErrorResponse;
+import neo4j.org.testkit.backend.messages.responses.TestkitResponse;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+
+import org.neo4j.driver.exceptions.Neo4jException;
+
+public class CommandProcessor
+{
+ private final TestkitState testkitState;
+
+ 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;
+ configureObjectMapper();
+ this.testkitState = new TestkitState( this::writeResponse, this::process );
+ }
+
+ private void configureObjectMapper()
+ {
+ TestkitModule testkitModule = new TestkitModule();
+ this.objectMapper.registerModule( testkitModule );
+ this.objectMapper.disable( DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES );
+ }
+
+ 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 = testkitState.newId();
+ testkitState.getErrors().put( id, (Neo4jException) e );
+ writeResponse( driverError( 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( TestkitErrorResponse.builder().errorMessage( e.toString() ).build() );
+ // 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 );
+ }
+ }
+ }
+
+ private DriverError driverError( String id )
+ {
+ return DriverError.builder().data( DriverError.DriverErrorBody.builder().id( id ).build() ).build();
+ }
+
+ public void processRequest( String request )
+ {
+ System.out.println( "request = " + request + ", in = " + in + ", out = " + out );
+ try
+ {
+ TestkitRequest testkitMessage = objectMapper.readValue( request, TestkitRequest.class );
+ TestkitResponse response = testkitMessage.process( testkitState );
+ if ( response != null )
+ {
+ writeResponse( response );
+ }
+ }
+ catch ( IOException e )
+ {
+ throw new UncheckedIOException( e );
+ }
+ }
+
+ private void writeResponse( TestkitResponse response )
+ {
+ try
+ {
+ String responseStr = objectMapper.writeValueAsString( response );
+ System.out.println("response = " + responseStr + ", in = " + in + ", out = " + out);
+ write( "#response begin\n" );
+ write( responseStr + "\n" );
+ write( "#response end\n" );
+ flush();
+ }
+ catch ( JsonProcessingException ex )
+ {
+ throw new RuntimeException( "Error writing response", ex );
+ }
+ }
+}
diff --git a/testkit-backend/src/main/java/Runner.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/Runner.java
similarity index 67%
rename from testkit-backend/src/main/java/Runner.java
rename to testkit-backend/src/main/java/neo4j/org/testkit/backend/Runner.java
index f0580e68a4..27b3a3963f 100644
--- a/testkit-backend/src/main/java/Runner.java
+++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/Runner.java
@@ -16,41 +16,60 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package neo4j.org.testkit.backend;
+
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.io.UncheckedIOException;
import java.net.ServerSocket;
import java.net.Socket;
+import java.util.concurrent.CompletableFuture;
public class Runner
{
public static void main( String[] args ) throws IOException
{
- ServerSocket serverSocket = new ServerSocket(9876);
- System.out.println("Starting Java Testkit Backend Started");
+ ServerSocket serverSocket = new ServerSocket( 9876 );
- while (true) {
- System.out.println("Listening on socket");
- Socket clientSocket = serverSocket.accept();
+ System.out.println( "Java TestKit Backend Started on port: " + serverSocket.getLocalPort() );
+ while ( true )
+ {
+ final Socket clientSocket = serverSocket.accept();
+ CompletableFuture.runAsync( () -> handleClient( clientSocket ) );
+ }
+ }
+
+ private static void handleClient( Socket clientSocket )
+ {
+ try
+ {
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);
+ CommandProcessor commandProcessor = new CommandProcessor( in, out );
boolean cont = true;
- while (cont) {
- try {
+ while ( cont )
+ {
+ try
+ {
cont = commandProcessor.process();
- } catch (Exception e) {
+ }
+ catch ( Exception e )
+ {
e.printStackTrace();
clientSocket.close();
cont = false;
}
}
}
+ catch ( IOException ex )
+ {
+ throw new UncheckedIOException( ex );
+ }
}
}
diff --git a/testkit-backend/src/main/java/SessionState.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/SessionState.java
similarity index 91%
rename from testkit-backend/src/main/java/SessionState.java
rename to testkit-backend/src/main/java/neo4j/org/testkit/backend/SessionState.java
index e3487a25d2..8afd8a83a6 100644
--- a/testkit-backend/src/main/java/SessionState.java
+++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/SessionState.java
@@ -16,9 +16,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package neo4j.org.testkit.backend;
+
+import lombok.Getter;
+import lombok.Setter;
import org.neo4j.driver.Session;
+@Getter
+@Setter
public class SessionState
{
public Session session;
diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/TestkitState.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/TestkitState.java
new file mode 100644
index 0000000000..2bb6e98808
--- /dev/null
+++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/TestkitState.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.
+ */
+package neo4j.org.testkit.backend;
+
+import lombok.Getter;
+import neo4j.org.testkit.backend.messages.responses.TestkitResponse;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+import org.neo4j.driver.Driver;
+import org.neo4j.driver.Result;
+import org.neo4j.driver.Transaction;
+import org.neo4j.driver.exceptions.Neo4jException;
+
+@Getter
+public class TestkitState
+{
+ 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 Consumer responseWriter;
+ private final Supplier processor;
+
+ public TestkitState( Consumer responseWriter, Supplier processor )
+ {
+ this.responseWriter = responseWriter;
+ this.processor = processor;
+ }
+
+ public String newId()
+ {
+ return String.valueOf( idGenerator++ );
+ }
+}
diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/TestkitCypherTypeDeserializer.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/TestkitCypherTypeDeserializer.java
new file mode 100644
index 0000000000..a4b725c6cf
--- /dev/null
+++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/TestkitCypherTypeDeserializer.java
@@ -0,0 +1,122 @@
+/*
+ * 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 neo4j.org.testkit.backend.messages;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class TestkitCypherTypeDeserializer extends StdDeserializer