knex/test/unit/knex.js

582 lines
18 KiB
JavaScript
Raw Normal View History

2019-07-10 22:48:43 +01:00
const Knex = require('../../lib/index');
const QueryBuilder = require('../../lib/query/builder');
const { expect } = require('chai');
const sqliteConfig = require('../knexfile').sqlite3;
const sqlite3 = require('sqlite3');
const { noop } = require('lodash');
const inherits = require('inherits');
describe('knex', () => {
describe('supports passing existing connection', () => {
let connection;
beforeEach(() => {
connection = new sqlite3.Database(':memory:');
});
afterEach(() => {
connection.close();
});
it('happy path', (done) => {
const knex = Knex({ client: 'sqlite3' });
knex
.connection(connection)
.select(knex.raw('"0" as value'))
.then((result) => {
expect(result[0].value).to.equal('0');
done();
});
});
});
it('throws error on unsupported config client value', () => {
expect(() => {
Knex({
client: 'dummy',
});
}).to.throw(
/Unknown configuration option 'client' value dummy. Note that it is case-sensitive, check documentation for supported values/
);
});
it('accepts supported config client value', () => {
expect(() => {
Knex({
client: 'mysql',
});
}).not.to.throw();
});
it('accepts supported config client value alias', () => {
expect(() => {
Knex({
client: 'sqlite',
});
}).not.to.throw();
});
it('supports creating copy with userParams', () => {
const knex = Knex({
client: 'sqlite',
});
const knexWithParams = knex.withUserParams({ userParam: '451' });
expect(knexWithParams).to.be.a('function');
expect(knexWithParams.userParams).to.deep.equal({ userParam: '451' });
expect(knexWithParams.client.config.client).to.equal('sqlite');
expect(knexWithParams.migrate).to.be.a('object');
});
it('supports passing user params in config', () => {
const knexWithParams = Knex({
client: 'sqlite',
userParams: {
userParam: '451',
},
});
expect(knexWithParams).to.be.a('function');
expect(knexWithParams.userParams).to.deep.equal({ userParam: '451' });
expect(knexWithParams.client.config.client).to.equal('sqlite');
expect(knexWithParams.migrate).to.be.a('object');
});
it('migrator of a copy with userParams has reference to correct Knex', () => {
const knex = Knex({
client: 'sqlite',
});
const knexWithParams = knex.withUserParams({ userParam: '451' });
expect(knexWithParams.migrate.knex.userParams).to.deep.equal({
userParam: '451',
});
});
it('copying does not result in duplicate listeners', () => {
const knex = Knex({
client: 'sqlite',
});
const knexWithParams = knex.withUserParams();
expect(knex.client.listeners('start').length).to.equal(1);
expect(knex.client.listeners('query').length).to.equal(1);
expect(knex.client.listeners('query-error').length).to.equal(1);
expect(knex.client.listeners('query-response').length).to.equal(1);
expect(knexWithParams.client.listeners('start').length).to.equal(1);
expect(knexWithParams.client.listeners('query').length).to.equal(1);
expect(knexWithParams.client.listeners('query-error').length).to.equal(1);
expect(knexWithParams.client.listeners('query-response').length).to.equal(
1
);
});
it('listeners added to knex directly get copied correctly', () => {
const knex = Knex({
client: 'sqlite',
});
const onQueryResponse = function(response, obj, builder) {};
expect(knex.listeners('query-response').length).to.equal(0);
knex.on('query-response', onQueryResponse);
const knexWithParams = knex.withUserParams();
expect(knex.listeners('query-response').length).to.equal(1);
expect(knexWithParams.listeners('query-response').length).to.equal(1);
});
it('adding listener to copy does not affect base knex', () => {
const knex = Knex({
client: 'sqlite',
});
expect(knex.client.listeners('start').length).to.equal(1);
expect(knex.client.listeners('query').length).to.equal(1);
expect(knex.client.listeners('query-error').length).to.equal(1);
expect(knex.client.listeners('query-response').length).to.equal(1);
const knexWithParams = knex.withUserParams();
knexWithParams.client.on('query', (obj) => {
knexWithParams.emit('query', obj);
});
expect(knex.client.listeners('start').length).to.equal(1);
expect(knex.client.listeners('query').length).to.equal(1);
expect(knex.client.listeners('query-error').length).to.equal(1);
expect(knex.client.listeners('query-response').length).to.equal(1);
expect(knexWithParams.client.listeners('query').length).to.equal(2);
});
it('sets correct postProcessResponse for builders instantiated from clone', () => {
const knex = Knex({
client: 'sqlite',
postProcessResponse: noop,
});
const knexWithParams = knex.withUserParams();
knexWithParams.client.config.postProcessResponse = null;
const builderForTable = knex('tableName');
const builderWithParamsForTable = knexWithParams('tableName');
expect(knex.client.config.postProcessResponse).to.equal(noop);
expect(knexWithParams.client.config.postProcessResponse).to.equal(null);
expect(builderForTable.client.config.postProcessResponse).to.equal(noop);
expect(
builderWithParamsForTable.client.config.postProcessResponse
).to.equal(null);
});
it('passes queryContext to wrapIdentifier in raw query', () => {
if (!sqliteConfig) {
return;
}
const knex = Knex(
Object.assign({}, sqliteConfig, {
wrapIdentifier: (str, origImpl, queryContext) => {
if (!queryContext) {
throw Error('We should have queryContext here right?');
}
if (str === 'iAmGoingToBeConvertedToId') {
str = 'id';
}
return origImpl(str);
},
})
);
return knex.schema
.queryContext({ someStuff: true })
.dropTableIfExists('test')
.then(() => {
return knex.schema
.queryContext({ someStuff: true })
.createTable('test', (table) => {
table.increments('id');
table.string('text');
});
})
.then(() => {
return knex('test')
.queryContext({ someStuff: true })
.select('id')
.whereRaw('id = ??', 'iAmGoingToBeConvertedToId');
})
.then(() => {
return knex.schema.queryContext({ someStuff: true }).dropTable('test');
});
});
it('passes queryContext to wrapIdentifier in raw query in transaction', () => {
if (!sqliteConfig) {
return;
}
const knex = Knex(
Object.assign({}, sqliteConfig, {
wrapIdentifier: (str, origImpl, queryContext) => {
if (!queryContext) {
throw Error('We should have queryContext here right?');
}
if (str === 'iAmGoingToBeConvertedToId') {
str = 'id';
}
return origImpl(str);
},
})
);
return knex.transaction((trx) => {
return trx.schema
.queryContext({ someStuff: true })
.dropTableIfExists('test')
.then(() => {
return trx.schema
.queryContext({ someStuff: true })
.createTable('test', (table) => {
table.increments('id');
table.string('text');
});
})
.then(() => {
return trx('test')
.queryContext({ someStuff: true })
.select('id')
.whereRaw('id = ??', 'iAmGoingToBeConvertedToId');
})
.then(() => {
return trx.schema.queryContext({ someStuff: true }).dropTable('test');
});
});
});
it('sets correct postProcessResponse for chained builders', () => {
const knex = Knex({
client: 'sqlite',
postProcessResponse: noop,
});
const knexWithParams = knex.withUserParams();
knexWithParams.client.config.postProcessResponse = null;
const builderForTable = knex('tableName').where('1 = 1');
const builderWithParamsForTable = knexWithParams('tableName').where(
'1 = 1'
);
expect(knex.client.config.postProcessResponse).to.equal(noop);
expect(knexWithParams.client.config.postProcessResponse).to.equal(null);
expect(builderForTable.client.config.postProcessResponse).to.equal(noop);
expect(
builderWithParamsForTable.client.config.postProcessResponse
).to.equal(null);
});
it('transaction of a copy with userParams retains userparams', () => {
if (!sqliteConfig) {
return;
}
const knex = Knex(sqliteConfig);
const knexWithParams = knex.withUserParams({ userParam: '451' });
return knexWithParams.transaction(async (trx) => {
expect(trx.userParams).to.deep.equal({
userParam: '451',
});
});
});
it('propagates error correctly when all connections are in use', function() {
this.timeout(2000);
const knex = Knex(sqliteConfig);
return knex
.transaction()
.then(() => {
return knex.transaction();
})
.then(() => {
throw new Error('Should not reach here');
})
.catch((err) => {
expect(err.message).to.include('Timeout acquiring a connection');
});
});
it('supports direct retrieval of a transaction from provider', () => {
const knex = Knex(sqliteConfig);
const trxProvider = knex.transactionProvider();
const trxPromise = trxProvider();
let transaction;
return trxPromise
.then((trx) => {
transaction = trx;
expect(trx.client.transacting).to.equal(true);
return knex.transacting(trx).select(knex.raw('1 as result'));
})
.then((rows) => {
expect(rows[0].result).to.equal(1);
return transaction.commit();
})
.then(() => {
return transaction.executionPromise;
});
});
it('supports nested transaction for promise transactions', async () => {
const knex = Knex(sqliteConfig);
const trx = await knex.transaction();
const nestedTrx = await trx.transaction();
const nestedTrx2 = await nestedTrx.transaction();
expect(nestedTrx.name).to.equal('knex');
expect(nestedTrx2.name).to.equal('knex');
});
it('does not reject rolled back nested transactions by default', async () => {
const knex = Knex(sqliteConfig);
const trx = await knex.transaction();
const nestedTrx = await trx.transaction();
await nestedTrx.rollback();
});
it('supports accessing execution promise from standalone transaction', async () => {
const knex = Knex(sqliteConfig);
const trx = await knex.transaction();
const executionPromise = trx.executionPromise;
expect(executionPromise).to.be.ok;
expect(trx.client.transacting).to.equal(true);
const rows = await knex.transacting(trx).select(knex.raw('1 as result'));
expect(rows[0].result).to.equal(1);
await trx.commit();
const result = await executionPromise;
expect(result).to.be.undefined;
});
it('supports accessing execution promise from transaction with a callback', async () => {
const knex = Knex(sqliteConfig);
const trxPromise = new Promise((resolve, reject) => {
knex.transaction((transaction) => {
resolve(transaction);
});
});
const trx = await trxPromise;
const executionPromise = trx.executionPromise;
expect(executionPromise).to.be.ok;
expect(trx.client.transacting).to.equal(true);
const rows = await knex.transacting(trx).select(knex.raw('1 as result'));
expect(rows[0].result).to.equal(1);
await trx.commit();
const result = await executionPromise;
expect(result).to.be.undefined;
});
it('resolves execution promise if there was a manual rollback and transaction is set not to reject', async () => {
const knex = Knex(sqliteConfig);
const trx = await knex.transaction();
const executionPromise = trx.executionPromise;
expect(trx.client.transacting).to.equal(true);
const rows = await knex.transacting(trx).select(knex.raw('1 as result'));
expect(rows[0].result).to.equal(1);
await trx.rollback();
const result = await executionPromise;
expect(result).to.be.undefined;
});
it('rejects execution promise if there was a manual rollback and transaction is set to reject', async () => {
const knex = Knex(sqliteConfig);
const trx = await knex.transaction(undefined, {
doNotRejectOnRollback: false,
});
const executionPromise = trx.executionPromise;
expect(trx.client.transacting).to.equal(true);
const rows = await knex.transacting(trx).select(knex.raw('1 as result'));
expect(rows[0].result).to.equal(1);
await trx.rollback();
let errorWasThrown;
try {
await executionPromise;
} catch (err) {
errorWasThrown = true;
expect(err.message).to.equal(
'Transaction rejected with non-error: undefined'
);
}
expect(errorWasThrown).to.be.true;
});
it('does not reject promise when rolling back a transaction', async () => {
const knex = Knex(sqliteConfig);
const trxProvider = knex.transactionProvider();
const trx = await trxProvider();
await trx.rollback();
await trx.executionPromise;
});
it('returns false when calling isCompleted on a transaction that is not complete', async () => {
const knex = Knex(sqliteConfig);
const trxProvider = knex.transactionProvider();
const trx = await trxProvider();
const completed = trx.isCompleted();
expect(completed).to.be.false;
});
it('returns true when calling isCompleted on a transaction that is committed', async () => {
const knex = Knex(sqliteConfig);
const trxProvider = knex.transactionProvider();
const trx = await trxProvider();
await trx.commit();
const completed = trx.isCompleted();
expect(completed).to.be.true;
});
it('returns true when calling isCompleted on a transaction that is rolled back', async () => {
const knex = Knex(sqliteConfig);
const trxProvider = knex.transactionProvider();
const trx = await trxProvider();
await trx.rollback();
const completed = trx.isCompleted();
expect(completed).to.be.true;
});
it('returns false when calling isCompleted within a transaction handler', async () => {
const knex = Knex(sqliteConfig);
await knex.transaction((trx) => {
expect(trx.isCompleted()).to.be.false;
return trx.select(trx.raw('1 as result'));
});
});
it('creating transaction copy with user params should throw an error', () => {
if (!sqliteConfig) {
return;
}
const knex = Knex(sqliteConfig);
return knex.transaction(async (trx) => {
expect(() => {
trx.withUserParams({ userParam: '451' });
}).to.throw(
/Cannot set user params on a transaction - it can only inherit params from main knex instance/
);
});
});
it('throws if client module has not been installed', () => {
// create dummy dialect which always fails when trying to load driver
2019-07-10 22:48:43 +01:00
const SqliteClient = require(`../../lib/dialects/sqlite3/index.js`);
function ClientFoobar(config) {
SqliteClient.call(this, config);
}
inherits(ClientFoobar, SqliteClient);
ClientFoobar.prototype._driver = () => {
throw new Error('Cannot require...');
};
ClientFoobar.prototype.driverName = 'foo-bar';
expect(() => {
Knex({ client: ClientFoobar, connection: {} });
}).to.throw('Knex: run\n$ npm install foo-bar --save\nCannot require...');
});
describe('async stack traces', () => {
it('should capture stack trace on query builder instantiation', () => {
if (!sqliteConfig) {
return;
}
const knex = Knex(
Object.assign({}, sqliteConfig, { asyncStackTraces: true })
);
return knex('some_nonexisten_table')
.select()
.catch((err) => {
expect(err.stack.split('\n')[1]).to.match(/at createQueryBuilder \(/); // the index 1 might need adjustment if the code is refactored
expect(typeof err.originalStack).to.equal('string');
});
});
});
describe('extend query builder', () => {
let connection;
beforeEach(() => {
connection = new sqlite3.Database(':memory:');
});
afterEach(() => {
connection.close();
delete QueryBuilder.prototype.customSelect;
});
it('should extend default queryBuilder', (done) => {
Knex.QueryBuilder.extend('customSelect', function(value) {
return this.select(this.client.raw(`${value} as value`));
});
const knex = Knex({ client: 'sqlite3' });
knex
.connection(connection)
.customSelect(42)
.then((result) => {
expect(result[0].value).to.equal(42);
done();
});
});
it('should have custom method with transaction', async () => {
Knex.QueryBuilder.extend('customSelect', function(value) {
return this.select(this.client.raw(`${value} as value`));
});
const knex = Knex(sqliteConfig);
const trx = await knex.transaction();
const result = await trx.customSelect(42);
expect(result[0].value).to.equal(42);
});
it('should have custom method on knex with user params', async () => {
Knex.QueryBuilder.extend('customSelect', function(value) {
return this.select(this.client.raw(`${value} as value`));
});
const knex = Knex(sqliteConfig);
const knewWithParams = knex.withUserParams({ foo: 'bar' });
const result = await knewWithParams.customSelect(42);
expect(result[0].value).to.equal(42);
});
it('should throw exception when extending existing method', () => {
expect(() =>
Knex.QueryBuilder.extend('select', function(value) {})
).to.throw(`Can't extend QueryBuilder with existing method ('select')`);
});
});
});