DefinitelyTyped/types/libpq/libpq-tests.ts
Andrew Bradley 9ea8da2d0e Remove or fix dependencies on mocha by other declarations
If dependency is unnecessary, replaces with stub declarations
If dependency is necessary, declares a requirement on at least TS2.1 to match mocha's
2018-03-14 22:52:57 -04:00

829 lines
22 KiB
TypeScript

import { Buffer } from 'buffer';
import assert = require('assert');
import * as async from 'async';
import PQ = require('libpq');
// Stub mocha functions
const {describe, it, before, after, beforeEach, afterEach} = null as any as {
[s: string]: ((s: string, cb: (done: any) => void) => void) & ((cb: (done: any) => void) => void) & {only: any, skip: any};
};
declare const _: { times<T>(n: number, f: () => T): T[] };
declare const ok: Function;
const createTable = (pq: PQ) => {
pq.exec('CREATE TEMP TABLE test_data(name text, age int)');
console.log(pq.resultErrorMessage());
pq.exec("INSERT INTO test_data(name, age) VALUES ('brian', 32), ('aaron', 30), ('', null);");
};
const blink = (n: number, cb: Function) => {
const connections: PQ[] = [];
for (let i = 0; i < 30; i++) {
connections.push(new PQ());
}
const connect = (con: PQ, cb: (err?: Error) => void) => {
con.connect(cb);
};
async.each(connections, connect, ok(() => {
connections.forEach((con) => {
con.finish();
});
cb();
}));
};
const queryText = "SELECT * FROM generate_series(1, 1000)";
const query = (pq: PQ, cb: Function) => {
const readError = (message?: string) => {
cleanup();
return cb(new Error(message || pq.errorMessage()));
};
const onReadable = () => {
if (!pq.consumeInput()) {
return readError();
}
if (pq.isBusy()) {
return;
}
pq.getResult();
if (pq.getResult()) {
return readError('Only one result at a time is accepted');
}
cleanup();
return cb(null, []);
};
const sent = pq.sendQuery(queryText);
if (!sent) return cb(new Error(pq.errorMessage()));
console.log('sent query');
const cleanup = () => {
pq.removeListener('readable', onReadable);
pq.stopReader();
};
pq.on('readable', onReadable);
pq.startReader();
};
describe('async connection', () => {
it('works', (done) => {
const pq = new PQ();
assert(!pq.connected, 'should have connected set to falsy');
pq.connect(err => {
assert.ifError(err);
pq.exec('SELECT NOW()');
assert.equal(pq.ntuples(), 1);
done();
});
});
it('works with hard-coded connection parameters', (done) => {
const pq = new PQ();
const conString = `host=${process.env.PGHOST || 'localhost'}`;
pq.connect(conString, done);
});
it('returns an error to the callback if connection fails', (done) => {
new PQ().connect('host=asldkfjasldkfjalskdfjasdf', err => {
assert(err, 'should have passed an error');
done();
});
});
it('respects the active domain', (done) => {
const pq = new PQ();
const domain = require('domain').create();
domain.run(() => {
const activeDomain = process.domain;
assert(activeDomain, 'Should have an active domain');
pq.connect(() => {
assert.strictEqual(process.domain, activeDomain, 'Active domain is lost');
done();
});
});
});
});
const consume = (pq: PQ, cb: Function) => {
if (!pq.isBusy()) return cb();
pq.startReader();
const onReadable = () => {
assert(pq.consumeInput(), pq.errorMessage());
if (pq.isBusy()) {
console.log('consuming a 2nd buffer of input later...');
return;
}
pq.removeListener('readable', onReadable);
pq.stopReader();
cb();
};
pq.on('readable', onReadable);
};
describe('async simple query', () => {
const pq: PQ = null as any;
it('dispatches simple query', (done: Function) => {
assert(pq.setNonBlocking(true));
pq.writable(() => {
const success = pq.sendQuery('SELECT 1');
assert.strictEqual(pq.flush(), 0, 'Should have flushed all data to socket');
assert(success, pq.errorMessage());
consume(pq, () => {
assert.ifError(pq.errorMessage());
assert(pq.getResult());
assert.strictEqual(pq.getResult(), false);
assert.strictEqual(pq.ntuples(), 1);
assert.strictEqual(pq.getvalue(0, 0), '1');
done();
});
});
});
it('dispatches parameterized query', (done: Function) => {
const success = pq.sendQueryParams('SELECT $1::text as name', ['Brian']);
assert(success, pq.errorMessage());
assert.strictEqual(pq.flush(), 0, 'Should have flushed query text & parameters');
consume(pq, () => {
assert.ifError(pq.errorMessage());
assert(pq.getResult());
assert.strictEqual(pq.getResult(), false);
assert.strictEqual(pq.ntuples(), 1);
assert.equal(pq.getvalue(0, 0), 'Brian');
done();
});
});
it('dispatches named query', (done: Function) => {
const statementName = 'async-get-name';
const success = pq.sendPrepare(statementName, 'SELECT $1::text as name', 1);
assert(success, pq.errorMessage());
assert.strictEqual(pq.flush(), 0, 'Should have flushed query text');
consume(pq, () => {
assert.ifError(pq.errorMessage());
// first time there should be a result
assert(pq.getResult());
// call 'getResult' until it returns false indicating
// there is no more input to consume
assert.strictEqual(pq.getResult(), false);
// since we only prepared a statement there should be
// 0 tuples in the result
assert.equal(pq.ntuples(), 0);
// now execute the previously prepared statement
const success = pq.sendQueryPrepared(statementName, ['Brian']);
assert(success, pq.errorMessage());
assert.strictEqual(pq.flush(), 0, 'Should have flushed parameters');
consume(pq, () => {
assert.ifError(pq.errorMessage());
// consume the result of the query execution
assert(pq.getResult());
assert.equal(pq.ntuples(), 1);
assert.equal(pq.getvalue(0, 0), 'Brian');
// call 'getResult' again to ensure we're finished
assert.strictEqual(pq.getResult(), false);
done();
});
});
});
});
describe('cancel a request', () => {
it('works', (done) => {
const pq = new PQ();
pq.connectSync();
const sent = pq.sendQuery('pg_sleep(5000)');
assert(sent, 'should have sent');
const canceled = pq.cancel();
assert.strictEqual(canceled, true, 'should have canceled');
const hasResult = pq.getResult();
assert(hasResult, 'should have a result');
assert.equal(pq.resultStatus(), 'PGRES_FATAL_ERROR');
assert.equal(pq.getResult(), false);
pq.exec('SELECT NOW()');
done();
});
});
describe('Constructing multiple', () => {
it('works all at once', () => {
for (let i = 0; i < 1000; i++) {
const pq = new PQ();
}
});
it('connects and disconnects each client', (done) => {
const connect = (n: number, cb: (err?: Error) => void) => {
const pq = new PQ();
pq.connect(cb);
};
async.times(30, connect, done);
});
});
describe('COPY IN', () => {
let pq: PQ;
before(() => {
pq = new PQ();
pq.connectSync();
createTable(pq);
});
after(() => {
pq.finish();
});
it('check existing data assuptions', () => {
pq.exec('SELECT COUNT(*) FROM test_data');
assert.equal(pq.getvalue(0, 0), 3);
});
it('copies data in', () => {
const success = pq.exec('COPY test_data FROM stdin');
assert.equal(pq.resultStatus(), 'PGRES_COPY_IN');
const buffer = new Buffer("bob\t100\n", 'utf8');
const res1 = pq.putCopyData(buffer);
assert.strictEqual(res1, 1);
const res2 = pq.putCopyEnd();
assert.strictEqual(res2, 1);
while (pq.getResult()) {
}
pq.exec('SELECT COUNT(*) FROM test_data');
assert.equal(pq.getvalue(0, 0), 4);
});
it('can cancel copy data in', () => {
const success = pq.exec('COPY test_data FROM stdin');
assert.equal(pq.resultStatus(), 'PGRES_COPY_IN');
const buffer = new Buffer("bob\t100\n", 'utf8');
const res1 = pq.putCopyData(buffer);
assert.strictEqual(res1, 1);
const res2 = pq.putCopyEnd('cancel!');
assert.strictEqual(res2, 1);
while (pq.getResult()) {
}
assert(pq.errorMessage());
assert(
pq.errorMessage().includes('cancel!'),
`${pq.errorMessage()} should have contained "cancel!"`
);
pq.exec('SELECT COUNT(*) FROM test_data');
assert.equal(pq.getvalue(0, 0), 4);
});
});
describe('COPY OUT', () => {
let pq: PQ;
before(() => {
pq = new PQ();
pq.connectSync();
createTable(pq);
});
after(() => {
pq.finish();
});
const getRow = (pq: PQ, expected: string) => {
const result = <Buffer> pq.getCopyData(false);
assert(result instanceof Buffer, 'Result should be a buffer');
assert.equal(result.toString('utf8'), expected);
};
it('copies data out', () => {
pq.exec('COPY test_data TO stdin');
assert.equal(pq.resultStatus(), 'PGRES_COPY_OUT');
getRow(pq, 'brian\t32\n');
getRow(pq, 'aaron\t30\n');
getRow(pq, '\t\\N\n');
assert.strictEqual(<number> pq.getCopyData(), -1);
});
});
describe('without being connected', () => {
it('exec fails', () => {
const pq = new PQ();
pq.exec();
assert.equal(pq.resultStatus(), 'PGRES_FATAL_ERROR');
assert(pq.errorMessage());
});
it('fails on async query', () => {
const pq = new PQ();
const success = pq.sendQuery('blah');
assert.strictEqual(success, false);
assert.equal(pq.resultStatus(), 'PGRES_FATAL_ERROR');
assert(pq.errorMessage());
});
it('throws when reading while not connected', () => {
const pq = new PQ();
assert.throws(() => {
pq.startReader();
});
});
it('throws when writing while not connected', () => {
const pq = new PQ();
assert.throws(() => {
pq.writable(() => {
});
});
});
});
describe('error info', () => {
let pq: PQ;
before(() => {
pq = new PQ();
pq.connectSync();
createTable(pq);
});
after(() => {
pq.finish();
});
describe('when there is no error', () => {
it('everything is null', () => {
pq.exec('SELECT NOW()');
assert(!pq.errorMessage(), pq.errorMessage());
assert.equal(pq.ntuples(), 1);
assert(pq.resultErrorFields(), undefined);
});
});
describe('when there is an error', () => {
it('sets all error codes', () => {
pq.exec('INSERT INTO test_data VALUES(1, NOW())');
assert(pq.errorMessage());
const err = pq.resultErrorFields();
assert.notEqual(err, null);
assert.equal(err.severity, 'ERROR');
assert.equal(err.sqlState, 42804);
assert.equal(err.messagePrimary, 'column "age" is of type integer but expression is of type timestamp with time zone');
assert.equal(err.messageDetail, undefined);
assert.equal(err.messageHint, 'You will need to rewrite or cast the expression.');
assert.equal(err.statementPosition, 33);
assert.equal(err.internalPosition, undefined);
assert.equal(err.internalQuery, undefined);
assert.equal(err.context, undefined);
assert.equal(err.schemaName, undefined);
assert.equal(err.tableName, undefined);
assert.equal(err.dataTypeName, undefined);
assert.equal(err.constraintName, undefined);
assert.equal(err.sourceFile, "parse_target.c");
assert(parseInt(err.sourceLine, 10));
assert.equal(err.sourceFunction, "transformAssignedExpr");
});
});
});
describe('escapeLiteral', () => {
it('fails to escape when the server is not connected', () => {
const pq = new PQ();
const result = pq.escapeLiteral('test');
assert.strictEqual(result, null);
assert(pq.errorMessage());
});
it('escapes a simple string', () => {
const pq = new PQ();
pq.connectSync();
const result = pq.escapeLiteral('bang');
assert.equal(result, "'bang'");
});
it('escapes a bad string', () => {
const pq = new PQ();
pq.connectSync();
const result = pq.escapeLiteral("'; TRUNCATE TABLE blah;");
assert.equal(result, "'''; TRUNCATE TABLE blah;'");
});
});
describe('escapeIdentifier', () => {
it('fails when the server is not connected', () => {
const pq = new PQ();
const result = pq.escapeIdentifier('test');
assert.strictEqual(result, null);
assert(pq.errorMessage());
});
it('escapes a simple string', () => {
const pq = new PQ();
pq.connectSync();
const result = pq.escapeIdentifier('bang');
assert.equal(result, '"bang"');
});
});
describe('connecting', () => {
it('works', () => {
const client = new PQ();
client.connectSync();
});
});
describe('many connections', () => {
it('works', (done) => {
async.timesSeries(10, blink, done);
});
});
describe('connectSync', () => {
it('works 50 times in a row', () => {
const pqs = _.times(50, () => new PQ());
pqs.forEach((pq) => {
pq.connectSync();
});
pqs.forEach((pq) => {
pq.finish();
});
});
});
describe('connect async', () => {
const total = 50;
it(`works ${total} times in a row`, (done) => {
const pqs = _.times(total, () => new PQ());
let count = 0;
const connect = (cb: Function) => {
pqs.forEach((pq) => {
pq.connect((err) => {
assert.ifError(err);
count++;
pq.startReader();
if (count === total) {
cb();
}
});
});
};
connect(() => {
pqs.forEach((pq) => {
pq.stopReader();
pq.finish();
});
done();
});
});
});
describe('multiple queries', () => {
const pq = new PQ();
before((done) => {
pq.connect(done);
});
it('first query works', (done) => {
query(pq, done);
});
it('second query works', (done) => {
query(pq, done);
});
it('third query works', (done) => {
query(pq, done);
});
});
describe('set & get non blocking', () => {
let pq: PQ;
before(() => {
pq = new PQ();
pq.connectSync();
createTable(pq);
});
after(() => {
pq.finish();
});
it('is initially set to false', () => {
assert.strictEqual(pq.isNonBlocking(), false);
});
it('can switch back and forth', () => {
assert.strictEqual(pq.setNonBlocking(true), true);
assert.strictEqual(pq.isNonBlocking(), true);
assert.strictEqual(pq.setNonBlocking(), true);
assert.strictEqual(pq.isNonBlocking(), false);
});
});
describe('LISTEN/NOTIFY', () => {
let listener: PQ;
let notifier: PQ;
before(() => {
listener = new PQ();
notifier = new PQ();
listener.connectSync();
notifier.connectSync();
});
it('works', () => {
notifier.exec("NOTIFY testing, 'My Payload'");
let notice = listener.notifies();
assert.equal(notice, null);
listener.exec('LISTEN testing');
notifier.exec("NOTIFY testing, 'My Second Payload'");
listener.exec('SELECT NOW()');
notice = listener.notifies();
assert(notice, 'listener should have had a notification come in');
assert.equal(notice.relname, 'testing', 'missing relname == testing');
assert.equal(notice.extra, 'My Second Payload');
assert(notice.be_pid);
});
after(() => {
listener.finish();
notifier.finish();
});
});
describe('result accessors', () => {
let pq: PQ;
before(() => {
pq = new PQ();
pq.connectSync();
createTable(pq);
});
after(() => {
pq.finish();
});
before(() => {
pq.exec("INSERT INTO test_data(name, age) VALUES ('bob', 80) RETURNING *");
assert(!pq.errorMessage());
});
it('has ntuples', () => {
assert.strictEqual(pq.ntuples(), 1);
});
it('has cmdStatus', () => {
assert.equal(pq.cmdStatus(), 'INSERT 0 1');
});
it('has command tuples', () => {
assert.strictEqual(pq.cmdTuples(), '1');
});
});
describe('Retrieve server version from connection', () => {
it('return version number when connected', () => {
const pq = new PQ();
pq.connectSync();
const version = pq.serverVersion();
assert.equal(typeof version, 'number');
assert(version > 60000);
});
it('return zero when not connected', () => {
const pq = new PQ();
const version = pq.serverVersion();
assert.equal(typeof version, 'number');
assert.equal(version, 0);
});
});
describe('getting socket', () => {
let pq: PQ;
before(() => {
pq = new PQ();
pq.connectSync();
createTable(pq);
});
after(() => {
pq.finish();
});
it('returns -1 when not connected', () => {
const pq = new PQ();
assert.equal(pq.socket(), -1);
});
it('returns value when connected', () => {
assert(pq.socket() > 0);
});
});
describe('connecting with bad credentials', () => {
it('throws an error', () => {
try {
new PQ().connectSync('asldkfjlasdf');
} catch (e) {
assert.equal(e.toString().indexOf('connection pointer is NULL'), -1);
return;
}
assert.fail(null, null, 'Should have thrown an exception', '');
});
});
describe('connecting with no credentials', () => {
let pq: PQ;
before(() => {
pq = new PQ();
pq.connectSync();
});
it('is connected', () => {
assert(pq.connected, 'should have connected == true');
});
after(() => {
pq.finish();
assert(!pq.connected);
});
});
describe('result checking', () => {
let pq: PQ;
before(() => {
pq = new PQ();
pq.connectSync();
});
after(() => {
pq.finish();
});
it('executes query', () => {
pq.exec('SELECT NOW() as my_col');
assert.equal(pq.resultStatus(), 'PGRES_TUPLES_OK');
});
it('has 1 tuple', () => {
assert.equal(pq.ntuples(), 1);
});
it('has 1 field', () => {
assert.strictEqual(pq.nfields(), 1);
});
it('has column name', () => {
assert.equal(pq.fname(0), 'my_col');
});
it('has oid type of timestamptz', () => {
assert.strictEqual(pq.ftype(0), 1184);
});
it('has value as a date', () => {
const now = new Date();
const val = pq.getvalue(0);
const date = new Date(Date.parse(val));
assert.equal(date.getFullYear(), now.getFullYear());
assert.equal(date.getMonth(), now.getMonth());
});
it('can manually clear result multiple times', () => {
pq.clear();
pq.clear();
pq.clear();
});
});
describe('low-level query integration tests', () => {
let pq: PQ;
before(() => {
pq = new PQ();
pq.connectSync();
createTable(pq);
});
after(() => {
pq.finish();
});
describe('exec', () => {
before(() => {
pq.exec('SELECT * FROM test_data');
});
it('has correct tuples', () => {
assert.strictEqual(pq.ntuples(), 3);
});
it('has correct field count', () => {
assert.strictEqual(pq.nfields(), 2);
});
it('has correct rows', () => {
assert.strictEqual(pq.getvalue(0, 0), 'brian');
assert.strictEqual(pq.getvalue(1, 1), '30');
assert.strictEqual(pq.getvalue(2, 0), '');
assert.strictEqual(pq.getisnull(2, 0), false);
assert.strictEqual(pq.getvalue(2, 1), '');
assert.strictEqual(pq.getisnull(2, 1), true);
});
});
});
describe('sync query with parameters', () => {
let pq: PQ;
before(() => {
pq = new PQ();
pq.connectSync();
createTable(pq);
});
after(() => {
pq.finish();
});
it('works with single string parameter', () => {
const queryText = 'SELECT $1::text as name';
pq.execParams(queryText, ['Brian']);
assert.strictEqual(pq.ntuples(), 1);
assert.strictEqual(pq.getvalue(0, 0), 'Brian');
});
it('works with a number parameter', () => {
const queryText = 'SELECT $1::int as age';
pq.execParams(queryText, [32]);
assert.strictEqual(pq.ntuples(), 1);
assert.strictEqual(pq.getvalue(0, 0), '32');
});
it('works with multiple parameters', () => {
const queryText = 'INSERT INTO test_data(name, age) VALUES($1, $2)';
pq.execParams(queryText, ['Barkley', 4]);
assert.equal(pq.resultErrorMessage(), '');
});
});
describe('prepare and execPrepared', () => {
let pq: PQ;
before(() => {
pq = new PQ();
pq.connectSync();
createTable(pq);
});
after(() => {
pq.finish();
});
const statementName = 'get-name';
describe('preparing a statement', () => {
it('works properly', () => {
pq.prepare(statementName, 'SELECT $1::text as name', 1);
assert.ifError(pq.resultErrorMessage());
assert.equal(pq.resultStatus(), 'PGRES_COMMAND_OK');
});
});
describe('executing a prepared statement', () => {
it('works properly', () => {
pq.execPrepared(statementName, ['Brian']);
assert.ifError(pq.resultErrorMessage());
assert.strictEqual(pq.ntuples(), 1);
assert.strictEqual(pq.nfields(), 1);
assert.strictEqual(pq.getvalue(0, 0), 'Brian');
});
});
});