diff --git a/src/v1/driver.js b/src/v1/driver.js index c5a732d41..a3cce792c 100644 --- a/src/v1/driver.js +++ b/src/v1/driver.js @@ -68,7 +68,7 @@ class Driver { */ _createConnection(url, release) { let sessionId = this._sessionIdGenerator++; - let conn = connect(url, this._config); + let conn = connect(url, this._config, this._connectionErrorCode()); let streamObserver = new _ConnectionStreamObserver(this, conn); conn.initialize(this._userAgent, this._token, streamObserver); conn._id = sessionId; @@ -126,16 +126,22 @@ class Driver { return mode; } - //Extension point + // Extension point _createConnectionProvider(address, connectionPool, driverOnErrorCallback) { return new DirectConnectionProvider(address, connectionPool, driverOnErrorCallback); } - //Extension point + // Extension point _createSession(mode, connectionProvider, bookmark, config) { return new Session(mode, connectionProvider, bookmark, config); } + // Extension point + _connectionErrorCode() { + // connection errors might result in different error codes depending on the driver + return SERVICE_UNAVAILABLE; + } + _driverOnErrorCallback(error) { const userDefinedOnErrorCallback = this.onError; if (userDefinedOnErrorCallback && error.code === SERVICE_UNAVAILABLE) { diff --git a/src/v1/internal/ch-config.js b/src/v1/internal/ch-config.js new file mode 100644 index 000000000..faf6ba10a --- /dev/null +++ b/src/v1/internal/ch-config.js @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2002-2017 "Neo Technology,"," + * Network Engine for Objects in Lund AB [http://neotechnology.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 hasFeature from './features'; +import {SERVICE_UNAVAILABLE} from '../error'; + +export default class ChannelConfig { + + constructor(host, port, driverConfig, connectionErrorCode) { + this.host = host; + this.port = port; + this.encrypted = ChannelConfig._extractEncrypted(driverConfig); + this.trust = ChannelConfig._extractTrust(driverConfig); + this.trustedCertificates = ChannelConfig._extractTrustedCertificates(driverConfig); + this.knownHostsPath = ChannelConfig._extractKnownHostsPath(driverConfig); + this.connectionErrorCode = connectionErrorCode || SERVICE_UNAVAILABLE; + } + + static _extractEncrypted(driverConfig) { + // check if encryption was configured by the user, use explicit null check because we permit boolean value + const encryptionConfigured = driverConfig.encrypted == null; + // default to using encryption if trust-all-certificates is available + return encryptionConfigured ? hasFeature('trust_all_certificates') : driverConfig.encrypted; + } + + static _extractTrust(driverConfig) { + if (driverConfig.trust) { + return driverConfig.trust; + } + // default to using TRUST_ALL_CERTIFICATES if it is available + return hasFeature('trust_all_certificates') ? 'TRUST_ALL_CERTIFICATES' : 'TRUST_CUSTOM_CA_SIGNED_CERTIFICATES'; + } + + static _extractTrustedCertificates(driverConfig) { + return driverConfig.trustedCertificates || []; + } + + static _extractKnownHostsPath(driverConfig) { + return driverConfig.knownHosts || null; + } +}; diff --git a/src/v1/internal/ch-dummy.js b/src/v1/internal/ch-dummy.js index b24f060ed..433799336 100644 --- a/src/v1/internal/ch-dummy.js +++ b/src/v1/internal/ch-dummy.js @@ -26,16 +26,24 @@ const observer = { }; class DummyChannel { - constructor(opts) { + + /** + * @constructor + * @param {ChannelConfig} config - configuration for the new channel. + */ + constructor(config) { this.written = []; } + isEncrypted() { return false; } + write( buf ) { this.written.push(buf); observer.updateInstance(this); } + toHex() { var out = ""; for( var i=0; i fs.readFileSync(f)), + ca: config.trustedCertificates.map((f) => fs.readFileSync(f)), // Because we manually check for this in the connect callback, to give // a more helpful error to the user rejectUnauthorized: false }; - let socket = tls.connect(opts.port, opts.host, tlsOpts, function () { + let socket = tls.connect(config.port, config.host, tlsOpts, function () { if (!socket.authorized) { onFailure(newError("Server certificate is not trusted. If you trust the database you are connecting to, add" + " the signing certificate, or the server certificate, to the list of certificates trusted by this driver" + @@ -145,14 +145,14 @@ const TrustStrategy = { socket.on('error', onFailure); return socket; }, - TRUST_SYSTEM_CA_SIGNED_CERTIFICATES : function( opts, onSuccess, onFailure ) { + TRUST_SYSTEM_CA_SIGNED_CERTIFICATES : function( config, onSuccess, onFailure ) { let tlsOpts = { // Because we manually check for this in the connect callback, to give // a more helpful error to the user rejectUnauthorized: false }; - let socket = tls.connect(opts.port, opts.host, tlsOpts, function () { + let socket = tls.connect(config.port, config.host, tlsOpts, function () { if (!socket.authorized) { onFailure(newError("Server certificate is not trusted. If you trust the database you are connecting to, use " + "TRUST_CUSTOM_CA_SIGNED_CERTIFICATES and add" + @@ -171,7 +171,7 @@ const TrustStrategy = { /** * @deprecated in 1.1 in favour of {@link #TRUST_ALL_CERTIFICATES}. Will be deleted in a future version. */ - TRUST_ON_FIRST_USE : function( opts, onSuccess, onFailure ) { + TRUST_ON_FIRST_USE : function( config, onSuccess, onFailure ) { console.log("`TRUST_ON_FIRST_USE` has been deprecated as option and will be removed in a future version of " + "the driver. Please use `TRUST_ALL_CERTIFICATES` instead."); @@ -180,7 +180,7 @@ const TrustStrategy = { rejectUnauthorized: false }; - let socket = tls.connect(opts.port, opts.host, tlsOpts, function () { + let socket = tls.connect(config.port, config.host, tlsOpts, function () { var serverCert = socket.getPeerCertificate(/*raw=*/true); if( !serverCert.raw ) { @@ -195,9 +195,9 @@ const TrustStrategy = { return; } - var serverFingerprint = require('crypto').createHash('sha512').update(serverCert.raw).digest("hex"); - let knownHostsPath = opts.knownHosts || path.join(userHome(), ".neo4j", "known_hosts"); - let serverId = opts.host + ":" + opts.port; + const serverFingerprint = require('crypto').createHash('sha512').update(serverCert.raw).digest("hex"); + const knownHostsPath = config.knownHostsPath || path.join(userHome(), ".neo4j", "known_hosts"); + const serverId = config.host + ":" + config.port; loadFingerprint(serverId, knownHostsPath, (knownFingerprint) => { if( knownFingerprint === serverFingerprint ) { @@ -228,11 +228,11 @@ const TrustStrategy = { return socket; }, - TRUST_ALL_CERTIFICATES: function (opts, onSuccess, onFailure) { + TRUST_ALL_CERTIFICATES: function (config, onSuccess, onFailure) { const tlsOpts = { rejectUnauthorized: false }; - const socket = tls.connect(opts.port, opts.host, tlsOpts, function () { + const socket = tls.connect(config.port, config.host, tlsOpts, function () { const certificate = socket.getPeerCertificate(); if (isEmptyObjectOrNull(certificate)) { onFailure(newError("Secure connection was successful but server did not return any valid " + @@ -249,16 +249,23 @@ const TrustStrategy = { } }; -function connect( opts, onSuccess, onFailure=(()=>null) ) { +/** + * Connect using node socket. + * @param {ChannelConfig} config - configuration of this channel. + * @param {function} onSuccess - callback to execute on connection success. + * @param {function} onFailure - callback to execute on connection failure. + * @return {*} socket connection. + */ +function connect( config, onSuccess, onFailure=(()=>null) ) { //still allow boolean for backwards compatibility - if (opts.encrypted === false || opts.encrypted === ENCRYPTION_OFF) { - var conn = net.connect(opts.port, opts.host, onSuccess); + if (config.encrypted === false || config.encrypted === ENCRYPTION_OFF) { + var conn = net.connect(config.port, config.host, onSuccess); conn.on('error', onFailure); return conn; - } else if( TrustStrategy[opts.trust]) { - return TrustStrategy[opts.trust](opts, onSuccess, onFailure); + } else if( TrustStrategy[config.trust]) { + return TrustStrategy[config.trust](config, onSuccess, onFailure); } else { - onFailure(newError("Unknown trust strategy: " + opts.trust + ". Please use either " + + onFailure(newError("Unknown trust strategy: " + config.trust + ". Please use either " + "trust:'TRUST_CUSTOM_CA_SIGNED_CERTIFICATES' or trust:'TRUST_ALL_CERTIFICATES' in your driver " + "configuration. Alternatively, you can disable encryption by setting " + "`encrypted:\"" + ENCRYPTION_OFF + "\"`. There is no mechanism to use encryption without trust verification, " + @@ -277,11 +284,9 @@ class NodeChannel { /** * Create new instance - * @param {Object} opts - Options object - * @param {string} opts.host - The host, including protocol to connect to. - * @param {Integer} opts.port - The port to use. + * @param {ChannelConfig} config - configuration for this channel. */ - constructor (opts) { + constructor (config) { let self = this; this.id = _CONNECTION_IDGEN++; @@ -291,9 +296,10 @@ class NodeChannel { this._error = null; this._handleConnectionError = this._handleConnectionError.bind(this); this._handleConnectionTerminated = this._handleConnectionTerminated.bind(this); + this._connectionErrorCode = config.connectionErrorCode; - this._encrypted = opts.encrypted; - this._conn = connect(opts, () => { + this._encrypted = config.encrypted; + this._conn = connect(config, () => { if(!self._open) { return; } @@ -318,14 +324,14 @@ class NodeChannel { _handleConnectionError( err ) { let msg = err.message || 'Failed to connect to server'; - this._error = newError(msg, SESSION_EXPIRED); + this._error = newError(msg, this._connectionErrorCode); if( this.onerror ) { this.onerror(this._error); } } _handleConnectionTerminated() { - this._error = newError('Connection was closed by server', SESSION_EXPIRED); + this._error = newError('Connection was closed by server', this._connectionErrorCode); if( this.onerror ) { this.onerror(this._error); } diff --git a/src/v1/internal/ch-websocket.js b/src/v1/internal/ch-websocket.js index e16c421c6..c937fded7 100644 --- a/src/v1/internal/ch-websocket.js +++ b/src/v1/internal/ch-websocket.js @@ -19,7 +19,7 @@ import {HeapBuffer} from './buf'; import {newError} from './../error'; -import {ENCRYPTION_ON, ENCRYPTION_OFF} from './util'; +import {ENCRYPTION_OFF, ENCRYPTION_ON} from './util'; /** * Create a new WebSocketChannel to be used in web browsers. @@ -29,33 +29,32 @@ class WebSocketChannel { /** * Create new instance - * @param {Object} opts - Options object - * @param {string} opts.host - The host, including protocol to connect to. - * @param {Integer} opts.port - The port to use. + * @param {ChannelConfig} config - configuration for this channel. */ - constructor (opts) { + constructor(config) { this._open = true; this._pending = []; this._error = null; this._handleConnectionError = this._handleConnectionError.bind(this); + this._connectionErrorCode = config.connectionErrorCode; - this._encrypted = opts.encrypted; + this._encrypted = config.encrypted; let scheme = "ws"; //Allow boolean for backwards compatibility - if( opts.encrypted === true || opts.encrypted === ENCRYPTION_ON) { - if((!opts.trust) || opts.trust === "TRUST_CUSTOM_CA_SIGNED_CERTIFICATES" ) { + if (config.encrypted === true || config.encrypted === ENCRYPTION_ON) { + if ((!config.trust) || config.trust === 'TRUST_CUSTOM_CA_SIGNED_CERTIFICATES') { scheme = "wss"; } else { this._error = newError("The browser version of this driver only supports one trust " + - "strategy, 'TRUST_CUSTOM_CA_SIGNED_CERTIFICATES'. "+opts.trust+" is not supported. Please " + + 'strategy, \'TRUST_CUSTOM_CA_SIGNED_CERTIFICATES\'. ' + config.trust + ' is not supported. Please ' + "either use TRUST_CUSTOM_CA_SIGNED_CERTIFICATES or disable encryption by setting " + "`encrypted:\"" + ENCRYPTION_OFF + "\"` in the driver configuration."); return; } } - this._url = scheme + "://" + opts.host + ":" + opts.port; + this._url = scheme + '://' + config.host + ':' + config.port; this._ws = new WebSocket(this._url); this._ws.binaryType = "arraybuffer"; @@ -95,7 +94,7 @@ class WebSocketChannel { "the root cause of the failure. Common reasons include the database being " + "unavailable, using the wrong connection URL or temporary network problems. " + "If you have enabled encryption, ensure your browser is configured to trust the " + - "certificate Neo4j is configured to use. WebSocket `readyState` is: " + this._ws.readyState ); + "certificate Neo4j is configured to use. WebSocket `readyState` is: " + this._ws.readyState, this._connectionErrorCode ); if (this.onerror) { this.onerror(this._error); } diff --git a/src/v1/internal/connection-providers.js b/src/v1/internal/connection-providers.js index 3b2fa25c5..a2e5e94e7 100644 --- a/src/v1/internal/connection-providers.js +++ b/src/v1/internal/connection-providers.js @@ -17,7 +17,7 @@ * limitations under the License. */ -import {newError, SERVICE_UNAVAILABLE, SESSION_EXPIRED} from '../error'; +import {newError, SERVICE_UNAVAILABLE} from '../error'; import {READ, WRITE} from '../driver'; import Session from '../session'; import RoundRobinArray from './round-robin-array'; @@ -96,7 +96,7 @@ export class LoadBalancer extends ConnectionProvider { _acquireConnectionToServer(serversRoundRobinArray, serverName) { const address = serversRoundRobinArray.next(); if (!address) { - return Promise.reject(newError('No ' + serverName + ' servers available', SESSION_EXPIRED)); + return Promise.reject(newError('No ' + serverName + ' servers available', SERVICE_UNAVAILABLE)); } return this._connectionPool.acquire(address); } diff --git a/src/v1/internal/connector.js b/src/v1/internal/connector.js index 9aae15831..9d19de16e 100644 --- a/src/v1/internal/connector.js +++ b/src/v1/internal/connector.js @@ -19,11 +19,11 @@ import WebSocketChannel from './ch-websocket'; import NodeChannel from './ch-node'; import {Chunker, Dechunker} from './chunking'; -import hasFeature from './features'; import {Packer, Unpacker} from './packstream'; import {alloc} from './buf'; import {Node, Path, PathSegment, Relationship, UnboundRelationship} from '../graph-types'; import {newError} from './../error'; +import ChannelConfig from './ch-config'; let Channel; if( NodeChannel.available ) { @@ -475,25 +475,18 @@ class Connection { * Crete new connection to the provided url. * @access private * @param {string} url - 'neo4j'-prefixed URL to Neo4j Bolt endpoint - * @param {object} config + * @param {object} config - this driver configuration + * @param {string=null} connectionErrorCode - error code for errors raised on connection errors * @return {Connection} - New connection */ -function connect( url, config = {}) { - let Ch = config.channel || Channel; +function connect(url, config = {}, connectionErrorCode = null) { + const Ch = config.channel || Channel; const host = parseHost(url); const port = parsePort(url) || 7687; const completeUrl = host + ':' + port; + const channelConfig = new ChannelConfig(host, port, config, connectionErrorCode); - return new Connection( new Ch({ - host: parseHost(url), - port: parsePort(url) || 7687, - // Default to using encryption if trust-on-first-use is available - encrypted : (config.encrypted == null) ? hasFeature("trust_all_certificates") : config.encrypted, - // Default to using TRUST_ALL_CERTIFICATES if it is available - trust : config.trust || (hasFeature("trust_all_certificates") ? "TRUST_ALL_CERTIFICATES" : "TRUST_CUSTOM_CA_SIGNED_CERTIFICATES"), - trustedCertificates : config.trustedCertificates || [], - knownHosts : config.knownHosts - }), completeUrl); + return new Connection( new Ch(channelConfig), completeUrl); } export { diff --git a/src/v1/routing-driver.js b/src/v1/routing-driver.js index 0a3a2e17a..adc4548b5 100644 --- a/src/v1/routing-driver.js +++ b/src/v1/routing-driver.js @@ -19,7 +19,7 @@ import Session from './session'; import {Driver} from './driver'; -import {newError, SERVICE_UNAVAILABLE, SESSION_EXPIRED} from './error'; +import {newError, SESSION_EXPIRED} from './error'; import {LoadBalancer} from './internal/connection-providers'; /** @@ -37,13 +37,10 @@ class RoutingDriver extends Driver { _createSession(mode, connectionProvider, bookmark, config) { return new RoutingSession(mode, connectionProvider, bookmark, config, (error, conn) => { - if (error.code === SERVICE_UNAVAILABLE || error.code === SESSION_EXPIRED) { - // connection is undefined if error happened before connection was acquired - if (conn) { - this._connectionProvider.forget(conn.url); - } + if (error.code === SESSION_EXPIRED) { + this._forgetConnection(conn); return error; - } else if (error.code === 'Neo.ClientError.Cluster.NotALeader') { + } else if (RoutingDriver._isFailureToWrite(error)) { let url = 'UNKNOWN'; // connection is undefined if error happened before connection was acquired if (conn) { @@ -57,12 +54,30 @@ class RoutingDriver extends Driver { }); } + _connectionErrorCode() { + // connection errors mean SERVICE_UNAVAILABLE for direct driver but for routing driver they should only + // result in SESSION_EXPIRED because there might still exist other servers capable of serving the request + return SESSION_EXPIRED; + } + + _forgetConnection(connection) { + // connection is undefined if error happened before connection was acquired + if (connection) { + this._connectionProvider.forget(connection.url); + } + } + static _validateConfig(config) { if(config.trust === 'TRUST_ON_FIRST_USE') { throw newError('The chosen trust mode is not compatible with a routing driver'); } return config; } + + static _isFailureToWrite(error) { + return error.code === 'Neo.ClientError.Cluster.NotALeader' || + error.code === 'Neo.ClientError.General.ForbiddenOnReadOnlyDatabase'; + } } class RoutingSession extends Session { diff --git a/test/internal/ch-config.test.js b/test/internal/ch-config.test.js new file mode 100644 index 000000000..3a9abb06a --- /dev/null +++ b/test/internal/ch-config.test.js @@ -0,0 +1,113 @@ +/** + * Copyright (c) 2002-2017 "Neo Technology,"," + * Network Engine for Objects in Lund AB [http://neotechnology.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 ChannelConfig from '../../src/v1/internal/ch-config'; +import hasFeature from '../../src/v1/internal/features'; +import {SERVICE_UNAVAILABLE} from '../../src/v1/error'; + +describe('ChannelConfig', () => { + + it('should respect given host', () => { + const host = 'neo4j.com'; + + const config = new ChannelConfig(host, 42, {}, ''); + + expect(config.host).toEqual(host); + }); + + it('should respect given port', () => { + const port = 4242; + + const config = new ChannelConfig('', port, {}, ''); + + expect(config.port).toEqual(port); + }); + + it('should respect given encrypted conf', () => { + const encrypted = 'ENCRYPTION_ON'; + + const config = new ChannelConfig('', 42, {encrypted: encrypted}, ''); + + expect(config.encrypted).toEqual(encrypted); + }); + + it('should respect given trust conf', () => { + const trust = 'TRUST_ALL_CERTIFICATES'; + + const config = new ChannelConfig('', 42, {trust: trust}, ''); + + expect(config.trust).toEqual(trust); + }); + + it('should respect given trusted certificates conf', () => { + const trustedCertificates = ['./foo.pem', './bar.pem', './baz.pem']; + + const config = new ChannelConfig('', 42, {trustedCertificates: trustedCertificates}, ''); + + expect(config.trustedCertificates).toEqual(trustedCertificates); + }); + + it('should respect given known hosts', () => { + const knownHostsPath = '~/.neo4j/known_hosts'; + + const config = new ChannelConfig('', 42, {knownHosts: knownHostsPath}, ''); + + expect(config.knownHostsPath).toEqual(knownHostsPath); + }); + + it('should respect given connection error code', () => { + const connectionErrorCode = 'ConnectionFailed'; + + const config = new ChannelConfig('', 42, {}, connectionErrorCode); + + expect(config.connectionErrorCode).toEqual(connectionErrorCode); + }); + + it('should use encryption if available but not configured', () => { + const config = new ChannelConfig('', 42, {}, ''); + + expect(config.encrypted).toEqual(hasFeature('trust_all_certificates')); + }); + + it('should use available trust conf when nothing configured', () => { + const config = new ChannelConfig('', 42, {}, ''); + + const availableTrust = hasFeature('trust_all_certificates') ? 'TRUST_ALL_CERTIFICATES' : 'TRUST_CUSTOM_CA_SIGNED_CERTIFICATES'; + expect(config.trust).toEqual(availableTrust); + }); + + it('should have no trusted certificates when not configured', () => { + const config = new ChannelConfig('', 42, {}, ''); + + expect(config.trustedCertificates).toEqual([]); + }); + + it('should have null known hosts path when not configured', () => { + const config = new ChannelConfig('', 42, {}, ''); + + expect(config.knownHostsPath).toBeNull(); + }); + + it('should have service unavailable as default error code', () => { + const config = new ChannelConfig('', 42, {}, ''); + + expect(config.connectionErrorCode).toEqual(SERVICE_UNAVAILABLE); + }); + +}); diff --git a/test/internal/connection-providers.test.js b/test/internal/connection-providers.test.js index 714b0b3de..5a53812a8 100644 --- a/test/internal/connection-providers.test.js +++ b/test/internal/connection-providers.test.js @@ -996,6 +996,30 @@ describe('LoadBalancer', () => { }); }); + it('throws service unavailable when refreshed routing table has no readers', done => { + const pool = newPool(); + const updatedRoutingTable = newRoutingTable( + ['server-A', 'server-B'], + [], + ['server-C', 'server-D'] + ); + const loadBalancer = newLoadBalancer( + ['server-1', 'server-2'], + ['server-3', 'server-4'], + ['server-5', 'server-6'], + pool, + int(0), // expired routing table + { + 'server-1': updatedRoutingTable, + } + ); + + loadBalancer.acquireConnection(READ).catch(error => { + expect(error.code).toEqual(SERVICE_UNAVAILABLE); + done(); + }); + }); + }); function newDirectConnectionProvider(address, pool) { diff --git a/test/v1/direct.driver.boltkit.it.js b/test/v1/direct.driver.boltkit.it.js index b623309bb..ff0b0337e 100644 --- a/test/v1/direct.driver.boltkit.it.js +++ b/test/v1/direct.driver.boltkit.it.js @@ -245,6 +245,31 @@ describe('direct driver', () => { }); }); }); + + it('should throw service unavailable when server dies', done => { + if (!boltkit.BoltKitSupport) { + done(); + return; + } + + const kit = new boltkit.BoltKit(); + const server = kit.start('./test/resources/boltkit/dead_read_server.script', 9001); + + kit.run(() => { + const driver = createDriver(); + const session = driver.session(); + session.run('MATCH (n) RETURN n.name').catch(error => { + expect(error.code).toEqual(neo4j.error.SERVICE_UNAVAILABLE); + + driver.close(); + server.exit(code => { + expect(code).toEqual(0); + done(); + }); + }); + }); + }); + }); function createDriver() { diff --git a/test/v1/driver.test.js b/test/v1/driver.test.js index c483bf4fa..9fa7bfb1e 100644 --- a/test/v1/driver.test.js +++ b/test/v1/driver.test.js @@ -17,38 +17,43 @@ * limitations under the License. */ -var neo4j = require("../../lib/v1"); +import neo4j from '../../src/v1'; -describe('driver', function() { - var driver; - beforeEach(function() { +describe('driver', () => { + + let driver; + + beforeEach(() => { driver = null; - }) - afterEach(function() { + }); + + afterEach(() => { if(driver) { driver.close(); } - }) - it('should expose sessions', function() { + }); + + it('should expose sessions', () => { // Given driver = neo4j.driver("bolt://localhost", neo4j.auth.basic("neo4j", "neo4j")); // When - var session = driver.session(); + const session = driver.session(); // Then expect( session ).not.toBeNull(); driver.close(); }); - it('should handle connection errors', function(done) { + it('should handle connection errors', done => { // Given driver = neo4j.driver("bolt://localhoste", neo4j.auth.basic("neo4j", "neo4j")); // Expect - driver.onError = function (err) { + driver.onError = error => { //the error message is different whether in browser or node - expect(err.message).not.toBeNull(); + expect(error.message).not.toBeNull(); + expect(error.code).toEqual(neo4j.error.SERVICE_UNAVAILABLE); done(); }; @@ -72,12 +77,12 @@ describe('driver', function() { }).toBeDefined(); }); - it('should fail early on wrong credentials', function(done) { + it('should fail early on wrong credentials', done => { // Given driver = neo4j.driver("bolt://localhost", neo4j.auth.basic("neo4j", "who would use such a password")); // Expect - driver.onError = function (err) { + driver.onError = err => { //the error message is different whether in browser or node expect(err.code).toEqual('Neo.ClientError.Security.Unauthorized'); done(); @@ -87,12 +92,12 @@ describe('driver', function() { startNewTransaction(driver); }); - it('should indicate success early on correct credentials', function(done) { + it('should indicate success early on correct credentials', done => { // Given driver = neo4j.driver("bolt://localhost", neo4j.auth.basic("neo4j", "neo4j")); // Expect - driver.onCompleted = function (meta) { + driver.onCompleted = meta => { done(); }; @@ -100,12 +105,12 @@ describe('driver', function() { startNewTransaction(driver); }); - it('should be possible to pass a realm with basic auth tokens', function(done) { + it('should be possible to pass a realm with basic auth tokens', done => { // Given driver = neo4j.driver("bolt://localhost", neo4j.auth.basic("neo4j", "neo4j", "native")); // Expect - driver.onCompleted = function (meta) { + driver.onCompleted = meta => { done(); }; @@ -113,12 +118,12 @@ describe('driver', function() { startNewTransaction(driver); }); - it('should be possible to create custom auth tokens', function(done) { + it('should be possible to create custom auth tokens', done => { // Given driver = neo4j.driver("bolt://localhost", neo4j.auth.custom("neo4j", "neo4j", "native", "basic")); // Expect - driver.onCompleted = function (meta) { + driver.onCompleted = meta => { done(); }; @@ -126,12 +131,12 @@ describe('driver', function() { startNewTransaction(driver); }); - it('should be possible to create custom auth tokens with additional parameters', function(done) { + it('should be possible to create custom auth tokens with additional parameters', done => { // Given driver = neo4j.driver("bolt://localhost", neo4j.auth.custom("neo4j", "neo4j", "native", "basic", {secret: 42})); // Expect - driver.onCompleted = function () { + driver.onCompleted = () => { done(); }; @@ -175,7 +180,7 @@ describe('driver', function() { expect(createRoutingDriverWithTOFU).toThrow(); }); - var exposedTypes = [ + const exposedTypes = [ 'Node', 'Path', 'PathSegment', @@ -187,7 +192,7 @@ describe('driver', function() { ]; exposedTypes.forEach(type => { - it(`should expose type ${type}`, function() { + it(`should expose type ${type}`, () => { expect(undefined === neo4j.types[type]).toBe(false); }); });