diff --git a/packages/bolt-connection/src/bolt/bolt-protocol-v1.js b/packages/bolt-connection/src/bolt/bolt-protocol-v1.js index f3a7b32a0..24d00c5fa 100644 --- a/packages/bolt-connection/src/bolt/bolt-protocol-v1.js +++ b/packages/bolt-connection/src/bolt/bolt-protocol-v1.js @@ -24,15 +24,16 @@ import { // eslint-disable-next-line no-unused-vars import { Chunker } from '../channel' import { structure, v1 } from '../packstream' -import RequestMessage from './request-message' +import RequestMessage, { SIGNATURES } from './request-message' import { LoginObserver, + LogoffObserver, ResetObserver, ResultStreamObserver, // eslint-disable-next-line no-unused-vars StreamObserver } from './stream-observers' -import { internal } from 'neo4j-driver-core' +import { internal, newError } from 'neo4j-driver-core' import transformersFactories from './bolt-protocol-v1.transformer' import Transformer from './transformer' @@ -99,6 +100,27 @@ export default class BoltProtocol { return BOLT_PROTOCOL_V1 } + /** + * @property {boolean} supportsReAuth Either if the protocol version supports re-auth or not. + */ + get supportsReAuth () { + return false + } + + /** + * @property {boolean} initialized Either if the protocol was initialized or not + */ + get initialized () { + return !!this._initialized + } + + /** + * @property {object} authToken The token used in the last login + */ + get authToken () { + return this._authToken + } + /** * Get the packer. * @return {Packer} the protocol's packer. @@ -162,6 +184,61 @@ export default class BoltProtocol { return observer } + /** + * Performs logoff of the underlying connection + * + * @param {Object} param + * @param {function(err: Error)} param.onError the callback to invoke on error. + * @param {function()} param.onComplete the callback to invoke on completion. + * @param {boolean} param.flush whether to flush the buffered messages. + * + * @returns {StreamObserver} the stream observer that monitors the corresponding server response. + */ + logoff ({ onComplete, onError, flush } = {}) { + const observer = new LogoffObserver({ + onCompleted: onComplete, + onError: onError + }) + + const error = newError( + 'Driver is connected to a database that does not support logoff. ' + + 'Please upgrade to Neo4j 5.5.0 or later in order to use this functionality.' + ) + + // unsupported API was used, consider this a fatal error for the current connection + this._onProtocolError(error.message) + observer.onError(error) + throw error + } + + /** + * Performs login of the underlying connection + * + * @param {Object} args + * @param {Object} args.authToken the authentication token. + * @param {function(err: Error)} args.onError the callback to invoke on error. + * @param {function()} args.onComplete the callback to invoke on completion. + * @param {boolean} args.flush whether to flush the buffered messages. + * + * @returns {StreamObserver} the stream observer that monitors the corresponding server response. + */ + logon ({ authToken, onComplete, onError, flush } = {}) { + const observer = new LoginObserver({ + onCompleted: () => this._onLoginCompleted({}, authToken, onComplete), + onError: (error) => this._onLoginError(error, onError) + }) + + const error = newError( + 'Driver is connected to a database that does not support logon. ' + + 'Please upgrade to Neo4j 5.5.0 or later in order to use this functionality.' + ) + + // unsupported API was used, consider this a fatal error for the current connection + this._onProtocolError(error.message) + observer.onError(error) + throw error + } + /** * Perform protocol related operations for closing this connection */ @@ -391,19 +468,19 @@ export default class BoltProtocol { this.packable(messageStruct)() this._chunker.messageBoundary() - if (flush) { this._chunker.flush() } } } - isLastMessageLogin () { - return this._lastMessageSignature === 0x01 + isLastMessageLogon () { + return this._lastMessageSignature === SIGNATURES.HELLO || + this._lastMessageSignature === SIGNATURES.LOGON } isLastMessageReset () { - return this._lastMessageSignature === 0x0f + return this._lastMessageSignature === SIGNATURES.RESET } /** @@ -472,7 +549,9 @@ export default class BoltProtocol { this._responseHandler._resetFailure() } - _onLoginCompleted (metadata, onCompleted) { + _onLoginCompleted (metadata, authToken, onCompleted) { + this._initialized = true + this._authToken = authToken if (metadata) { const serverVersion = metadata.server if (!this._server.version) { diff --git a/packages/bolt-connection/src/bolt/bolt-protocol-v3.js b/packages/bolt-connection/src/bolt/bolt-protocol-v3.js index 25dd7ad5a..523f6b229 100644 --- a/packages/bolt-connection/src/bolt/bolt-protocol-v3.js +++ b/packages/bolt-connection/src/bolt/bolt-protocol-v3.js @@ -72,7 +72,7 @@ export default class BoltProtocol extends BoltProtocolV2 { initialize ({ userAgent, authToken, onError, onComplete } = {}) { const observer = new LoginObserver({ onError: error => this._onLoginError(error, onError), - onCompleted: metadata => this._onLoginCompleted(metadata, onComplete) + onCompleted: metadata => this._onLoginCompleted(metadata, authToken, onComplete) }) this.write(RequestMessage.hello(userAgent, authToken), observer, true) diff --git a/packages/bolt-connection/src/bolt/bolt-protocol-v4x1.js b/packages/bolt-connection/src/bolt/bolt-protocol-v4x1.js index f8a1de208..7c79f6e35 100644 --- a/packages/bolt-connection/src/bolt/bolt-protocol-v4x1.js +++ b/packages/bolt-connection/src/bolt/bolt-protocol-v4x1.js @@ -75,7 +75,7 @@ export default class BoltProtocol extends BoltProtocolV4 { initialize ({ userAgent, authToken, onError, onComplete } = {}) { const observer = new LoginObserver({ onError: error => this._onLoginError(error, onError), - onCompleted: metadata => this._onLoginCompleted(metadata, onComplete) + onCompleted: metadata => this._onLoginCompleted(metadata, authToken, onComplete) }) this.write( diff --git a/packages/bolt-connection/src/bolt/bolt-protocol-v4x3.js b/packages/bolt-connection/src/bolt/bolt-protocol-v4x3.js index 3ca55e4f4..2efedb1af 100644 --- a/packages/bolt-connection/src/bolt/bolt-protocol-v4x3.js +++ b/packages/bolt-connection/src/bolt/bolt-protocol-v4x3.js @@ -94,7 +94,7 @@ export default class BoltProtocol extends BoltProtocolV42 { if (metadata.patch_bolt !== undefined) { this._applyPatches(metadata.patch_bolt) } - return this._onLoginCompleted(metadata, onComplete) + return this._onLoginCompleted(metadata, authToken, onComplete) } }) diff --git a/packages/bolt-connection/src/bolt/bolt-protocol-v5x0.js b/packages/bolt-connection/src/bolt/bolt-protocol-v5x0.js index 8cfbefa2b..d4834f4b7 100644 --- a/packages/bolt-connection/src/bolt/bolt-protocol-v5x0.js +++ b/packages/bolt-connection/src/bolt/bolt-protocol-v5x0.js @@ -54,7 +54,7 @@ export default class BoltProtocol extends BoltProtocolV44 { initialize ({ userAgent, authToken, onError, onComplete } = {}) { const observer = new LoginObserver({ onError: error => this._onLoginError(error, onError), - onCompleted: metadata => this._onLoginCompleted(metadata, onComplete) + onCompleted: metadata => this._onLoginCompleted(metadata, authToken, onComplete) }) this.write( diff --git a/packages/bolt-connection/src/bolt/bolt-protocol-v5x1.js b/packages/bolt-connection/src/bolt/bolt-protocol-v5x1.js new file mode 100644 index 000000000..1a4861623 --- /dev/null +++ b/packages/bolt-connection/src/bolt/bolt-protocol-v5x1.js @@ -0,0 +1,132 @@ +/** + * Copyright (c) "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 BoltProtocolV5x0 from './bolt-protocol-v5x0' + +import transformersFactories from './bolt-protocol-v5x1.transformer' +import Transformer from './transformer' +import RequestMessage from './request-message' +import { LoginObserver, LogoffObserver } from './stream-observers' + +import { internal } from 'neo4j-driver-core' + +const { + constants: { BOLT_PROTOCOL_V5_1 } +} = internal + +export default class BoltProtocol extends BoltProtocolV5x0 { + get version () { + return BOLT_PROTOCOL_V5_1 + } + + get transformer () { + if (this._transformer === undefined) { + this._transformer = new Transformer(Object.values(transformersFactories).map(create => create(this._config, this._log))) + } + return this._transformer + } + + get supportsReAuth () { + return true + } + + /** + * Initialize a connection with the server + * + * @param {Object} param0 The params + * @param {string} param0.userAgent The user agent + * @param {any} param0.authToken The auth token + * @param {function(error)} param0.onError On error callback + * @param {function(onComplte)} param0.onComplete On complete callback + * @returns {LoginObserver} The Login observer + */ + initialize ({ userAgent, authToken, onError, onComplete } = {}) { + const state = {} + const observer = new LoginObserver({ + onError: error => this._onLoginError(error, onError), + onCompleted: metadata => { + state.metadata = metadata + return this._onLoginCompleted(metadata) + } + }) + + this.write( + RequestMessage.hello5x1(userAgent, this._serversideRouting), + observer, + false + ) + + return this.logon({ + authToken, + onComplete: metadata => onComplete({ ...metadata, ...state.metadata }), + onError, + flush: true + }) + } + + /** + * Performs login of the underlying connection + * + * @param {Object} args + * @param {Object} args.authToken the authentication token. + * @param {function(err: Error)} args.onError the callback to invoke on error. + * @param {function()} args.onComplete the callback to invoke on completion. + * @param {boolean} args.flush whether to flush the buffered messages. + * + * @returns {StreamObserver} the stream observer that monitors the corresponding server response. + */ + logon ({ authToken, onComplete, onError, flush } = {}) { + const observer = new LoginObserver({ + onCompleted: () => this._onLoginCompleted(null, authToken, onComplete), + onError: (error) => this._onLoginError(error, onError) + }) + + this.write( + RequestMessage.logon(authToken), + observer, + flush + ) + + return observer + } + + /** + * Performs logoff of the underlying connection + * + * @param {Object} param + * @param {function(err: Error)} param.onError the callback to invoke on error. + * @param {function()} param.onComplete the callback to invoke on completion. + * @param {boolean} param.flush whether to flush the buffered messages. + * + * @returns {StreamObserver} the stream observer that monitors the corresponding server response. + */ + logoff ({ onComplete, onError, flush } = {}) { + const observer = new LogoffObserver({ + onCompleted: onComplete, + onError: onError + }) + + this.write( + RequestMessage.logoff(), + observer, + flush + ) + + return observer + } +} diff --git a/packages/bolt-connection/src/bolt/bolt-protocol-v5x1.transformer.js b/packages/bolt-connection/src/bolt/bolt-protocol-v5x1.transformer.js new file mode 100644 index 000000000..0923aef4d --- /dev/null +++ b/packages/bolt-connection/src/bolt/bolt-protocol-v5x1.transformer.js @@ -0,0 +1,24 @@ +/** + * Copyright (c) "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 v5x0 from './bolt-protocol-v5x0.transformer' + +export default { + ...v5x0 +} diff --git a/packages/bolt-connection/src/bolt/create.js b/packages/bolt-connection/src/bolt/create.js index f2f4065b3..374266fda 100644 --- a/packages/bolt-connection/src/bolt/create.js +++ b/packages/bolt-connection/src/bolt/create.js @@ -27,6 +27,7 @@ import BoltProtocolV4x2 from './bolt-protocol-v4x2' import BoltProtocolV4x3 from './bolt-protocol-v4x3' import BoltProtocolV4x4 from './bolt-protocol-v4x4' import BoltProtocolV5x0 from './bolt-protocol-v5x0' +import BoltProtocolV5x1 from './bolt-protocol-v5x1' // eslint-disable-next-line no-unused-vars import { Chunker, Dechunker } from '../channel' import ResponseHandler from './response-handler' @@ -191,6 +192,16 @@ function createProtocol ( onProtocolError, serversideRouting ) + case 5.1: + return new BoltProtocolV5x1( + server, + chunker, + packingConfig, + createResponseHandler, + log, + onProtocolError, + serversideRouting + ) default: throw newError('Unknown Bolt protocol version: ' + version) } diff --git a/packages/bolt-connection/src/bolt/handshake.js b/packages/bolt-connection/src/bolt/handshake.js index c41d4bd82..09eb4beff 100644 --- a/packages/bolt-connection/src/bolt/handshake.js +++ b/packages/bolt-connection/src/bolt/handshake.js @@ -76,7 +76,7 @@ function parseNegotiatedResponse (buffer) { */ function newHandshakeBuffer () { return createHandshakeMessage([ - version(5, 0), + [version(5, 1), version(5, 0)], [version(4, 4), version(4, 2)], version(4, 1), version(3, 0) diff --git a/packages/bolt-connection/src/bolt/request-message.js b/packages/bolt-connection/src/bolt/request-message.js index 4a525daa7..446a7828d 100644 --- a/packages/bolt-connection/src/bolt/request-message.js +++ b/packages/bolt-connection/src/bolt/request-message.js @@ -40,6 +40,9 @@ const COMMIT = 0x12 // 0001 0010 // COMMIT const ROLLBACK = 0x13 // 0001 0011 // ROLLBACK const ROUTE = 0x66 // 0110 0110 // ROUTE +const LOGON = 0x6A // LOGON +const LOGOFF = 0x6B // LOGOFF + const DISCARD = 0x2f // 0010 1111 // DISCARD const PULL = 0x3f // 0011 1111 // PULL @@ -48,6 +51,23 @@ const READ_MODE = 'r' const NO_STATEMENT_ID = -1 +const SIGNATURES = Object.freeze({ + INIT, + RESET, + RUN, + PULL_ALL, + HELLO, + GOODBYE, + BEGIN, + COMMIT, + ROLLBACK, + ROUTE, + LOGON, + LOGOFF, + DISCARD, + PULL +}) + export default class RequestMessage { constructor (signature, fields, toString) { this.signature = signature @@ -121,6 +141,52 @@ export default class RequestMessage { ) } + /** + * Create a new HELLO message. + * @param {string} userAgent the user agent. + * @param {Object} optional server side routing, set to routing context to turn on server side routing (> 4.1) + * @return {RequestMessage} new HELLO message. + */ + static hello5x1 (userAgent, routing = null) { + const metadata = { user_agent: userAgent } + if (routing) { + metadata.routing = routing + } + + return new RequestMessage( + HELLO, + [metadata], + () => `HELLO {user_agent: '${userAgent}', ...}` + ) + } + + /** + * Create a new LOGON message. + * + * @param {object} authToken The auth token + * @returns {RequestMessage} new LOGON message + */ + static logon (authToken) { + return new RequestMessage( + LOGON, + [authToken], + () => 'LOGON { ... }' + ) + } + + /** + * Create a new LOGOFF message. + * + * @returns {RequestMessage} new LOGOFF message + */ + static logoff () { + return new RequestMessage( + LOGOFF, + [], + () => 'LOGOFF' + ) + } + /** * Create a new BEGIN message. * @param {Bookmarks} bookmarks the bookmarks. @@ -327,3 +393,7 @@ const RESET_MESSAGE = new RequestMessage(RESET, [], () => 'RESET') const COMMIT_MESSAGE = new RequestMessage(COMMIT, [], () => 'COMMIT') const ROLLBACK_MESSAGE = new RequestMessage(ROLLBACK, [], () => 'ROLLBACK') const GOODBYE_MESSAGE = new RequestMessage(GOODBYE, [], () => 'GOODBYE') + +export { + SIGNATURES +} diff --git a/packages/bolt-connection/src/bolt/stream-observers.js b/packages/bolt-connection/src/bolt/stream-observers.js index 4ad0ce445..04a243e4b 100644 --- a/packages/bolt-connection/src/bolt/stream-observers.js +++ b/packages/bolt-connection/src/bolt/stream-observers.js @@ -451,6 +451,38 @@ class LoginObserver extends StreamObserver { } } +class LogoffObserver extends StreamObserver { + /** + * + * @param {Object} param - + * @param {function(err: Error)} param.onError + * @param {function(metadata)} param.onCompleted + */ + constructor ({ onError, onCompleted } = {}) { + super() + this._onError = onError + this._onCompleted = onCompleted + } + + onNext (record) { + this.onError( + newError('Received RECORD when logging off ' + json.stringify(record)) + ) + } + + onError (error) { + if (this._onError) { + this._onError(error) + } + } + + onCompleted (metadata) { + if (this._onCompleted) { + this._onCompleted(metadata) + } + } +} + class ResetObserver extends StreamObserver { /** * @@ -671,6 +703,7 @@ export { StreamObserver, ResultStreamObserver, LoginObserver, + LogoffObserver, ResetObserver, FailedObserver, CompletedObserver, diff --git a/packages/bolt-connection/src/connection-provider/connection-provider-pooled.js b/packages/bolt-connection/src/connection-provider/connection-provider-pooled.js index 42cb30fbc..f6424d5ba 100644 --- a/packages/bolt-connection/src/connection-provider/connection-provider-pooled.js +++ b/packages/bolt-connection/src/connection-provider/connection-provider-pooled.js @@ -121,7 +121,7 @@ export default class PooledConnectionProvider extends ConnectionProvider { const connection = await this._connectionPool.acquire(address) const serverInfo = new ServerInfo(connection.server, connection.protocol().version) try { - if (!connection.protocol().isLastMessageLogin()) { + if (!connection.protocol().isLastMessageLogon()) { await connection.resetAndFlush() } } finally { diff --git a/packages/bolt-connection/test/bolt/__snapshots__/bolt-protocol-v5x1.test.js.snap b/packages/bolt-connection/test/bolt/__snapshots__/bolt-protocol-v5x1.test.js.snap new file mode 100644 index 000000000..8f5271141 --- /dev/null +++ b/packages/bolt-connection/test/bolt/__snapshots__/bolt-protocol-v5x1.test.js.snap @@ -0,0 +1,61 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`#unit BoltProtocolV5x1 .packable() should pack not pack graph types (Node) 1`] = `"It is not allowed to pass nodes in query parameters, given: (c:a {a:\\"b\\"})"`; + +exports[`#unit BoltProtocolV5x1 .packable() should pack not pack graph types (Path) 1`] = `"It is not allowed to pass paths in query parameters, given: [object Object]"`; + +exports[`#unit BoltProtocolV5x1 .packable() should pack not pack graph types (Relationship) 1`] = `"It is not allowed to pass relationships in query parameters, given: (e)-[:a {b:\\"c\\"}]->(f)"`; + +exports[`#unit BoltProtocolV5x1 .packable() should pack not pack graph types (UnboundRelationship) 1`] = `"It is not allowed to pass unbound relationships in query parameters, given: -[:a {b:\\"c\\"}]->"`; + +exports[`#unit BoltProtocolV5x1 .unpack() should not unpack with wrong size (Date with less fields) 1`] = `"Wrong struct size for Date, expected 1 but was 0"`; + +exports[`#unit BoltProtocolV5x1 .unpack() should not unpack with wrong size (Date with more fields) 1`] = `"Wrong struct size for Date, expected 1 but was 2"`; + +exports[`#unit BoltProtocolV5x1 .unpack() should not unpack with wrong size (DateTimeWithZoneId with less fields) 1`] = `"Wrong struct size for DateTimeWithZoneId, expected 3 but was 2"`; + +exports[`#unit BoltProtocolV5x1 .unpack() should not unpack with wrong size (DateTimeWithZoneId with more fields) 1`] = `"Wrong struct size for DateTimeWithZoneId, expected 3 but was 4"`; + +exports[`#unit BoltProtocolV5x1 .unpack() should not unpack with wrong size (DateTimeWithZoneOffset with less fields) 1`] = `"Wrong struct size for DateTimeWithZoneOffset, expected 3 but was 2"`; + +exports[`#unit BoltProtocolV5x1 .unpack() should not unpack with wrong size (DateTimeWithZoneOffset with more fields) 1`] = `"Wrong struct size for DateTimeWithZoneOffset, expected 3 but was 4"`; + +exports[`#unit BoltProtocolV5x1 .unpack() should not unpack with wrong size (Duration with less fields) 1`] = `"Wrong struct size for Duration, expected 4 but was 3"`; + +exports[`#unit BoltProtocolV5x1 .unpack() should not unpack with wrong size (Duration with more fields) 1`] = `"Wrong struct size for Duration, expected 4 but was 5"`; + +exports[`#unit BoltProtocolV5x1 .unpack() should not unpack with wrong size (LocalDateTime with less fields) 1`] = `"Wrong struct size for LocalDateTime, expected 2 but was 1"`; + +exports[`#unit BoltProtocolV5x1 .unpack() should not unpack with wrong size (LocalDateTime with more fields) 1`] = `"Wrong struct size for LocalDateTime, expected 2 but was 3"`; + +exports[`#unit BoltProtocolV5x1 .unpack() should not unpack with wrong size (LocalTime with less fields) 1`] = `"Wrong struct size for LocalTime, expected 1 but was 0"`; + +exports[`#unit BoltProtocolV5x1 .unpack() should not unpack with wrong size (LocalTime with more fields) 1`] = `"Wrong struct size for LocalTime, expected 1 but was 2"`; + +exports[`#unit BoltProtocolV5x1 .unpack() should not unpack with wrong size (Node with less fields) 1`] = `"Wrong struct size for Node, expected 4 but was 3"`; + +exports[`#unit BoltProtocolV5x1 .unpack() should not unpack with wrong size (Node with more fields) 1`] = `"Wrong struct size for Node, expected 4 but was 5"`; + +exports[`#unit BoltProtocolV5x1 .unpack() should not unpack with wrong size (Path with less fields) 1`] = `"Wrong struct size for Path, expected 3 but was 2"`; + +exports[`#unit BoltProtocolV5x1 .unpack() should not unpack with wrong size (Path with more fields) 1`] = `"Wrong struct size for Path, expected 3 but was 4"`; + +exports[`#unit BoltProtocolV5x1 .unpack() should not unpack with wrong size (Point with less fields) 1`] = `"Wrong struct size for Point2D, expected 3 but was 2"`; + +exports[`#unit BoltProtocolV5x1 .unpack() should not unpack with wrong size (Point with more fields) 1`] = `"Wrong struct size for Point2D, expected 3 but was 4"`; + +exports[`#unit BoltProtocolV5x1 .unpack() should not unpack with wrong size (Point3D with less fields) 1`] = `"Wrong struct size for Point3D, expected 4 but was 3"`; + +exports[`#unit BoltProtocolV5x1 .unpack() should not unpack with wrong size (Point3D with more fields) 1`] = `"Wrong struct size for Point3D, expected 4 but was 5"`; + +exports[`#unit BoltProtocolV5x1 .unpack() should not unpack with wrong size (Relationship with less fields) 1`] = `"Wrong struct size for Relationship, expected 8 but was 5"`; + +exports[`#unit BoltProtocolV5x1 .unpack() should not unpack with wrong size (Relationship with more fields) 1`] = `"Wrong struct size for Relationship, expected 8 but was 9"`; + +exports[`#unit BoltProtocolV5x1 .unpack() should not unpack with wrong size (Time with less fields) 1`] = `"Wrong struct size for Time, expected 2 but was 1"`; + +exports[`#unit BoltProtocolV5x1 .unpack() should not unpack with wrong size (Time with more fileds) 1`] = `"Wrong struct size for Time, expected 2 but was 3"`; + +exports[`#unit BoltProtocolV5x1 .unpack() should not unpack with wrong size (UnboundRelationship with less fields) 1`] = `"Wrong struct size for UnboundRelationship, expected 4 but was 3"`; + +exports[`#unit BoltProtocolV5x1 .unpack() should not unpack with wrong size (UnboundRelationship with more fields) 1`] = `"Wrong struct size for UnboundRelationship, expected 4 but was 5"`; diff --git a/packages/bolt-connection/test/bolt/bolt-protocol-v1.test.js b/packages/bolt-connection/test/bolt/bolt-protocol-v1.test.js index 9d69a2909..1ec6c840e 100644 --- a/packages/bolt-connection/test/bolt/bolt-protocol-v1.test.js +++ b/packages/bolt-connection/test/bolt/bolt-protocol-v1.test.js @@ -600,4 +600,39 @@ describe('#unit BoltProtocolV1', () => { expect(unpacked).toEqual(struct) }) }) + + describe('Bolt v5.1', () => { + it('should not support logoff', () => { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV1(recorder, null, false) + + expect(protocol.supportsReAuth).toBe(false) + }) + + it('should thrown when logoff is called', () => { + verifyMethodNotSupportedError( + protocol => protocol.logoff(), + 'Driver is connected to a database that does not support logoff. ' + + 'Please upgrade to Neo4j 5.5.0 or later in order to use this functionality.') + }) + + it('should thrown when logon is called', () => { + verifyMethodNotSupportedError( + protocol => protocol.logon(), + 'Driver is connected to a database that does not support logon. ' + + 'Please upgrade to Neo4j 5.5.0 or later in order to use this functionality.') + }) + + /** + * @param {string} impersonatedUser The impersonated user. + * @param {function(protocol)} fn + */ + function verifyMethodNotSupportedError (fn, message) { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV1(recorder, null, false, + () => null, null, () => {}) + + expect(() => fn(protocol)).toThrowError(message) + } + }) }) diff --git a/packages/bolt-connection/test/bolt/bolt-protocol-v2.test.js b/packages/bolt-connection/test/bolt/bolt-protocol-v2.test.js index a9d1f1a50..70843b424 100644 --- a/packages/bolt-connection/test/bolt/bolt-protocol-v2.test.js +++ b/packages/bolt-connection/test/bolt/bolt-protocol-v2.test.js @@ -466,4 +466,39 @@ describe('#unit BoltProtocolV2', () => { expect(unpacked).toEqual(object) }) }) + + describe('Bolt v5.1', () => { + it('should not support logoff', () => { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV2(recorder, null, false) + + expect(protocol.supportsReAuth).toBe(false) + }) + + it('should thrown when logoff is called', () => { + verifyMethodNotSupportedError( + protocol => protocol.logoff(), + 'Driver is connected to a database that does not support logoff. ' + + 'Please upgrade to Neo4j 5.5.0 or later in order to use this functionality.') + }) + + it('should thrown when logon is called', () => { + verifyMethodNotSupportedError( + protocol => protocol.logon(), + 'Driver is connected to a database that does not support logon. ' + + 'Please upgrade to Neo4j 5.5.0 or later in order to use this functionality.') + }) + + /** + * @param {string} impersonatedUser The impersonated user. + * @param {function(protocol)} fn + */ + function verifyMethodNotSupportedError (fn, message) { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV2(recorder, null, false, + () => null, null, () => {}) + + expect(() => fn(protocol)).toThrowError(message) + } + }) }) diff --git a/packages/bolt-connection/test/bolt/bolt-protocol-v3.test.js b/packages/bolt-connection/test/bolt/bolt-protocol-v3.test.js index b3139c3cd..40adfb621 100644 --- a/packages/bolt-connection/test/bolt/bolt-protocol-v3.test.js +++ b/packages/bolt-connection/test/bolt/bolt-protocol-v3.test.js @@ -671,6 +671,41 @@ describe('#unit BoltProtocolV3', () => { expect(unpacked).toEqual(object) }) }) + + describe('Bolt v5.1', () => { + it('should not support logoff', () => { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV3(recorder, null, false) + + expect(protocol.supportsReAuth).toBe(false) + }) + + it('should thrown when logoff is called', () => { + verifyMethodNotSupportedError( + protocol => protocol.logoff(), + 'Driver is connected to a database that does not support logoff. ' + + 'Please upgrade to Neo4j 5.5.0 or later in order to use this functionality.') + }) + + it('should thrown when logon is called', () => { + verifyMethodNotSupportedError( + protocol => protocol.logon(), + 'Driver is connected to a database that does not support logon. ' + + 'Please upgrade to Neo4j 5.5.0 or later in order to use this functionality.') + }) + + /** + * @param {string} impersonatedUser The impersonated user. + * @param {function(protocol)} fn + */ + function verifyMethodNotSupportedError (fn, message) { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV3(recorder, null, false, + () => null, null, () => {}) + + expect(() => fn(protocol)).toThrowError(message) + } + }) }) class SpiedBoltProtocolV3 extends BoltProtocolV3 { diff --git a/packages/bolt-connection/test/bolt/bolt-protocol-v4x0.test.js b/packages/bolt-connection/test/bolt/bolt-protocol-v4x0.test.js index 0bd626c9d..b84e78fc8 100644 --- a/packages/bolt-connection/test/bolt/bolt-protocol-v4x0.test.js +++ b/packages/bolt-connection/test/bolt/bolt-protocol-v4x0.test.js @@ -589,6 +589,15 @@ describe('#unit BoltProtocolV4x0', () => { expect(unpacked).toEqual(object) }) }) + + describe('Bolt v5.1', () => { + it('should not support logoff', () => { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV4x0(recorder, null, false) + + expect(protocol.supportsReAuth).toBe(false) + }) + }) }) class SpiedBoltProtocolV4x0 extends BoltProtocolV4x0 { diff --git a/packages/bolt-connection/test/bolt/bolt-protocol-v4x1.test.js b/packages/bolt-connection/test/bolt/bolt-protocol-v4x1.test.js index c80b94f86..7b1d05e26 100644 --- a/packages/bolt-connection/test/bolt/bolt-protocol-v4x1.test.js +++ b/packages/bolt-connection/test/bolt/bolt-protocol-v4x1.test.js @@ -463,4 +463,39 @@ describe('#unit BoltProtocolV4x1', () => { expect(unpacked).toEqual(object) }) }) + + describe('Bolt v5.1', () => { + it('should not support logoff', () => { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV4x1(recorder, null, false) + + expect(protocol.supportsReAuth).toBe(false) + }) + + it('should thrown when logoff is called', () => { + verifyMethodNotSupportedError( + protocol => protocol.logoff(), + 'Driver is connected to a database that does not support logoff. ' + + 'Please upgrade to Neo4j 5.5.0 or later in order to use this functionality.') + }) + + it('should thrown when logon is called', () => { + verifyMethodNotSupportedError( + protocol => protocol.logon(), + 'Driver is connected to a database that does not support logon. ' + + 'Please upgrade to Neo4j 5.5.0 or later in order to use this functionality.') + }) + + /** + * @param {string} impersonatedUser The impersonated user. + * @param {function(protocol)} fn + */ + function verifyMethodNotSupportedError (fn, message) { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV4x1(recorder, null, false, + () => null, null, () => {}) + + expect(() => fn(protocol)).toThrowError(message) + } + }) }) diff --git a/packages/bolt-connection/test/bolt/bolt-protocol-v4x2.test.js b/packages/bolt-connection/test/bolt/bolt-protocol-v4x2.test.js index 998ac3206..9df65f4cd 100644 --- a/packages/bolt-connection/test/bolt/bolt-protocol-v4x2.test.js +++ b/packages/bolt-connection/test/bolt/bolt-protocol-v4x2.test.js @@ -462,4 +462,39 @@ describe('#unit BoltProtocolV4x2', () => { expect(unpacked).toEqual(object) }) }) + + describe('Bolt v5.1', () => { + it('should not support logoff', () => { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV4x2(recorder, null, false) + + expect(protocol.supportsReAuth).toBe(false) + }) + + it('should thrown when logoff is called', () => { + verifyMethodNotSupportedError( + protocol => protocol.logoff(), + 'Driver is connected to a database that does not support logoff. ' + + 'Please upgrade to Neo4j 5.5.0 or later in order to use this functionality.') + }) + + it('should thrown when logon is called', () => { + verifyMethodNotSupportedError( + protocol => protocol.logon(), + 'Driver is connected to a database that does not support logon. ' + + 'Please upgrade to Neo4j 5.5.0 or later in order to use this functionality.') + }) + + /** + * @param {string} impersonatedUser The impersonated user. + * @param {function(protocol)} fn + */ + function verifyMethodNotSupportedError (fn, message) { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV4x2(recorder, null, false, + () => null, null, () => {}) + + expect(() => fn(protocol)).toThrowError(message) + } + }) }) diff --git a/packages/bolt-connection/test/bolt/bolt-protocol-v4x3.test.js b/packages/bolt-connection/test/bolt/bolt-protocol-v4x3.test.js index 5b2d393ff..9bcce10d4 100644 --- a/packages/bolt-connection/test/bolt/bolt-protocol-v4x3.test.js +++ b/packages/bolt-connection/test/bolt/bolt-protocol-v4x3.test.js @@ -1095,4 +1095,39 @@ describe('#unit BoltProtocolV4x3', () => { }) }) }) + + describe('Bolt v5.1', () => { + it('should not support logoff', () => { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV4x3(recorder, null, false) + + expect(protocol.supportsReAuth).toBe(false) + }) + + it('should thrown when logoff is called', () => { + verifyMethodNotSupportedError( + protocol => protocol.logoff(), + 'Driver is connected to a database that does not support logoff. ' + + 'Please upgrade to Neo4j 5.5.0 or later in order to use this functionality.') + }) + + it('should thrown when logon is called', () => { + verifyMethodNotSupportedError( + protocol => protocol.logon(), + 'Driver is connected to a database that does not support logon. ' + + 'Please upgrade to Neo4j 5.5.0 or later in order to use this functionality.') + }) + + /** + * @param {string} impersonatedUser The impersonated user. + * @param {function(protocol)} fn + */ + function verifyMethodNotSupportedError (fn, message) { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV4x3(recorder, null, false, + () => null, null, () => {}) + + expect(() => fn(protocol)).toThrowError(message) + } + }) }) diff --git a/packages/bolt-connection/test/bolt/bolt-protocol-v4x4.test.js b/packages/bolt-connection/test/bolt/bolt-protocol-v4x4.test.js index 8d250e7c2..f541c869e 100644 --- a/packages/bolt-connection/test/bolt/bolt-protocol-v4x4.test.js +++ b/packages/bolt-connection/test/bolt/bolt-protocol-v4x4.test.js @@ -1128,4 +1128,39 @@ describe('#unit BoltProtocolV4x4', () => { }) }) }) + + describe('Bolt v5.1', () => { + it('should not support logoff', () => { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV4x4(recorder, null, false) + + expect(protocol.supportsReAuth).toBe(false) + }) + + it('should thrown when logoff is called', () => { + verifyMethodNotSupportedError( + protocol => protocol.logoff(), + 'Driver is connected to a database that does not support logoff. ' + + 'Please upgrade to Neo4j 5.5.0 or later in order to use this functionality.') + }) + + it('should thrown when logon is called', () => { + verifyMethodNotSupportedError( + protocol => protocol.logon(), + 'Driver is connected to a database that does not support logon. ' + + 'Please upgrade to Neo4j 5.5.0 or later in order to use this functionality.') + }) + + /** + * @param {string} impersonatedUser The impersonated user. + * @param {function(protocol)} fn + */ + function verifyMethodNotSupportedError (fn, message) { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV4x4(recorder, null, false, + () => null, null, () => {}) + + expect(() => fn(protocol)).toThrowError(message) + } + }) }) diff --git a/packages/bolt-connection/test/bolt/bolt-protocol-v5x0.test.js b/packages/bolt-connection/test/bolt/bolt-protocol-v5x0.test.js index 221c1893b..72d603215 100644 --- a/packages/bolt-connection/test/bolt/bolt-protocol-v5x0.test.js +++ b/packages/bolt-connection/test/bolt/bolt-protocol-v5x0.test.js @@ -1027,4 +1027,39 @@ describe('#unit BoltProtocolV5x0', () => { expect(unpacked).toEqual(struct) }) }) + + describe('Bolt v5.1', () => { + it('should not support logoff', () => { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x0(recorder, null, false) + + expect(protocol.supportsReAuth).toBe(false) + }) + + it('should thrown when logoff is called', () => { + verifyMethodNotSupportedError( + protocol => protocol.logoff(), + 'Driver is connected to a database that does not support logoff. ' + + 'Please upgrade to Neo4j 5.5.0 or later in order to use this functionality.') + }) + + it('should thrown when logon is called', () => { + verifyMethodNotSupportedError( + protocol => protocol.logon(), + 'Driver is connected to a database that does not support logon. ' + + 'Please upgrade to Neo4j 5.5.0 or later in order to use this functionality.') + }) + + /** + * @param {string} impersonatedUser The impersonated user. + * @param {function(protocol)} fn + */ + function verifyMethodNotSupportedError (fn, message) { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x0(recorder, null, false, + () => null, null, () => {}) + + expect(() => fn(protocol)).toThrowError(message) + } + }) }) diff --git a/packages/bolt-connection/test/bolt/bolt-protocol-v5x1.test.js b/packages/bolt-connection/test/bolt/bolt-protocol-v5x1.test.js new file mode 100644 index 000000000..afce3f0a3 --- /dev/null +++ b/packages/bolt-connection/test/bolt/bolt-protocol-v5x1.test.js @@ -0,0 +1,1092 @@ +/** + * Copyright (c) "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 BoltProtocolV5x1 from '../../src/bolt/bolt-protocol-v5x1' +import RequestMessage from '../../src/bolt/request-message' +import { v2, structure } from '../../src/packstream' +import utils from '../test-utils' +import { LoginObserver, RouteObserver } from '../../src/bolt/stream-observers' +import fc from 'fast-check' +import { + Date, + DateTime, + Duration, + LocalDateTime, + LocalTime, + Path, + PathSegment, + Point, + Relationship, + Time, + UnboundRelationship, + Node, + internal +} from 'neo4j-driver-core' + +import { alloc } from '../../src/channel' + +const WRITE = 'WRITE' + +const { + txConfig: { TxConfig }, + bookmarks: { Bookmarks }, + logger: { Logger }, + temporalUtil +} = internal + +describe('#unit BoltProtocolV5x1', () => { + beforeEach(() => { + expect.extend(utils.matchers) + }) + + it('should request routing information', () => { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x1(recorder, null, false) + utils.spyProtocolWrite(protocol) + const routingContext = { someContextParam: 'value' } + const databaseName = 'name' + + const observer = protocol.requestRoutingInformation({ + routingContext, + databaseName + }) + + protocol.verifyMessageCount(1) + expect(protocol.messages[0]).toBeMessage( + RequestMessage.routeV4x4(routingContext, [], { databaseName, impersonatedUser: null }) + ) + expect(protocol.observers).toEqual([observer]) + expect(observer).toEqual(expect.any(RouteObserver)) + expect(protocol.flushes).toEqual([true]) + }) + + it('should request routing information sending bookmarks', () => { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x1(recorder, null, false) + utils.spyProtocolWrite(protocol) + const routingContext = { someContextParam: 'value' } + const listOfBookmarks = ['a', 'b', 'c'] + const bookmarks = new Bookmarks(listOfBookmarks) + const databaseName = 'name' + + const observer = protocol.requestRoutingInformation({ + routingContext, + databaseName, + sessionContext: { bookmarks } + }) + + protocol.verifyMessageCount(1) + expect(protocol.messages[0]).toBeMessage( + RequestMessage.routeV4x4(routingContext, listOfBookmarks, { databaseName, impersonatedUser: null }) + ) + expect(protocol.observers).toEqual([observer]) + expect(observer).toEqual(expect.any(RouteObserver)) + expect(protocol.flushes).toEqual([true]) + }) + + it('should run a query', () => { + const database = 'testdb' + const bookmarks = new Bookmarks([ + 'neo4j:bookmark:v1:tx1', + 'neo4j:bookmark:v1:tx2' + ]) + const txConfig = new TxConfig({ + timeout: 5000, + metadata: { x: 1, y: 'something' } + }) + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x1(recorder, null, false) + utils.spyProtocolWrite(protocol) + + const query = 'RETURN $x, $y' + const parameters = { x: 'x', y: 'y' } + + const observer = protocol.run(query, parameters, { + bookmarks, + txConfig, + database, + mode: WRITE + }) + + protocol.verifyMessageCount(2) + + expect(protocol.messages[0]).toBeMessage( + RequestMessage.runWithMetadata(query, parameters, { + bookmarks, + txConfig, + database, + mode: WRITE + }) + ) + expect(protocol.messages[1]).toBeMessage(RequestMessage.pull()) + expect(protocol.observers).toEqual([observer, observer]) + expect(protocol.flushes).toEqual([false, true]) + }) + + it('should run a with impersonated user', () => { + const database = 'testdb' + const impersonatedUser = 'the impostor' + const bookmarks = new Bookmarks([ + 'neo4j:bookmark:v1:tx1', + 'neo4j:bookmark:v1:tx2' + ]) + const txConfig = new TxConfig({ + timeout: 5000, + metadata: { x: 1, y: 'something' } + }) + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x1(recorder, null, false) + utils.spyProtocolWrite(protocol) + + const query = 'RETURN $x, $y' + const parameters = { x: 'x', y: 'y' } + + const observer = protocol.run(query, parameters, { + bookmarks, + txConfig, + database, + mode: WRITE, + impersonatedUser + }) + + protocol.verifyMessageCount(2) + + expect(protocol.messages[0]).toBeMessage( + RequestMessage.runWithMetadata(query, parameters, { + bookmarks, + txConfig, + database, + mode: WRITE, + impersonatedUser + }) + ) + expect(protocol.messages[1]).toBeMessage(RequestMessage.pull()) + expect(protocol.observers).toEqual([observer, observer]) + expect(protocol.flushes).toEqual([false, true]) + }) + + it('should begin a transaction', () => { + const database = 'testdb' + const bookmarks = new Bookmarks([ + 'neo4j:bookmark:v1:tx1', + 'neo4j:bookmark:v1:tx2' + ]) + const txConfig = new TxConfig({ + timeout: 5000, + metadata: { x: 1, y: 'something' } + }) + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x1(recorder, null, false) + utils.spyProtocolWrite(protocol) + + const observer = protocol.beginTransaction({ + bookmarks, + txConfig, + database, + mode: WRITE + }) + + protocol.verifyMessageCount(1) + expect(protocol.messages[0]).toBeMessage( + RequestMessage.begin({ bookmarks, txConfig, database, mode: WRITE }) + ) + expect(protocol.observers).toEqual([observer]) + expect(protocol.flushes).toEqual([true]) + }) + + it('should begin a transaction with impersonated user', () => { + const database = 'testdb' + const impersonatedUser = 'the impostor' + const bookmarks = new Bookmarks([ + 'neo4j:bookmark:v1:tx1', + 'neo4j:bookmark:v1:tx2' + ]) + const txConfig = new TxConfig({ + timeout: 5000, + metadata: { x: 1, y: 'something' } + }) + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x1(recorder, null, false) + utils.spyProtocolWrite(protocol) + + const observer = protocol.beginTransaction({ + bookmarks, + txConfig, + database, + mode: WRITE, + impersonatedUser + }) + + protocol.verifyMessageCount(1) + expect(protocol.messages[0]).toBeMessage( + RequestMessage.begin({ bookmarks, txConfig, database, mode: WRITE, impersonatedUser }) + ) + expect(protocol.observers).toEqual([observer]) + expect(protocol.flushes).toEqual([true]) + }) + + it('should return correct bolt version number', () => { + const protocol = new BoltProtocolV5x1(null, null, false) + + expect(protocol.version).toBe(5.1) + }) + + it('should update metadata', () => { + const metadata = { t_first: 1, t_last: 2, db_hits: 3, some_other_key: 4 } + const protocol = new BoltProtocolV5x1(null, null, false) + + const transformedMetadata = protocol.transformMetadata(metadata) + + expect(transformedMetadata).toEqual({ + result_available_after: 1, + result_consumed_after: 2, + db_hits: 3, + some_other_key: 4 + }) + }) + + it('should initialize connection', () => { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x1(recorder, null, false) + utils.spyProtocolWrite(protocol) + + const clientName = 'js-driver/1.2.3' + const authToken = { username: 'neo4j', password: 'secret' } + + const observer = protocol.initialize({ userAgent: clientName, authToken }) + + protocol.verifyMessageCount(2) + expect(protocol.messages[0]).toBeMessage( + RequestMessage.hello5x1(clientName) + ) + expect(protocol.messages[1]).toBeMessage( + RequestMessage.logon(authToken) + ) + + expect(protocol.observers.length).toBe(2) + + // hello observer + const helloObserver = protocol.observers[0] + expect(helloObserver).toBeInstanceOf(LoginObserver) + expect(helloObserver).not.toBe(observer) + + // login observer + const loginObserver = protocol.observers[1] + expect(loginObserver).toBeInstanceOf(LoginObserver) + expect(loginObserver).toBe(observer) + + expect(protocol.flushes).toEqual([false, true]) + }) + + it.each( + [true, false] + )('should logon to the server [flush=%s]', (flush) => { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x1(recorder, null, false) + utils.spyProtocolWrite(protocol) + + const authToken = { username: 'neo4j', password: 'secret' } + + const observer = protocol.logon({ authToken, flush }) + + protocol.verifyMessageCount(1) + + expect(protocol.messages[0]).toBeMessage( + RequestMessage.logon(authToken) + ) + + expect(protocol.observers).toEqual([observer]) + expect(protocol.flushes).toEqual([flush]) + }) + + it.each( + [true, false] + )('should logoff from the server [flush=%s]', (flush) => { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x1(recorder, null, false) + utils.spyProtocolWrite(protocol) + + const observer = protocol.logoff({ flush }) + + protocol.verifyMessageCount(1) + + expect(protocol.messages[0]).toBeMessage( + RequestMessage.logoff() + ) + + expect(protocol.observers).toEqual([observer]) + expect(protocol.flushes).toEqual([flush]) + }) + + it('should begin a transaction', () => { + const bookmarks = new Bookmarks([ + 'neo4j:bookmark:v1:tx1', + 'neo4j:bookmark:v1:tx2' + ]) + const txConfig = new TxConfig({ + timeout: 5000, + metadata: { x: 1, y: 'something' } + }) + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x1(recorder, null, false) + utils.spyProtocolWrite(protocol) + + const observer = protocol.beginTransaction({ + bookmarks, + txConfig, + mode: WRITE + }) + + protocol.verifyMessageCount(1) + expect(protocol.messages[0]).toBeMessage( + RequestMessage.begin({ bookmarks, txConfig, mode: WRITE }) + ) + expect(protocol.observers).toEqual([observer]) + expect(protocol.flushes).toEqual([true]) + }) + + it('should commit', () => { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x1(recorder, null, false) + utils.spyProtocolWrite(protocol) + + const observer = protocol.commitTransaction() + + protocol.verifyMessageCount(1) + expect(protocol.messages[0]).toBeMessage(RequestMessage.commit()) + expect(protocol.observers).toEqual([observer]) + expect(protocol.flushes).toEqual([true]) + }) + + it('should rollback', () => { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x1(recorder, null, false) + utils.spyProtocolWrite(protocol) + + const observer = protocol.rollbackTransaction() + + protocol.verifyMessageCount(1) + expect(protocol.messages[0]).toBeMessage(RequestMessage.rollback()) + expect(protocol.observers).toEqual([observer]) + expect(protocol.flushes).toEqual([true]) + }) + + it('should support logoff', () => { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x1(recorder, null, false) + + expect(protocol.supportsReAuth).toBe(true) + }) + + describe('unpacker configuration', () => { + test.each([ + [false, false], + [false, true], + [true, false], + [true, true] + ])( + 'should create unpacker with disableLosslessIntegers=%p and useBigInt=%p', + (disableLosslessIntegers, useBigInt) => { + const protocol = new BoltProtocolV5x1(null, null, { + disableLosslessIntegers, + useBigInt + }) + expect(protocol._unpacker._disableLosslessIntegers).toBe( + disableLosslessIntegers + ) + expect(protocol._unpacker._useBigInt).toBe(useBigInt) + } + ) + }) + + describe('watermarks', () => { + it('.run() should configure watermarks', () => { + const recorder = new utils.MessageRecordingConnection() + const protocol = utils.spyProtocolWrite( + new BoltProtocolV5x1(recorder, null, false) + ) + + const query = 'RETURN $x, $y' + const parameters = { x: 'x', y: 'y' } + const observer = protocol.run(query, parameters, { + bookmarks: Bookmarks.empty(), + txConfig: TxConfig.empty(), + lowRecordWatermark: 100, + highRecordWatermark: 200 + }) + + expect(observer._lowRecordWatermark).toEqual(100) + expect(observer._highRecordWatermark).toEqual(200) + }) + }) + + describe('packstream', () => { + it('should configure v2 packer', () => { + const protocol = new BoltProtocolV5x1(null, null, false) + expect(protocol.packer()).toBeInstanceOf(v2.Packer) + }) + + it('should configure v2 unpacker', () => { + const protocol = new BoltProtocolV5x1(null, null, false) + expect(protocol.unpacker()).toBeInstanceOf(v2.Unpacker) + }) + }) + + describe('.packable()', () => { + it.each([ + ['Node', new Node(1, ['a'], { a: 'b' }, 'c')], + ['Relationship', new Relationship(1, 2, 3, 'a', { b: 'c' }, 'd', 'e', 'f')], + ['UnboundRelationship', new UnboundRelationship(1, 'a', { b: 'c' }, '1')], + ['Path', new Path(new Node(1, [], {}), new Node(2, [], {}), [])] + ])('should pack not pack graph types (%s)', (_, graphType) => { + const protocol = new BoltProtocolV5x1( + new utils.MessageRecordingConnection(), + null, + false + ) + + const packable = protocol.packable(graphType) + + expect(packable).toThrowErrorMatchingSnapshot() + }) + + it.each([ + ['Duration', new Duration(1, 1, 1, 1)], + ['LocalTime', new LocalTime(1, 1, 1, 1)], + ['Time', new Time(1, 1, 1, 1, 1)], + ['Date', new Date(1, 1, 1)], + ['LocalDateTime', new LocalDateTime(1, 1, 1, 1, 1, 1, 1)], + [ + 'DateTimeWithZoneOffset', + new DateTime(2022, 6, 14, 15, 21, 18, 183_000_000, 120 * 60) + ], + [ + 'DateTimeWithZoneOffset / 1978', + new DateTime(1978, 12, 16, 10, 5, 59, 128000987, -150 * 60) + ], + [ + 'DateTimeWithZoneId / Berlin 2:30 CET', + new DateTime(2022, 10, 30, 2, 30, 0, 183_000_000, 2 * 60 * 60, 'Europe/Berlin') + ], + [ + 'DateTimeWithZoneId / Berlin 2:30 CEST', + new DateTime(2022, 10, 30, 2, 30, 0, 183_000_000, 1 * 60 * 60, 'Europe/Berlin') + ], + ['Point2D', new Point(1, 1, 1)], + ['Point3D', new Point(1, 1, 1, 1)] + ])('should pack spatial types and temporal types (%s)', (_, object) => { + const buffer = alloc(256) + const protocol = new BoltProtocolV5x1( + new utils.MessageRecordingConnection(), + buffer, + { + disableLosslessIntegers: true + } + ) + + const packable = protocol.packable(object) + + expect(packable).not.toThrow() + + buffer.reset() + + const unpacked = protocol.unpack(buffer) + + expect(unpacked).toEqual(object) + }) + + it.each([ + [ + 'DateTimeWithZoneId / Australia', + new DateTime(2022, 6, 15, 15, 21, 18, 183_000_000, undefined, 'Australia/Eucla') + ], + [ + 'DateTimeWithZoneId', + new DateTime(2022, 6, 22, 15, 21, 18, 183_000_000, undefined, 'Europe/Berlin') + ], + [ + 'DateTimeWithZoneId / Europe just before turn CEST', + new DateTime(2022, 3, 27, 1, 59, 59, 183_000_000, undefined, 'Europe/Berlin') + ], + [ + 'DateTimeWithZoneId / Europe just 1 before turn CEST', + new DateTime(2022, 3, 27, 0, 59, 59, 183_000_000, undefined, 'Europe/Berlin') + ], + [ + 'DateTimeWithZoneId / Europe just after turn CEST', + new DateTime(2022, 3, 27, 3, 0, 0, 183_000_000, undefined, 'Europe/Berlin') + ], + [ + 'DateTimeWithZoneId / Europe just 1 after turn CEST', + new DateTime(2022, 3, 27, 4, 0, 0, 183_000_000, undefined, 'Europe/Berlin') + ], + [ + 'DateTimeWithZoneId / Europe just before turn CET', + new DateTime(2022, 10, 30, 2, 59, 59, 183_000_000, undefined, 'Europe/Berlin') + ], + [ + 'DateTimeWithZoneId / Europe just 1 before turn CET', + new DateTime(2022, 10, 30, 1, 59, 59, 183_000_000, undefined, 'Europe/Berlin') + ], + [ + 'DateTimeWithZoneId / Europe just after turn CET', + new DateTime(2022, 10, 30, 3, 0, 0, 183_000_000, undefined, 'Europe/Berlin') + ], + [ + 'DateTimeWithZoneId / Europe just 1 after turn CET', + new DateTime(2022, 10, 30, 4, 0, 0, 183_000_000, undefined, 'Europe/Berlin') + ], + [ + 'DateTimeWithZoneId / Sao Paulo just before turn summer time', + new DateTime(2018, 11, 4, 11, 59, 59, 183_000_000, undefined, 'America/Sao_Paulo') + ], + [ + 'DateTimeWithZoneId / Sao Paulo just 1 before turn summer time', + new DateTime(2018, 11, 4, 10, 59, 59, 183_000_000, undefined, 'America/Sao_Paulo') + ], + [ + 'DateTimeWithZoneId / Sao Paulo just after turn summer time', + new DateTime(2018, 11, 5, 1, 0, 0, 183_000_000, undefined, 'America/Sao_Paulo') + ], + [ + 'DateTimeWithZoneId / Sao Paulo just 1 after turn summer time', + new DateTime(2018, 11, 5, 2, 0, 0, 183_000_000, undefined, 'America/Sao_Paulo') + ], + [ + 'DateTimeWithZoneId / Sao Paulo just before turn winter time', + new DateTime(2019, 2, 17, 11, 59, 59, 183_000_000, undefined, 'America/Sao_Paulo') + ], + [ + 'DateTimeWithZoneId / Sao Paulo just 1 before turn winter time', + new DateTime(2019, 2, 17, 10, 59, 59, 183_000_000, undefined, 'America/Sao_Paulo') + ], + [ + 'DateTimeWithZoneId / Sao Paulo just after turn winter time', + new DateTime(2019, 2, 18, 0, 0, 0, 183_000_000, undefined, 'America/Sao_Paulo') + ], + [ + 'DateTimeWithZoneId / Sao Paulo just 1 after turn winter time', + new DateTime(2019, 2, 18, 1, 0, 0, 183_000_000, undefined, 'America/Sao_Paulo') + ], + [ + 'DateTimeWithZoneId / Istanbul', + new DateTime(1978, 12, 16, 12, 35, 59, 128000987, undefined, 'Europe/Istanbul') + ], + [ + 'DateTimeWithZoneId / Istanbul', + new DateTime(2020, 6, 15, 4, 30, 0, 183_000_000, undefined, 'Pacific/Honolulu') + ], + [ + 'DateWithWithZoneId / Berlin before common era', + new DateTime(-2020, 6, 15, 4, 30, 0, 183_000_000, undefined, 'Europe/Berlin') + ], + [ + 'DateWithWithZoneId / Max Date', + new DateTime(99_999, 12, 31, 23, 59, 59, 999_999_999, undefined, 'Pacific/Kiritimati') + ], + [ + 'DateWithWithZoneId / Min Date', + new DateTime(-99_999, 12, 31, 23, 59, 59, 999_999_999, undefined, 'Pacific/Samoa') + ], + [ + 'DateWithWithZoneId / Ambiguous date between 00 and 99', + new DateTime(50, 12, 31, 23, 59, 59, 999_999_999, undefined, 'Pacific/Samoa') + ] + ])('should pack and unpack DateTimeWithZoneId and without offset (%s)', (_, object) => { + const buffer = alloc(256) + const loggerFunction = jest.fn() + const protocol = new BoltProtocolV5x1( + new utils.MessageRecordingConnection(), + buffer, + { + disableLosslessIntegers: true + }, + undefined, + new Logger('debug', loggerFunction) + ) + + const packable = protocol.packable(object) + + expect(packable).not.toThrow() + expect(loggerFunction) + .toBeCalledWith('warn', + 'DateTime objects without "timeZoneOffsetSeconds" property ' + + 'are prune to bugs related to ambiguous times. For instance, ' + + '2022-10-30T2:30:00[Europe/Berlin] could be GMT+1 or GMT+2.') + + buffer.reset() + + const unpacked = protocol.unpack(buffer) + + expect(unpacked.timeZoneOffsetSeconds).toBeDefined() + + const unpackedDateTimeWithoutOffset = new DateTime( + unpacked.year, + unpacked.month, + unpacked.day, + unpacked.hour, + unpacked.minute, + unpacked.second, + unpacked.nanosecond, + undefined, + unpacked.timeZoneId + ) + + expect(unpackedDateTimeWithoutOffset).toEqual(object) + }) + + it('should pack and unpack DateTimeWithOffset', () => { + fc.assert( + fc.property( + fc.date({ + min: temporalUtil.newDate(utils.MIN_UTC_IN_MS + utils.ONE_DAY_IN_MS), + max: temporalUtil.newDate(utils.MAX_UTC_IN_MS - utils.ONE_DAY_IN_MS) + }), + fc.integer({ min: 0, max: 999_999 }), + utils.arbitraryTimeZoneId(), + (date, nanoseconds, timeZoneId) => { + const object = new DateTime( + date.getUTCFullYear(), + date.getUTCMonth() + 1, + date.getUTCDate(), + date.getUTCHours(), + date.getUTCMinutes(), + date.getUTCSeconds(), + date.getUTCMilliseconds() * 1_000_000 + nanoseconds, + undefined, + timeZoneId + ) + const buffer = alloc(256) + const loggerFunction = jest.fn() + const protocol = new BoltProtocolV5x1( + new utils.MessageRecordingConnection(), + buffer, + { + disableLosslessIntegers: true + }, + undefined, + new Logger('debug', loggerFunction) + ) + + const packable = protocol.packable(object) + + expect(packable).not.toThrow() + expect(loggerFunction) + .toBeCalledWith('warn', + 'DateTime objects without "timeZoneOffsetSeconds" property ' + + 'are prune to bugs related to ambiguous times. For instance, ' + + '2022-10-30T2:30:00[Europe/Berlin] could be GMT+1 or GMT+2.') + + buffer.reset() + + const unpacked = protocol.unpack(buffer) + + expect(unpacked.timeZoneOffsetSeconds).toBeDefined() + + const unpackedDateTimeWithoutOffset = new DateTime( + unpacked.year, + unpacked.month, + unpacked.day, + unpacked.hour, + unpacked.minute, + unpacked.second, + unpacked.nanosecond, + undefined, + unpacked.timeZoneId + ) + + expect(unpackedDateTimeWithoutOffset).toEqual(object) + }) + ) + }) + + it('should pack and unpack DateTimeWithZoneIdAndNoOffset', () => { + fc.assert( + fc.property(fc.date(), date => { + const object = DateTime.fromStandardDate(date) + const buffer = alloc(256) + const loggerFunction = jest.fn() + const protocol = new BoltProtocolV5x1( + new utils.MessageRecordingConnection(), + buffer, + { + disableLosslessIntegers: true + }, + undefined, + new Logger('debug', loggerFunction) + ) + + const packable = protocol.packable(object) + + expect(packable).not.toThrow() + + buffer.reset() + + const unpacked = protocol.unpack(buffer) + + expect(unpacked.timeZoneOffsetSeconds).toBeDefined() + + expect(unpacked).toEqual(object) + }) + ) + }) + }) + + describe('.unpack()', () => { + it.each([ + [ + 'Node', + new structure.Structure(0x4e, [1, ['a'], { c: 'd' }, 'elementId']), + new Node(1, ['a'], { c: 'd' }, 'elementId') + ], + [ + 'Relationship', + new structure.Structure(0x52, [1, 2, 3, '4', { 5: 6 }, 'elementId', 'node1', 'node2']), + new Relationship(1, 2, 3, '4', { 5: 6 }, 'elementId', 'node1', 'node2') + ], + [ + 'UnboundRelationship', + new structure.Structure(0x72, [1, '2', { 3: 4 }, 'elementId']), + new UnboundRelationship(1, '2', { 3: 4 }, 'elementId') + ], + [ + 'Path', + new structure.Structure( + 0x50, + [ + [ + new structure.Structure(0x4e, [1, ['2'], { 3: '4' }, 'node1']), + new structure.Structure(0x4e, [4, ['5'], { 6: 7 }, 'node2']), + new structure.Structure(0x4e, [2, ['3'], { 4: '5' }, 'node3']) + ], + [ + new structure.Structure(0x52, [3, 1, 4, 'reltype1', { 4: '5' }, 'rel1', 'node1', 'node2']), + new structure.Structure(0x52, [5, 4, 2, 'reltype2', { 6: 7 }, 'rel2', 'node2', 'node3']) + ], + [1, 1, 2, 2] + ] + ), + new Path( + new Node(1, ['2'], { 3: '4' }, 'node1'), + new Node(2, ['3'], { 4: '5' }, 'node3'), + [ + new PathSegment( + new Node(1, ['2'], { 3: '4' }, 'node1'), + new Relationship(3, 1, 4, 'reltype1', { 4: '5' }, 'rel1', 'node1', 'node2'), + new Node(4, ['5'], { 6: 7 }, 'node2') + ), + new PathSegment( + new Node(4, ['5'], { 6: 7 }, 'node2'), + new Relationship(5, 4, 2, 'reltype2', { 6: 7 }, 'rel2', 'node2', 'node3'), + new Node(2, ['3'], { 4: '5' }, 'node3') + ) + ] + ) + ] + ])('should unpack graph types (%s)', (_, struct, graphObject) => { + const buffer = alloc(256) + const protocol = new BoltProtocolV5x1( + new utils.MessageRecordingConnection(), + buffer, + false + ) + + const packable = protocol.packable(struct) + + expect(packable).not.toThrow() + + buffer.reset() + + const unpacked = protocol.unpack(buffer) + expect(unpacked).toEqual(graphObject) + }) + + it.each([ + [ + 'Node with less fields', + new structure.Structure(0x4e, [1, ['a'], { c: 'd' }]) + ], + [ + 'Node with more fields', + new structure.Structure(0x4e, [1, ['a'], { c: 'd' }, '1', 'b']) + ], + [ + 'Relationship with less fields', + new structure.Structure(0x52, [1, 2, 3, '4', { 5: 6 }]) + ], + [ + 'Relationship with more fields', + new structure.Structure(0x52, [1, 2, 3, '4', { 5: 6 }, '1', '2', '3', '4']) + ], + [ + 'UnboundRelationship with less fields', + new structure.Structure(0x72, [1, '2', { 3: 4 }]) + ], + [ + 'UnboundRelationship with more fields', + new structure.Structure(0x72, [1, '2', { 3: 4 }, '1', '2']) + ], + [ + 'Path with less fields', + new structure.Structure( + 0x50, + [ + [ + new structure.Structure(0x4e, [1, ['2'], { 3: '4' }]), + new structure.Structure(0x4e, [4, ['5'], { 6: 7 }]), + new structure.Structure(0x4e, [2, ['3'], { 4: '5' }]) + ], + [ + new structure.Structure(0x52, [3, 1, 4, 'rel1', { 4: '5' }]), + new structure.Structure(0x52, [5, 4, 2, 'rel2', { 6: 7 }]) + ] + ] + ) + ], + [ + 'Path with more fields', + new structure.Structure( + 0x50, + [ + [ + new structure.Structure(0x4e, [1, ['2'], { 3: '4' }]), + new structure.Structure(0x4e, [4, ['5'], { 6: 7 }]), + new structure.Structure(0x4e, [2, ['3'], { 4: '5' }]) + ], + [ + new structure.Structure(0x52, [3, 1, 4, 'rel1', { 4: '5' }]), + new structure.Structure(0x52, [5, 4, 2, 'rel2', { 6: 7 }]) + ], + [1, 1, 2, 2], + 'a' + ] + ) + ], + [ + 'Point with less fields', + new structure.Structure(0x58, [1, 2]) + ], + [ + 'Point with more fields', + new structure.Structure(0x58, [1, 2, 3, 4]) + ], + [ + 'Point3D with less fields', + new structure.Structure(0x59, [1, 2, 3]) + ], + + [ + 'Point3D with more fields', + new structure.Structure(0x59, [1, 2, 3, 4, 6]) + ], + [ + 'Duration with less fields', + new structure.Structure(0x45, [1, 2, 3]) + ], + [ + 'Duration with more fields', + new structure.Structure(0x45, [1, 2, 3, 4, 5]) + ], + [ + 'LocalTime with less fields', + new structure.Structure(0x74, []) + ], + [ + 'LocalTime with more fields', + new structure.Structure(0x74, [1, 2]) + ], + [ + 'Time with less fields', + new structure.Structure(0x54, [1]) + ], + [ + 'Time with more fileds', + new structure.Structure(0x54, [1, 2, 3]) + ], + [ + 'Date with less fields', + new structure.Structure(0x44, []) + ], + [ + 'Date with more fields', + new structure.Structure(0x44, [1, 2]) + ], + [ + 'LocalDateTime with less fields', + new structure.Structure(0x64, [1]) + ], + [ + 'LocalDateTime with more fields', + new structure.Structure(0x64, [1, 2, 3]) + ], + [ + 'DateTimeWithZoneOffset with less fields', + new structure.Structure(0x49, [1, 2]) + ], + [ + 'DateTimeWithZoneOffset with more fields', + new structure.Structure(0x49, [1, 2, 3, 4]) + ], + [ + 'DateTimeWithZoneId with less fields', + new structure.Structure(0x69, [1, 2]) + ], + [ + 'DateTimeWithZoneId with more fields', + new structure.Structure(0x69, [1, 2, 'America/Sao Paulo', 'Brasil']) + ] + ])('should not unpack with wrong size (%s)', (_, struct) => { + const buffer = alloc(256) + const protocol = new BoltProtocolV5x1( + new utils.MessageRecordingConnection(), + buffer, + false + ) + + const packable = protocol.packable(struct) + + expect(packable).not.toThrow() + + buffer.reset() + + const unpacked = protocol.unpack(buffer) + expect(() => unpacked instanceof structure.Structure).toThrowErrorMatchingSnapshot() + }) + + it.each([ + [ + 'Point', + new structure.Structure(0x58, [1, 2, 3]), + new Point(1, 2, 3) + ], + [ + 'Point3D', + new structure.Structure(0x59, [1, 2, 3, 4]), + new Point(1, 2, 3, 4) + ], + [ + 'Duration', + new structure.Structure(0x45, [1, 2, 3, 4]), + new Duration(1, 2, 3, 4) + ], + [ + 'LocalTime', + new structure.Structure(0x74, [1]), + new LocalTime(0, 0, 0, 1) + ], + [ + 'Time', + new structure.Structure(0x54, [1, 2]), + new Time(0, 0, 0, 1, 2) + ], + [ + 'Date', + new structure.Structure(0x44, [1]), + new Date(1970, 1, 2) + ], + [ + 'LocalDateTime', + new structure.Structure(0x64, [1, 2]), + new LocalDateTime(1970, 1, 1, 0, 0, 1, 2) + ], + [ + 'DateTimeWithZoneOffset', + new structure.Structure(0x49, [ + 1655212878, 183_000_000, 120 * 60 + ]), + new DateTime(2022, 6, 14, 15, 21, 18, 183_000_000, 120 * 60) + ], + [ + 'DateTimeWithZoneOffset / 1978', + new structure.Structure(0x49, [ + 282659759, 128000987, -150 * 60 + ]), + new DateTime(1978, 12, 16, 10, 5, 59, 128000987, -150 * 60) + ], + [ + 'DateTimeWithZoneId', + new structure.Structure(0x69, [ + 1655212878, 183_000_000, 'Europe/Berlin' + ]), + new DateTime(2022, 6, 14, 15, 21, 18, 183_000_000, 2 * 60 * 60, 'Europe/Berlin') + ], + [ + 'DateTimeWithZoneId / Australia', + new structure.Structure(0x69, [ + 1655212878, 183_000_000, 'Australia/Eucla' + ]), + new DateTime(2022, 6, 14, 22, 6, 18, 183_000_000, 8 * 60 * 60 + 45 * 60, 'Australia/Eucla') + ], + [ + 'DateTimeWithZoneId / Honolulu', + new structure.Structure(0x69, [ + 1592231400, 183_000_000, 'Pacific/Honolulu' + ]), + new DateTime(2020, 6, 15, 4, 30, 0, 183_000_000, -10 * 60 * 60, 'Pacific/Honolulu') + ] + ])('should unpack spatial types and temporal types (%s)', (_, struct, object) => { + const buffer = alloc(256) + const protocol = new BoltProtocolV5x1( + new utils.MessageRecordingConnection(), + buffer, + { + disableLosslessIntegers: true + } + ) + + const packable = protocol.packable(struct) + + expect(packable).not.toThrow() + + buffer.reset() + + const unpacked = protocol.unpack(buffer) + expect(unpacked).toEqual(object) + }) + + it.each([ + [ + 'DateTimeWithZoneOffset/0x46', + new structure.Structure(0x46, [1, 2, 3]) + ], + [ + 'DateTimeWithZoneId/0x66', + new structure.Structure(0x66, [1, 2, 'America/Sao_Paulo']) + ] + ])('should unpack deprecated temporal types as unknown structs (%s)', (_, struct) => { + const buffer = alloc(256) + const protocol = new BoltProtocolV5x1( + new utils.MessageRecordingConnection(), + buffer, + { + disableLosslessIntegers: true + } + ) + + const packable = protocol.packable(struct) + + expect(packable).not.toThrow() + + buffer.reset() + + const unpacked = protocol.unpack(buffer) + expect(unpacked).toEqual(struct) + }) + }) +}) diff --git a/packages/bolt-connection/test/bolt/index.test.js b/packages/bolt-connection/test/bolt/index.test.js index 4542c6008..49273b1ca 100644 --- a/packages/bolt-connection/test/bolt/index.test.js +++ b/packages/bolt-connection/test/bolt/index.test.js @@ -44,13 +44,13 @@ describe('#unit Bolt', () => { const writtenBuffer = channel.written[0] const boltMagicPreamble = '60 60 b0 17' - const protocolVersion5x0 = '00 00 00 05' + const protocolVersion5x1to5x0 = '00 01 01 05' const protocolVersion4x4to4x2 = '00 02 04 04' const protocolVersion4x1 = '00 00 01 04' const protocolVersion3 = '00 00 00 03' expect(writtenBuffer.toHex()).toEqual( - `${boltMagicPreamble} ${protocolVersion5x0} ${protocolVersion4x4to4x2} ${protocolVersion4x1} ${protocolVersion3}` + `${boltMagicPreamble} ${protocolVersion5x1to5x0} ${protocolVersion4x4to4x2} ${protocolVersion4x1} ${protocolVersion3}` ) }) @@ -171,7 +171,7 @@ describe('#unit Bolt', () => { }) describe('create', () => { - forEachAvailableProtcol(({ version, protocolClass }) => { + forEachAvailableProtocol(({ version, protocolClass }) => { it(`it should create protocol ${version}`, () => { const params = createBoltCreateParams({ version }) @@ -345,7 +345,7 @@ describe('#unit Bolt', () => { }) }) - function forEachAvailableProtcol (lambda) { + function forEachAvailableProtocol (lambda) { function v (version, protocolClass) { return { version, protocolClass } } @@ -359,7 +359,8 @@ describe('#unit Bolt', () => { v(4.2, BoltProtocolV4x2), v(4.3, BoltProtocolV4x3), v(4.4, BoltProtocolV4x4), - v(5.0, BoltProtocolV5x0) + v(5.0, BoltProtocolV5x0), + v(5.1, BoltProtocolV5x0) ] availableProtocols.forEach(lambda) diff --git a/packages/bolt-connection/test/connection-provider/connection-provider-direct.test.js b/packages/bolt-connection/test/connection-provider/connection-provider-direct.test.js index 73deb7e96..2b659de63 100644 --- a/packages/bolt-connection/test/connection-provider/connection-provider-direct.test.js +++ b/packages/bolt-connection/test/connection-provider/connection-provider-direct.test.js @@ -251,7 +251,7 @@ describe('.verifyConnectivityAndGetServerInfo()', () => { const create = (address, release) => { const connection = new FakeConnection(address, release, server) connection.protocol = () => { - return { version: protocolVersion, isLastMessageLogin () { return false } } + return { version: protocolVersion, isLastMessageLogon () { return false } } } connection.resetAndFlush = resetAndFlush if (releaseMock) { diff --git a/packages/bolt-connection/test/connection-provider/connection-provider-routing.test.js b/packages/bolt-connection/test/connection-provider/connection-provider-routing.test.js index 96dc5c7ab..8e742cf3c 100644 --- a/packages/bolt-connection/test/connection-provider/connection-provider-routing.test.js +++ b/packages/bolt-connection/test/connection-provider/connection-provider-routing.test.js @@ -3120,7 +3120,7 @@ class FakeConnection extends Connection { protocol () { return { version: this._protocolVersion, - isLastMessageLogin: () => this._firstUsage + isLastMessageLogon: () => this._firstUsage } } } diff --git a/packages/core/src/internal/constants.ts b/packages/core/src/internal/constants.ts index 39e790f2f..1b9519387 100644 --- a/packages/core/src/internal/constants.ts +++ b/packages/core/src/internal/constants.ts @@ -34,6 +34,7 @@ const BOLT_PROTOCOL_V4_2: number = 4.2 const BOLT_PROTOCOL_V4_3: number = 4.3 const BOLT_PROTOCOL_V4_4: number = 4.4 const BOLT_PROTOCOL_V5_0: number = 5.0 +const BOLT_PROTOCOL_V5_1: number = 5.1 export { FETCH_ALL, @@ -50,5 +51,6 @@ export { BOLT_PROTOCOL_V4_2, BOLT_PROTOCOL_V4_3, BOLT_PROTOCOL_V4_4, - BOLT_PROTOCOL_V5_0 + BOLT_PROTOCOL_V5_0, + BOLT_PROTOCOL_V5_1 } diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v1.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v1.js index 20bf7a31b..7874be285 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v1.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v1.js @@ -24,15 +24,16 @@ import { // eslint-disable-next-line no-unused-vars import { Chunker } from '../channel/index.js' import { structure, v1 } from '../packstream/index.js' -import RequestMessage from './request-message.js' +import RequestMessage, { SIGNATURES } from './request-message.js' import { LoginObserver, + LogoffObserver, ResetObserver, ResultStreamObserver, // eslint-disable-next-line no-unused-vars StreamObserver } from './stream-observers.js' -import { internal } from '../../core/index.ts' +import { internal, newError } from '../../core/index.ts' import transformersFactories from './bolt-protocol-v1.transformer.js' import Transformer from './transformer.js' @@ -99,6 +100,27 @@ export default class BoltProtocol { return BOLT_PROTOCOL_V1 } + /** + * @property {boolean} supportsReAuth Either if the protocol version supports re-auth or not. + */ + get supportsReAuth () { + return false + } + + /** + * @property {boolean} initialized Either if the protocol was initialized or not + */ + get initialized () { + return !!this._initialized + } + + /** + * @property {object} authToken The token used in the last login + */ + get authToken () { + return this._authToken + } + /** * Get the packer. * @return {Packer} the protocol's packer. @@ -162,6 +184,61 @@ export default class BoltProtocol { return observer } + /** + * Performs logoff of the underlying connection + * + * @param {Object} param + * @param {function(err: Error)} param.onError the callback to invoke on error. + * @param {function()} param.onComplete the callback to invoke on completion. + * @param {boolean} param.flush whether to flush the buffered messages. + * + * @returns {StreamObserver} the stream observer that monitors the corresponding server response. + */ + logoff ({ onComplete, onError, flush } = {}) { + const observer = new LogoffObserver({ + onCompleted: onComplete, + onError: onError + }) + + const error = newError( + 'Driver is connected to a database that does not support logoff. ' + + 'Please upgrade to Neo4j 5.5.0 or later in order to use this functionality.' + ) + + // unsupported API was used, consider this a fatal error for the current connection + this._onProtocolError(error.message) + observer.onError(error) + throw error + } + + /** + * Performs login of the underlying connection + * + * @param {Object} args + * @param {Object} args.authToken the authentication token. + * @param {function(err: Error)} args.onError the callback to invoke on error. + * @param {function()} args.onComplete the callback to invoke on completion. + * @param {boolean} args.flush whether to flush the buffered messages. + * + * @returns {StreamObserver} the stream observer that monitors the corresponding server response. + */ + logon ({ authToken, onComplete, onError, flush } = {}) { + const observer = new LoginObserver({ + onCompleted: () => this._onLoginCompleted({}, authToken, onComplete), + onError: (error) => this._onLoginError(error, onError) + }) + + const error = newError( + 'Driver is connected to a database that does not support logon. ' + + 'Please upgrade to Neo4j 5.5.0 or later in order to use this functionality.' + ) + + // unsupported API was used, consider this a fatal error for the current connection + this._onProtocolError(error.message) + observer.onError(error) + throw error + } + /** * Perform protocol related operations for closing this connection */ @@ -391,19 +468,19 @@ export default class BoltProtocol { this.packable(messageStruct)() this._chunker.messageBoundary() - if (flush) { this._chunker.flush() } } } - isLastMessageLogin () { - return this._lastMessageSignature === 0x01 + isLastMessageLogon () { + return this._lastMessageSignature === SIGNATURES.HELLO || + this._lastMessageSignature === SIGNATURES.LOGON } isLastMessageReset () { - return this._lastMessageSignature === 0x0f + return this._lastMessageSignature === SIGNATURES.RESET } /** @@ -472,7 +549,9 @@ export default class BoltProtocol { this._responseHandler._resetFailure() } - _onLoginCompleted (metadata, onCompleted) { + _onLoginCompleted (metadata, authToken, onCompleted) { + this._initialized = true + this._authToken = authToken if (metadata) { const serverVersion = metadata.server if (!this._server.version) { diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v3.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v3.js index 57b5632ae..f9826485c 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v3.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v3.js @@ -72,7 +72,7 @@ export default class BoltProtocol extends BoltProtocolV2 { initialize ({ userAgent, authToken, onError, onComplete } = {}) { const observer = new LoginObserver({ onError: error => this._onLoginError(error, onError), - onCompleted: metadata => this._onLoginCompleted(metadata, onComplete) + onCompleted: metadata => this._onLoginCompleted(metadata, authToken, onComplete) }) this.write(RequestMessage.hello(userAgent, authToken), observer, true) diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v4x1.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v4x1.js index 001f41e68..d3e813602 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v4x1.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v4x1.js @@ -75,7 +75,7 @@ export default class BoltProtocol extends BoltProtocolV4 { initialize ({ userAgent, authToken, onError, onComplete } = {}) { const observer = new LoginObserver({ onError: error => this._onLoginError(error, onError), - onCompleted: metadata => this._onLoginCompleted(metadata, onComplete) + onCompleted: metadata => this._onLoginCompleted(metadata, authToken, onComplete) }) this.write( diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v4x3.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v4x3.js index 86d62a1f1..68d50bad7 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v4x3.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v4x3.js @@ -94,7 +94,7 @@ export default class BoltProtocol extends BoltProtocolV42 { if (metadata.patch_bolt !== undefined) { this._applyPatches(metadata.patch_bolt) } - return this._onLoginCompleted(metadata, onComplete) + return this._onLoginCompleted(metadata, authToken, onComplete) } }) diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x0.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x0.js index 4e30ccb0b..d29f73044 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x0.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x0.js @@ -54,7 +54,7 @@ export default class BoltProtocol extends BoltProtocolV44 { initialize ({ userAgent, authToken, onError, onComplete } = {}) { const observer = new LoginObserver({ onError: error => this._onLoginError(error, onError), - onCompleted: metadata => this._onLoginCompleted(metadata, onComplete) + onCompleted: metadata => this._onLoginCompleted(metadata, authToken, onComplete) }) this.write( diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x1.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x1.js new file mode 100644 index 000000000..4dda70917 --- /dev/null +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x1.js @@ -0,0 +1,132 @@ +/** + * Copyright (c) "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 BoltProtocolV5x0 from './bolt-protocol-v5x0.js' + +import transformersFactories from './bolt-protocol-v5x1.transformer.js' +import Transformer from './transformer.js' +import RequestMessage from './request-message.js' +import { LoginObserver, LogoffObserver } from './stream-observers.js' + +import { internal } from '../../core/index.ts' + +const { + constants: { BOLT_PROTOCOL_V5_1 } +} = internal + +export default class BoltProtocol extends BoltProtocolV5x0 { + get version () { + return BOLT_PROTOCOL_V5_1 + } + + get transformer () { + if (this._transformer === undefined) { + this._transformer = new Transformer(Object.values(transformersFactories).map(create => create(this._config, this._log))) + } + return this._transformer + } + + get supportsReAuth () { + return true + } + + /** + * Initialize a connection with the server + * + * @param {Object} param0 The params + * @param {string} param0.userAgent The user agent + * @param {any} param0.authToken The auth token + * @param {function(error)} param0.onError On error callback + * @param {function(onComplte)} param0.onComplete On complete callback + * @returns {LoginObserver} The Login observer + */ + initialize ({ userAgent, authToken, onError, onComplete } = {}) { + const state = {} + const observer = new LoginObserver({ + onError: error => this._onLoginError(error, onError), + onCompleted: metadata => { + state.metadata = metadata + return this._onLoginCompleted(metadata) + } + }) + + this.write( + RequestMessage.hello5x1(userAgent, this._serversideRouting), + observer, + false + ) + + return this.logon({ + authToken, + onComplete: metadata => onComplete({ ...metadata, ...state.metadata }), + onError, + flush: true + }) + } + + /** + * Performs login of the underlying connection + * + * @param {Object} args + * @param {Object} args.authToken the authentication token. + * @param {function(err: Error)} args.onError the callback to invoke on error. + * @param {function()} args.onComplete the callback to invoke on completion. + * @param {boolean} args.flush whether to flush the buffered messages. + * + * @returns {StreamObserver} the stream observer that monitors the corresponding server response. + */ + logon ({ authToken, onComplete, onError, flush } = {}) { + const observer = new LoginObserver({ + onCompleted: () => this._onLoginCompleted(null, authToken, onComplete), + onError: (error) => this._onLoginError(error, onError) + }) + + this.write( + RequestMessage.logon(authToken), + observer, + flush + ) + + return observer + } + + /** + * Performs logoff of the underlying connection + * + * @param {Object} param + * @param {function(err: Error)} param.onError the callback to invoke on error. + * @param {function()} param.onComplete the callback to invoke on completion. + * @param {boolean} param.flush whether to flush the buffered messages. + * + * @returns {StreamObserver} the stream observer that monitors the corresponding server response. + */ + logoff ({ onComplete, onError, flush } = {}) { + const observer = new LogoffObserver({ + onCompleted: onComplete, + onError: onError + }) + + this.write( + RequestMessage.logoff(), + observer, + flush + ) + + return observer + } +} diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x1.transformer.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x1.transformer.js new file mode 100644 index 000000000..b8583e846 --- /dev/null +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x1.transformer.js @@ -0,0 +1,24 @@ +/** + * Copyright (c) "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 v5x0 from './bolt-protocol-v5x0.transformer.js' + +export default { + ...v5x0 +} diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/create.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/create.js index 9a4549cd6..45c33eef2 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/create.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/create.js @@ -27,6 +27,7 @@ import BoltProtocolV4x2 from './bolt-protocol-v4x2.js' import BoltProtocolV4x3 from './bolt-protocol-v4x3.js' import BoltProtocolV4x4 from './bolt-protocol-v4x4.js' import BoltProtocolV5x0 from './bolt-protocol-v5x0.js' +import BoltProtocolV5x1 from './bolt-protocol-v5x1.js' // eslint-disable-next-line no-unused-vars import { Chunker, Dechunker } from '../channel/index.js' import ResponseHandler from './response-handler.js' @@ -191,6 +192,16 @@ function createProtocol ( onProtocolError, serversideRouting ) + case 5.1: + return new BoltProtocolV5x1( + server, + chunker, + packingConfig, + createResponseHandler, + log, + onProtocolError, + serversideRouting + ) default: throw newError('Unknown Bolt protocol version: ' + version) } diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js index 515458ee5..f8c0de714 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js @@ -76,7 +76,7 @@ function parseNegotiatedResponse (buffer) { */ function newHandshakeBuffer () { return createHandshakeMessage([ - version(5, 0), + [version(5, 1), version(5, 0)], [version(4, 4), version(4, 2)], version(4, 1), version(3, 0) diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/request-message.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/request-message.js index 0ceb430c0..fc9d6a07d 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/request-message.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/request-message.js @@ -40,6 +40,9 @@ const COMMIT = 0x12 // 0001 0010 // COMMIT const ROLLBACK = 0x13 // 0001 0011 // ROLLBACK const ROUTE = 0x66 // 0110 0110 // ROUTE +const LOGON = 0x6A // LOGON +const LOGOFF = 0x6B // LOGOFF + const DISCARD = 0x2f // 0010 1111 // DISCARD const PULL = 0x3f // 0011 1111 // PULL @@ -48,6 +51,23 @@ const READ_MODE = 'r' const NO_STATEMENT_ID = -1 +const SIGNATURES = Object.freeze({ + INIT, + RESET, + RUN, + PULL_ALL, + HELLO, + GOODBYE, + BEGIN, + COMMIT, + ROLLBACK, + ROUTE, + LOGON, + LOGOFF, + DISCARD, + PULL +}) + export default class RequestMessage { constructor (signature, fields, toString) { this.signature = signature @@ -121,6 +141,52 @@ export default class RequestMessage { ) } + /** + * Create a new HELLO message. + * @param {string} userAgent the user agent. + * @param {Object} optional server side routing, set to routing context to turn on server side routing (> 4.1) + * @return {RequestMessage} new HELLO message. + */ + static hello5x1 (userAgent, routing = null) { + const metadata = { user_agent: userAgent } + if (routing) { + metadata.routing = routing + } + + return new RequestMessage( + HELLO, + [metadata], + () => `HELLO {user_agent: '${userAgent}', ...}` + ) + } + + /** + * Create a new LOGON message. + * + * @param {object} authToken The auth token + * @returns {RequestMessage} new LOGON message + */ + static logon (authToken) { + return new RequestMessage( + LOGON, + [authToken], + () => 'LOGON { ... }' + ) + } + + /** + * Create a new LOGOFF message. + * + * @returns {RequestMessage} new LOGOFF message + */ + static logoff () { + return new RequestMessage( + LOGOFF, + [], + () => 'LOGOFF' + ) + } + /** * Create a new BEGIN message. * @param {Bookmarks} bookmarks the bookmarks. @@ -327,3 +393,7 @@ const RESET_MESSAGE = new RequestMessage(RESET, [], () => 'RESET') const COMMIT_MESSAGE = new RequestMessage(COMMIT, [], () => 'COMMIT') const ROLLBACK_MESSAGE = new RequestMessage(ROLLBACK, [], () => 'ROLLBACK') const GOODBYE_MESSAGE = new RequestMessage(GOODBYE, [], () => 'GOODBYE') + +export { + SIGNATURES +} diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/stream-observers.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/stream-observers.js index d8ff0b88d..849c8b674 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/stream-observers.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/stream-observers.js @@ -451,6 +451,38 @@ class LoginObserver extends StreamObserver { } } +class LogoffObserver extends StreamObserver { + /** + * + * @param {Object} param - + * @param {function(err: Error)} param.onError + * @param {function(metadata)} param.onCompleted + */ + constructor ({ onError, onCompleted } = {}) { + super() + this._onError = onError + this._onCompleted = onCompleted + } + + onNext (record) { + this.onError( + newError('Received RECORD when logging off ' + json.stringify(record)) + ) + } + + onError (error) { + if (this._onError) { + this._onError(error) + } + } + + onCompleted (metadata) { + if (this._onCompleted) { + this._onCompleted(metadata) + } + } +} + class ResetObserver extends StreamObserver { /** * @@ -671,6 +703,7 @@ export { StreamObserver, ResultStreamObserver, LoginObserver, + LogoffObserver, ResetObserver, FailedObserver, CompletedObserver, diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-pooled.js b/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-pooled.js index 208cbd585..03651b15b 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-pooled.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/connection-provider/connection-provider-pooled.js @@ -121,7 +121,7 @@ export default class PooledConnectionProvider extends ConnectionProvider { const connection = await this._connectionPool.acquire(address) const serverInfo = new ServerInfo(connection.server, connection.protocol().version) try { - if (!connection.protocol().isLastMessageLogin()) { + if (!connection.protocol().isLastMessageLogon()) { await connection.resetAndFlush() } } finally { diff --git a/packages/neo4j-driver-deno/lib/core/internal/constants.ts b/packages/neo4j-driver-deno/lib/core/internal/constants.ts index 39e790f2f..1b9519387 100644 --- a/packages/neo4j-driver-deno/lib/core/internal/constants.ts +++ b/packages/neo4j-driver-deno/lib/core/internal/constants.ts @@ -34,6 +34,7 @@ const BOLT_PROTOCOL_V4_2: number = 4.2 const BOLT_PROTOCOL_V4_3: number = 4.3 const BOLT_PROTOCOL_V4_4: number = 4.4 const BOLT_PROTOCOL_V5_0: number = 5.0 +const BOLT_PROTOCOL_V5_1: number = 5.1 export { FETCH_ALL, @@ -50,5 +51,6 @@ export { BOLT_PROTOCOL_V4_2, BOLT_PROTOCOL_V4_3, BOLT_PROTOCOL_V4_4, - BOLT_PROTOCOL_V5_0 + BOLT_PROTOCOL_V5_0, + BOLT_PROTOCOL_V5_1 } diff --git a/packages/testkit-backend/src/feature/common.js b/packages/testkit-backend/src/feature/common.js index aa8d33b70..bd270cf3e 100644 --- a/packages/testkit-backend/src/feature/common.js +++ b/packages/testkit-backend/src/feature/common.js @@ -16,6 +16,7 @@ const features = [ 'Feature:Bolt:4.3', 'Feature:Bolt:4.4', 'Feature:Bolt:5.0', + 'Feature:Bolt:5.1', 'Feature:Bolt:Patch:UTC', 'Feature:API:ConnectionAcquisitionTimeout', 'Feature:API:Driver.ExecuteQuery', @@ -24,7 +25,8 @@ const features = [ 'Optimization:EagerTransactionBegin', 'Optimization:ImplicitDefaultArguments', 'Optimization:MinimalBookmarksSet', - 'Optimization:MinimalResets' + 'Optimization:MinimalResets', + 'Optimization:AuthPipelining' ] export default features