Skip to content

Commit

Permalink
fix: remove any-promise
Browse files Browse the repository at this point in the history
Removes the any-promise package. Maintaining support for it proved too much of a hassle for the
value it presented. Promises now have native support in Node and I believe there are better
solutions if you really want to use a different implementation. If you didn't use the
any-promise package then you shouldn't have to make any changes.

BREAKING CHANGE: Removes the any-promise package
  • Loading branch information
jamesfer committed Sep 17, 2020
1 parent e9fb03b commit f624574
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 46 deletions.
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@
"@types/lodash": "^4.14.136",
"@types/node": "^12.6.1",
"any-observable": "^0.5.0",
"any-promise": "^1.3.0",
"lodash": "^4.17.15",
"neo4j-driver": "^4.0.1",
"node-cleanup": "^2.1.2",
Expand Down
36 changes: 15 additions & 21 deletions src/connection.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
// tslint:disable-next-line import-name
import AnyPromise from 'any-promise';
// tslint:disable-next-line import-name
import Observable from 'any-observable';
import { Dictionary, isFunction } from 'lodash';
import nodeCleanup from 'node-cleanup';
Expand Down Expand Up @@ -226,37 +224,33 @@ export class Connection extends Builder<Query> {
* @param {Query} query
* @returns {Promise<Dictionary<R>[]>}
*/
run<R = any>(query: Query): Promise<Dictionary<R>[]> {
async run<R = any>(query: Query): Promise<Dictionary<R>[]> {
if (!this.open) {
return AnyPromise.reject(
new Error('Cannot run query; connection is not open.'),
) as Promise<Dictionary<R>[]>;
throw new Error('Cannot run query; connection is not open.');
}

if (query.getClauses().length === 0) {
return AnyPromise.reject(
new Error('Cannot run query: no clauses attached to the query.'),
) as Promise<Dictionary<R>[]>;
throw new Error('Cannot run query: no clauses attached to the query.');
}

const session = this.session();
if (!session) {
throw Error('Cannot run query: connection is not open.');
throw new Error('Cannot run query: connection is not open.');
}

const queryObj = query.buildQueryObject();
const result = session.run(queryObj.query, queryObj.params);

// Need to wrap promise in an any-promise
return AnyPromise.resolve(result)
.then(async (result) => {
await session.close();
return this.transformer.transformRecords<R>(result.records);
})
.catch(async (error) => {
await session.close();
return Promise.reject(error);
}) as Promise<Dictionary<R>[]>;
return session.run(queryObj.query, queryObj.params)
.then(
async ({ records }) => {
await session.close();
return this.transformer.transformRecords<R>(records);
},
async (error) => {
await session.close();
throw error;
},
);
}

/**
Expand Down
8 changes: 2 additions & 6 deletions src/query.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
// tslint:disable-next-line import-name
import AnyPromise from 'any-promise';
// tslint:disable-next-line import-name
import Observable from 'any-observable';
import { Observable as RxObservable } from 'rxjs';
import { Dictionary } from 'lodash';
Expand Down Expand Up @@ -75,11 +73,9 @@ export class Query extends Builder<Query> {
*
* @returns {Promise<Dictionary<R>[]>}
*/
run<R = any>(): Promise<Dictionary<R>[]> {
async run<R = any>(): Promise<Dictionary<R>[]> {
if (!this.connection) {
return AnyPromise.reject(
new Error('Cannot run query; no connection object available.'),
) as Promise<Dictionary<R>[]>;
throw new Error('Cannot run query; no connection object available.');
}

return this.connection.run<R>(this);
Expand Down
64 changes: 51 additions & 13 deletions tests/connection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import * as neo4j from 'neo4j-driver';
import { Driver, Session } from 'neo4j-driver/types';
import { AuthToken, Config } from 'neo4j-driver/types/driver';
import { tap } from 'rxjs/operators';
import { SinonSpy, spy } from 'sinon';
import { SinonSpy, SinonStub, spy, stub } from 'sinon';
import { Connection, Node, Query } from '../src';
import { NodePattern } from '../src/clauses';
import { expect } from '../test-setup';
Expand All @@ -15,37 +15,44 @@ type ArgumentTypes<T extends (...args: any) => any>
= T extends (...a: infer Args) => any ? Args : never;
type SinonSpyFor<T extends (...args: any) => any>
= SinonSpy<ArgumentTypes<T>, ReturnType<T>>;
type SinonStubFor<T extends (...args: any) => any>
= SinonStub<ArgumentTypes<T>, ReturnType<T>>;

describe('Connection', () => {
let connection: Connection;
let driver: Driver;
let driverCloseSpy: SinonSpyFor<Driver['close']>;
let driverSessionSpy: SinonSpyFor<Driver['session']>;
let driverSessionStub: SinonStubFor<Driver['session']>;
let sessionRunSpy: SinonSpyFor<Session['run']>;
let sessionCloseSpy: SinonSpyFor<Session['close']>;
const stubSession = stub<[Session], void>();

function makeSessionMock(driver: Driver): Driver {
const defaultSessionConstructor = driver.session;
driver.session = function (...args: ArgumentTypes<typeof driver.session>) {
const session = defaultSessionConstructor.call(this, ...args);
sessionRunSpy = spy(session, 'run');
sessionCloseSpy = spy(session, 'close');
function attachSessionSpies(session: Session): void {
sessionRunSpy = spy(session, 'run');
sessionCloseSpy = spy(session, 'close');
}

function makeSessionMock(createSession: Driver['session']): Driver['session'] {
return (...args) => {
const session = createSession(...args);
stubSession(session);
return session;
};
return driver;
}

function driverConstructor(url: string, authToken?: AuthToken, config?: Config) {
driver = makeSessionMock(neo4j.driver(url, authToken, config));
driver = neo4j.driver(url, authToken, config);
const mock = makeSessionMock(driver.session.bind(driver));
driverSessionStub = stub(driver, 'session').callsFake(mock);
driverCloseSpy = spy(driver, 'close');
driverSessionSpy = spy(driver, 'session');
return driver;
}

// Wait for neo4j to be ready before testing
before(waitForNeo);

beforeEach(() => {
stubSession.callsFake(attachSessionSpies);
connection = new Connection(neo4jUrl, neo4jCredentials, driverConstructor);
});

Expand Down Expand Up @@ -100,14 +107,14 @@ describe('Connection', () => {
describe('#session', () => {
it('should use the driver to create a session', () => {
connection.session();
expect(driverSessionSpy.calledOnce);
expect(driverSessionStub.calledOnce);
});

it('should return null if the connection has been closed', async () => {
await connection.close();
const result = connection.session();

expect(driverSessionSpy.notCalled);
expect(driverSessionStub.notCalled);
expect(result).to.equal(null);
});
});
Expand All @@ -124,6 +131,13 @@ describe('Connection', () => {
await expect(promise).to.be.rejectedWith(Error, 'connection is not open');
});

it('should reject if a session cannot be opened', async () => {
const connectionSessionStub = stub(connection, 'session').returns(null);
const promise = connection.run(connection.query().return('1'));
await expect(promise).to.be.rejectedWith(Error, 'connection is not open');
connectionSessionStub.restore();
});

it('should run the query through a session', async () => {
const params = {};
const query = new Query().raw('RETURN 1', params);
Expand All @@ -146,6 +160,30 @@ describe('Connection', () => {
await expect(promise).to.be.rejectedWith(Error)
.then(() => expect(sessionCloseSpy.calledOnce));
});

describe('when session.close throws', async () => {
const message = 'Fake error';
let sessionCloseStub: SinonStubFor<Session['close']>;

beforeEach(() => {
stubSession.resetBehavior();
stubSession.callsFake((session) => {
sessionCloseStub = stub(session, 'close').throws(new Error(message));
});
});

it('the error should bubble up', async () => {
const promise = connection.run(new Query().raw('RETURN 1'));
await expect(promise).to.be.rejectedWith(Error, message);
});

it('does not call session.close again', async () => {
try {
await connection.run(new Query().raw('RETURN a'));
} catch (e) {}
expect(sessionCloseStub.calledOnce);
});
});
});

describe('stream', () => {
Expand Down
5 changes: 0 additions & 5 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -661,11 +661,6 @@ any-observable@^0.5.0:
resolved "https://registry.yarnpkg.com/any-observable/-/any-observable-0.5.0.tgz#2dc6af0382b67cfd1a49e1f65e515196d4e32d38"
integrity sha512-GnS7zaS5yBufhXeqfROuyt//AlqrN6dNHTN0Ex6vy22cIyUdeJY46rll8WLVmbV2yV2DEEl3HjspPLVLS79YZw==

any-promise@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
integrity sha1-q8av7tzqUugJzcA3au0845Y10X8=

append-transform@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-1.0.0.tgz#046a52ae582a228bd72f58acfbe2967c678759ab"
Expand Down

0 comments on commit f624574

Please sign in to comment.