Skip to content

Commit 163a240

Browse files
committed
Type checks for string parameters
Commit adds assertions that given parameter is string to: * `neo4j.driver(boltUrl)` * `session.run(cypher)` * `session.beginTransaction(bookmark)` * `transaction.run(cypher)`
1 parent 8291a56 commit 163a240

File tree

8 files changed

+178
-22
lines changed

8 files changed

+178
-22
lines changed

src/v1/index.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {Driver, READ, WRITE} from './driver';
2727
import RoutingDriver from './routing-driver';
2828
import VERSION from '../version';
2929
import {parseScheme, parseUrl} from "./internal/connector";
30+
import {assertString} from "./internal/util";
3031

3132

3233
const auth ={
@@ -107,7 +108,8 @@ let USER_AGENT = "neo4j-javascript/" + VERSION;
107108
* @returns {Driver}
108109
*/
109110
function driver(url, authToken, config = {}) {
110-
let scheme = parseScheme(url);
111+
assertString(url, 'Bolt URL');
112+
const scheme = parseScheme(url);
111113
if (scheme === "bolt+routing://") {
112114
return new RoutingDriver(parseUrl(url), USER_AGENT, authToken, config);
113115
} else if (scheme === "bolt://") {

src/v1/internal/util.js

+37-12
Original file line numberDiff line numberDiff line change
@@ -20,22 +20,47 @@
2020
const ENCRYPTION_ON = "ENCRYPTION_ON";
2121
const ENCRYPTION_OFF = "ENCRYPTION_OFF";
2222

23-
function isEmptyObjectOrNull(object) {
24-
if (!object) {
25-
return true;
26-
}
23+
function isEmptyObjectOrNull(obj) {
24+
if (isNull(obj)) {
25+
return true;
26+
}
27+
28+
if (!isObject(obj)) {
29+
return false;
30+
}
2731

28-
for (let prop in object) {
29-
if (object.hasOwnProperty(prop)) {
30-
return false;
31-
}
32+
for (let prop in obj) {
33+
if (obj.hasOwnProperty(prop)) {
34+
return false;
3235
}
36+
}
3337

34-
return true;
38+
return true;
39+
}
40+
41+
function isNull(obj) {
42+
return obj === null;
43+
}
44+
45+
function isObject(obj) {
46+
const type = typeof obj;
47+
return type === 'function' || type === 'object' && Boolean(obj);
48+
}
49+
50+
function assertString(obj, objName) {
51+
if (!isString(obj)) {
52+
throw new TypeError(objName + ' expected to be string but was: ' + JSON.stringify(obj));
53+
}
54+
return obj;
55+
}
56+
57+
function isString(str) {
58+
return Object.prototype.toString.call(str) === '[object String]';
3559
}
3660

3761
export {
38-
isEmptyObjectOrNull,
39-
ENCRYPTION_ON,
40-
ENCRYPTION_OFF
62+
isEmptyObjectOrNull,
63+
assertString,
64+
ENCRYPTION_ON,
65+
ENCRYPTION_OFF
4166
}

src/v1/session.js

+11-7
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,11 @@
1616
* See the License for the specific language governing permissions and
1717
* limitations under the License.
1818
*/
19-
20-
import StreamObserver from './internal/stream-observer';
21-
import Result from './result';
22-
import Transaction from './transaction';
23-
import Integer from "./integer";
24-
import {int} from "./integer";
19+
import StreamObserver from "./internal/stream-observer";
20+
import Result from "./result";
21+
import Transaction from "./transaction";
2522
import {newError} from "./error";
23+
import {assertString} from "./internal/util";
2624

2725
/**
2826
* A Session instance is used for handling the connection and
@@ -55,7 +53,9 @@ class Session {
5553
parameters = statement.parameters || {};
5654
statement = statement.text;
5755
}
58-
let streamObserver = new _RunObserver(this._onRunFailure());
56+
assertString(statement, "Cypher statement");
57+
58+
const streamObserver = new _RunObserver(this._onRunFailure());
5959
if (!this._hasTx) {
6060
this._connectionPromise.then((conn) => {
6161
streamObserver.resolveConnection(conn);
@@ -80,6 +80,10 @@ class Session {
8080
* @returns {Transaction} - New Transaction
8181
*/
8282
beginTransaction(bookmark) {
83+
if (bookmark) {
84+
assertString(bookmark, "Bookmark");
85+
}
86+
8387
if (this._hasTx) {
8488
throw newError("You cannot begin a transaction on a session with an "
8589
+ "open transaction; either run from within the transaction or use a "

src/v1/transaction.js

+3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
*/
1919
import StreamObserver from './internal/stream-observer';
2020
import Result from './result';
21+
import {assertString} from './internal/util';
2122

2223
/**
2324
* Represents a transaction in the Neo4j database.
@@ -65,6 +66,8 @@ class Transaction {
6566
parameters = statement.parameters || {};
6667
statement = statement.text;
6768
}
69+
assertString(statement, "Cypher statement");
70+
6871
return this._state.run(this._connectionPromise, new _TransactionStreamObserver(this), statement, parameters);
6972
}
7073

test/internal/util.test.js

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/**
2+
* Copyright (c) 2002-2017 "Neo Technology,","
3+
* Network Engine for Objects in Lund AB [http://neotechnology.com]
4+
*
5+
* This file is part of Neo4j.
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
20+
const util = require('../../lib/v1/internal/util.js');
21+
22+
describe('util', () => {
23+
24+
it('should check empty objects', () => {
25+
expect(util.isEmptyObjectOrNull(null)).toBeTruthy();
26+
expect(util.isEmptyObjectOrNull({})).toBeTruthy();
27+
expect(util.isEmptyObjectOrNull([])).toBeTruthy();
28+
29+
const func = () => {
30+
return 42;
31+
};
32+
expect(util.isEmptyObjectOrNull(func)).toBeTruthy();
33+
func.foo = 'bar';
34+
expect(util.isEmptyObjectOrNull(func)).toBeFalsy();
35+
36+
expect(util.isEmptyObjectOrNull()).toBeFalsy();
37+
expect(util.isEmptyObjectOrNull(undefined)).toBeFalsy();
38+
expect(util.isEmptyObjectOrNull(0)).toBeFalsy();
39+
expect(util.isEmptyObjectOrNull('')).toBeFalsy();
40+
expect(util.isEmptyObjectOrNull('abc')).toBeFalsy();
41+
expect(util.isEmptyObjectOrNull({foo: 'bar'})).toBeFalsy();
42+
});
43+
44+
it('should check strings', () => {
45+
verifyValidString('');
46+
verifyValidString(new String('foo'));
47+
verifyValidString(String('foo'));
48+
verifyValidString("hi!");
49+
50+
verifyInvalidString({});
51+
verifyInvalidString({foo: 1});
52+
verifyInvalidString([]);
53+
verifyInvalidString(['1']);
54+
verifyInvalidString([1, '2']);
55+
verifyInvalidString(console.log);
56+
});
57+
58+
function verifyValidString(str) {
59+
expect(util.assertString(str, 'Test string')).toBe(str);
60+
}
61+
62+
function verifyInvalidString(str) {
63+
expect(() => util.assertString(str, 'Test string')).toThrowError(TypeError);
64+
}
65+
66+
});

test/v1/driver.test.js

+14-2
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,20 @@ describe('driver', function() {
5656
driver.session();
5757
});
5858

59-
it('should handle wrong scheme ', function() {
60-
expect(function(){neo4j.driver("tank://localhost", neo4j.auth.basic("neo4j", "neo4j"))}).toThrow(new Error("Unknown scheme: tank://"));
59+
it('should handle wrong scheme', () => {
60+
expect(() => neo4j.driver("tank://localhost", neo4j.auth.basic("neo4j", "neo4j")))
61+
.toThrow(new Error("Unknown scheme: tank://"));
62+
});
63+
64+
it('should handle URL parameter string', () => {
65+
expect(() => neo4j.driver({uri: 'bolt://localhost'})).toThrowError(TypeError);
66+
67+
expect(() => neo4j.driver(['bolt:localhost'])).toThrowError(TypeError);
68+
69+
expect(() => {
70+
const driver = neo4j.driver(String('bolt://localhost', neo4j.auth.basic("neo4j", "neo4j")));
71+
return driver.session();
72+
}).toBeDefined();
6173
});
6274

6375
it('should fail early on wrong credentials', function(done) {

test/v1/session.test.js

+19
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,25 @@ describe('session', function () {
356356
done();
357357
})
358358
});
359+
360+
it('should fail nicely for illegal statement', () => {
361+
expect(() => session.run()).toThrowError(TypeError);
362+
expect(() => session.run(null)).toThrowError(TypeError);
363+
expect(() => session.run({})).toThrowError(TypeError);
364+
expect(() => session.run(42)).toThrowError(TypeError);
365+
expect(() => session.run([])).toThrowError(TypeError);
366+
expect(() => session.run(['CREATE ()'])).toThrowError(TypeError);
367+
368+
expect(() => session.run({statement: 'CREATE ()'})).toThrowError(TypeError);
369+
expect(() => session.run({cypher: 'CREATE ()'})).toThrowError(TypeError);
370+
});
371+
372+
it('should fail nicely for illegal bookmark', () => {
373+
expect(() => session.beginTransaction({})).toThrowError(TypeError);
374+
expect(() => session.beginTransaction(42)).toThrowError(TypeError);
375+
expect(() => session.beginTransaction([])).toThrowError(TypeError);
376+
expect(() => session.beginTransaction(['bookmark'])).toThrowError(TypeError);
377+
});
359378
});
360379

361380

test/v1/transaction.test.js

+25
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,31 @@ describe('transaction', () => {
374374
});
375375
});
376376

377+
it('should fail nicely for illegal statement', () => {
378+
const tx = session.beginTransaction();
379+
380+
expect(() => tx.run()).toThrowError(TypeError);
381+
expect(() => tx.run(null)).toThrowError(TypeError);
382+
expect(() => tx.run({})).toThrowError(TypeError);
383+
expect(() => tx.run(42)).toThrowError(TypeError);
384+
expect(() => tx.run([])).toThrowError(TypeError);
385+
expect(() => tx.run(['CREATE ()'])).toThrowError(TypeError);
386+
387+
expect(() => tx.run({statement: 'CREATE ()'})).toThrowError(TypeError);
388+
expect(() => tx.run({cypher: 'CREATE ()'})).toThrowError(TypeError);
389+
});
390+
391+
it('should accept a statement object ', done => {
392+
const tx = session.beginTransaction();
393+
const statement = {text: "RETURN 1 AS a"};
394+
395+
tx.run(statement).then(result => {
396+
expect(result.records.length).toBe(1);
397+
expect(result.records[0].get('a').toInt()).toBe(1);
398+
done();
399+
}).catch(console.log);
400+
});
401+
377402
function expectSyntaxError(error) {
378403
const code = error.fields[0].code;
379404
expect(code).toBe('Neo.ClientError.Statement.SyntaxError');

0 commit comments

Comments
 (0)