From 7d7c826d48bd2dbc9f1a69db4957443966ea1181 Mon Sep 17 00:00:00 2001 From: Vlad Date: Mon, 9 Aug 2021 21:08:45 +0300 Subject: [PATCH] test: improve code coverage to 100% #88 --- .taprc | 1 - index.js | 3 + test/initialization.test.js | 45 ++++++++- test/query.test.js | 10 +- test/req-initialization.test.js | 95 ++++++++++++++++-- test/transaction.test.js | 170 ++++++++++++++++++++++++-------- 6 files changed, 262 insertions(+), 62 deletions(-) delete mode 100644 .taprc diff --git a/.taprc b/.taprc deleted file mode 100644 index d51127c..0000000 --- a/.taprc +++ /dev/null @@ -1 +0,0 @@ -check-coverage: false diff --git a/index.js b/index.js index 1d26d18..9299108 100644 --- a/index.js +++ b/index.js @@ -144,14 +144,17 @@ function fastifyPostgres (fastify, options, next) { } if (client[name]) { + client.release() throw new Error(`pg client '${name}' is a reserved keyword`) } else if (req.pg[name]) { + client.release() throw new Error(`request client '${name}' has already been registered`) } req.pg[name] = client } else { if (req.pg) { + client.release() throw new Error('request client has already been registered') } else { req.pg = client diff --git a/test/initialization.test.js b/test/initialization.test.js index f17a5e9..0a49788 100644 --- a/test/initialization.test.js +++ b/test/initialization.test.js @@ -3,6 +3,7 @@ const t = require('tap') const test = t.test const Fastify = require('fastify') +const pg = require('pg') const fastifyPostgres = require('../index') const { connectionString } = require('./helpers') @@ -23,7 +24,41 @@ test('Should be able to use native module', (t) => { fastify.pg .query('SELECT 1 AS one') .then((result) => { - t.is(result.rows[0].one, 1) + t.equal(result.rows[0].one, 1) + }) + .catch((err) => { + t.fail(err) + }) + }) +}) + +test('Should print warning when native module not installed', (t) => { + t.plan(3) + + const mockedFastifyPostgres = t.mock('../index', { + pg: { ...pg, native: null } + }) + const realConsole = global.console + global.console.warn = (msg) => t.equal(msg, "pg-native not installed, can't use native option - fallback to pg module") + + const fastify = Fastify() + t.teardown(() => { + fastify.close() + global.console = realConsole + }) + + fastify.register(mockedFastifyPostgres, { + connectionString, + native: true + }) + + fastify.ready((err) => { + t.error(err) + + fastify.pg + .query('SELECT 1 AS one') + .then((result) => { + t.equal(result.rows[0].one, 1) }) .catch((err) => { t.fail(err) @@ -49,7 +84,7 @@ test('Should be able to use an alternative pg module', (t) => { fastify.pg .query('SELECT 1 AS one') .then((result) => { - t.is(result.rows[0].one, 1) + t.equal(result.rows[0].one, 1) }) .catch((err) => { t.fail(err) @@ -106,7 +141,7 @@ test('Should throw when trying to register multiple instances without giving a n fastify.ready((err) => { t.ok(err) - t.is((err || {}).message, 'fastify-postgres has already been registered') + t.equal((err || {}).message, 'fastify-postgres has already been registered') }) }) @@ -149,7 +184,7 @@ test('Should throw when trying to register duplicate connection names', (t) => { fastify.ready((err) => { t.ok(err) - t.is((err || {}).message, `fastify-postgres '${name}' instance name has already been registered`) + t.equal((err || {}).message, `fastify-postgres '${name}' instance name has already been registered`) }) }) @@ -167,7 +202,7 @@ test('Should throw when trying to register a named connection with a reserved ke fastify.ready((err) => { t.ok(err) - t.is((err || {}).message, `fastify-postgres '${name}' is a reserved keyword`) + t.equal((err || {}).message, `fastify-postgres '${name}' is a reserved keyword`) }) }) diff --git a/test/query.test.js b/test/query.test.js index e726b6a..e963003 100644 --- a/test/query.test.js +++ b/test/query.test.js @@ -99,9 +99,9 @@ test('When fastify.pg root namespace is used:', (t) => { t.error(err) fastify.pg.query('SELECT NOW()', (err, result) => { - t.is(result, undefined) + t.equal(result, undefined) t.ok(err) - t.is(err.message, `database "${BAD_DB_NAME}" does not exist`) + t.equal(err.message, `database "${BAD_DB_NAME}" does not exist`) }) }) } @@ -127,7 +127,7 @@ test('When fastify.pg root namespace is used:', (t) => { }) .catch((err) => { t.ok(err) - t.is(err.message, `database "${BAD_DB_NAME}" does not exist`) + t.equal(err.message, `database "${BAD_DB_NAME}" does not exist`) }) }) }) @@ -225,7 +225,7 @@ test('When fastify.pg custom namespace is used:', (t) => { fastify.pg.test .query('SELECT 1 AS one') .then((result) => { - t.is(result.rows[0].one, 1) + t.equal(result.rows[0].one, 1) }) .catch((err) => { t.fail(err) @@ -254,7 +254,7 @@ test('When fastify.pg custom namespace is used:', (t) => { }) .catch((err) => { t.ok(err) - t.is(err.message, `database "${BAD_DB_NAME}" does not exist`) + t.equal(err.message, `database "${BAD_DB_NAME}" does not exist`) }) }) }) diff --git a/test/req-initialization.test.js b/test/req-initialization.test.js index 15f194f..707f54a 100644 --- a/test/req-initialization.test.js +++ b/test/req-initialization.test.js @@ -38,7 +38,7 @@ test('fastify postgress useTransaction route option', t => { url: '/count-users' }) - t.is(extractUserCount(response), 2) + t.equal(extractUserCount(response), 2) }) test('queries that succeed provided to a namespace', async t => { const fastify = Fastify() @@ -71,7 +71,7 @@ test('fastify postgress useTransaction route option', t => { url: '/count-users' }) - t.is(extractUserCount(response), 2) + t.equal(extractUserCount(response), 2) }) test('queries that fail provided', async t => { const fastify = Fastify() @@ -103,7 +103,7 @@ test('fastify postgress useTransaction route option', t => { url: '/count-users' }) - t.is(extractUserCount(response), 0) + t.equal(extractUserCount(response), 0) }) t.end() @@ -121,7 +121,7 @@ test('combinations of registrationOptions.name and routeOptions.pg.transact that }) fastify.get('/', (req, reply) => { - t.is(req.pg, null) + t.equal(req.pg, null) }) fastify.inject({ url: '/' }) @@ -138,7 +138,7 @@ test('combinations of registrationOptions.name and routeOptions.pg.transact that }) fastify.get('/', (req, reply) => { - t.is(req.pg, null) + t.equal(req.pg, null) }) fastify.inject({ url: '/' }) @@ -155,7 +155,7 @@ test('combinations of registrationOptions.name and routeOptions.pg.transact that }) fastify.get('/', { pg: { transact: true } }, (req, reply) => { - t.is(req.pg, null) + t.equal(req.pg, null) }) fastify.inject({ url: '/' }) @@ -171,7 +171,7 @@ test('combinations of registrationOptions.name and routeOptions.pg.transact that }) fastify.get('/', { pg: { transact: 'test' } }, (req, reply) => { - t.is(req.pg, null) + t.equal(req.pg, null) }) fastify.inject({ url: '/' }) @@ -188,10 +188,89 @@ test('combinations of registrationOptions.name and routeOptions.pg.transact that }) fastify.get('/', { pg: { transact: 'different' } }, (req, reply) => { - t.is(req.pg, null) + t.equal(req.pg, null) }) fastify.inject({ url: '/' }) }) t.end() }) + +test('incorrect combinations of registrationOptions.name and routeOptions.pg.transact should throw errors', t => { + t.test('name set as reserved keyword', t => { + t.plan(2) + + const fastify = Fastify() + t.teardown(() => fastify.close()) + + const name = 'user' + + fastify.register(fastifyPostgres, { + connectionString, + name + }) + + fastify.get('/', { pg: { transact: name } }, (req, reply) => {}) + + fastify.inject({ url: '/' }, (err, response) => { + t.error(err) + t.same(response.json(), { + statusCode: 500, + error: 'Internal Server Error', + message: `request client '${name}' does not exist` + }) + }) + }) + + t.test('named pg client has already registered', t => { + t.plan(2) + + const fastify = Fastify() + t.teardown(() => fastify.close()) + + const name = 'test' + + fastify.register(fastifyPostgres, { + connectionString, + name + }) + fastify.addHook('onRequest', async (req, reply) => { + req.pg = { [name]: await fastify.pg[name].connect() } + }) + fastify.get('/', { pg: { transact: name } }, (req, reply) => {}) + + fastify.inject({ url: '/' }, (err, response) => { + t.error(err) + t.same(response.json(), { + statusCode: 500, + error: 'Internal Server Error', + message: `request client '${name}' has already been registered` + }) + }) + }) + + t.test('pg client has already registered', t => { + t.plan(2) + + const fastify = Fastify() + t.teardown(() => fastify.close()) + + fastify.register(fastifyPostgres, { + connectionString + }) + fastify.addHook('onRequest', async (req, reply) => { + req.pg = await fastify.pg.connect() + }) + fastify.get('/', { pg: { transact: true } }, (req, reply) => {}) + + fastify.inject({ url: '/' }, (err, response) => { + t.error(err) + t.same(response.json(), { + statusCode: 500, + error: 'Internal Server Error', + message: 'request client has already been registered' + }) + }) + }) + t.end() +}) diff --git a/test/transaction.test.js b/test/transaction.test.js index 3fdcf9e..f9a013b 100644 --- a/test/transaction.test.js +++ b/test/transaction.test.js @@ -3,6 +3,7 @@ const t = require('tap') const test = t.test const Fastify = require('fastify') +const pg = require('pg') const fastifyPostgres = require('../index') const { BAD_DB_NAME, @@ -17,9 +18,7 @@ test('When fastify.pg root namespace is used:', (t) => { const fastify = Fastify() t.teardown(() => fastify.close()) - fastify.register(fastifyPostgres, { - connectionString: process.env.DATABASE_TEST_URL || 'postgres://postgres:postgres@localhost/postgres' - }) + fastify.register(fastifyPostgres, { connectionString }) fastify.ready((err) => { t.error(err) @@ -31,14 +30,14 @@ test('When fastify.pg root namespace is used:', (t) => { ]), function (err, result) { t.error(err) - t.is(result.rows.length, 1) + t.equal(result.rows.length, 1) const userId = result.rows[0].id fastify.pg .query('SELECT * FROM users WHERE id = $1', [userId]) .then((result) => { - t.is(result.rows[0].username, 'root-with-callback') + t.equal(result.rows[0].username, 'root-with-callback') }) .catch((err) => { t.fail(err) @@ -54,9 +53,7 @@ test('When fastify.pg root namespace is used:', (t) => { const fastify = Fastify() t.teardown(() => fastify.close()) - fastify.register(fastifyPostgres, { - connectionString: process.env.DATABASE_TEST_URL || 'postgres://postgres:postgres@localhost/postgres' - }) + fastify.register(fastifyPostgres, { connectionString }) fastify.ready((err) => { t.error(err) @@ -66,14 +63,14 @@ test('When fastify.pg root namespace is used:', (t) => { client.query('INSERT INTO users(username) VALUES($1) RETURNING id', ['root-with-promise']) ) .then((result) => { - t.is(result.rows.length, 1) + t.equal(result.rows.length, 1) const userId = result.rows[0].id fastify.pg .query('SELECT * FROM users WHERE id = $1', [userId]) .then((result) => { - t.is(result.rows[0].username, 'root-with-promise') + t.equal(result.rows[0].username, 'root-with-promise') }) .catch((err) => { t.fail(err) @@ -91,9 +88,7 @@ test('When fastify.pg root namespace is used:', (t) => { const fastify = Fastify() t.teardown(() => fastify.close()) - fastify.register(fastifyPostgres, { - connectionString: process.env.DATABASE_TEST_URL || 'postgres://postgres:postgres@localhost/postgres' - }) + fastify.register(fastifyPostgres, { connectionString }) fastify.ready((err) => { t.error(err) @@ -109,14 +104,14 @@ test('When fastify.pg root namespace is used:', (t) => { ), function (err, result) { t.error(err) - t.is(result.rows.length, 1) + t.equal(result.rows.length, 1) const userId = result.rows[0].id fastify.pg .query('SELECT * FROM users WHERE id = $1', [userId]) .then((result) => { - t.is(result.rows[0].username, 'root-commit-callback') + t.equal(result.rows[0].username, 'root-commit-callback') }) .catch((err) => { t.fail(err) @@ -132,9 +127,7 @@ test('When fastify.pg root namespace is used:', (t) => { const fastify = Fastify() t.teardown(() => fastify.close()) - fastify.register(fastifyPostgres, { - connectionString: process.env.DATABASE_TEST_URL || 'postgres://postgres:postgres@localhost/postgres' - }) + fastify.register(fastifyPostgres, { connectionString }) fastify.ready((err) => { t.error(err) @@ -154,7 +147,7 @@ test('When fastify.pg root namespace is used:', (t) => { .query('SELECT * FROM users WHERE id = $1', [userId]) .then((result) => { t.ok(result) - t.is(result.rows[0].username, 'root-rollback-user-callback') + t.equal(result.rows[0].username, 'root-rollback-user-callback') }) .then(() => { throw new Error('We make it throw on purpose to trigger a rollback') @@ -163,14 +156,14 @@ test('When fastify.pg root namespace is used:', (t) => { }, function (err, result) { t.ok(err) - t.is(err.message, 'We make it throw on purpose to trigger a rollback') - t.is(result, undefined) + t.equal(err.message, 'We make it throw on purpose to trigger a rollback') + t.equal(result, undefined) fastify.pg .query('SELECT * FROM users WHERE username = \'root-rollback-user-callback\'') .then((result) => { t.ok(result) - t.is(result.rows.length, 0) + t.equal(result.rows.length, 0) }) .catch((err) => { t.fail(err) @@ -186,9 +179,7 @@ test('When fastify.pg root namespace is used:', (t) => { const fastify = Fastify() t.teardown(() => fastify.close()) - fastify.register(fastifyPostgres, { - connectionString: process.env.DATABASE_TEST_URL || 'postgres://postgres:postgres@localhost/postgres' - }) + fastify.register(fastifyPostgres, { connectionString }) fastify.ready((err) => { t.error(err) @@ -208,7 +199,7 @@ test('When fastify.pg root namespace is used:', (t) => { .query('SELECT * FROM users WHERE id = $1', [userId]) .then((result) => { t.ok(result) - t.is(result.rows[0].username, 'root-rollback-user-promise') + t.equal(result.rows[0].username, 'root-rollback-user-promise') }) .then(() => { throw new Error('We make it throw on purpose to trigger a rollback') @@ -217,13 +208,13 @@ test('When fastify.pg root namespace is used:', (t) => { ) .catch((err) => { t.ok(err) - t.is(err.message, 'We make it throw on purpose to trigger a rollback') + t.equal(err.message, 'We make it throw on purpose to trigger a rollback') fastify.pg .query('SELECT * FROM users WHERE username = \'root-rollback-user-promise\'') .then((result) => { t.ok(result) - t.is(result.rows.length, 0) + t.equal(result.rows.length, 0) }) .catch((err) => { t.fail(err) @@ -247,11 +238,104 @@ test('When fastify.pg root namespace is used:', (t) => { fastify.pg.transact((client) => client.query('SELECT NOW()')) .catch((err) => { t.ok(err) - t.is(err.message, `database "${BAD_DB_NAME}" does not exist`) + t.equal(err.message, `database "${BAD_DB_NAME}" does not exist`) }) }) }) + t.test('Should trigger a rollback when it is impossible to begin transaction', (t) => { + t.plan(4) + + const fastify = Fastify() + t.teardown(() => fastify.close()) + + class FakeClient extends pg.Client { + query (cmd, cb) { + if (cmd === 'BEGIN') { + cb(new Error('Boom')) + } + cb(null, {}) + } + } + class FakePool extends pg.Pool { + constructor (options) { + super() + this.Client = new FakeClient(options) + this.options = Object.assign({}, options) + } + + connect (cb) { + cb(null, this.Client, () => {}) + } + } + + fastify.register(fastifyPostgres, { connectionString, pg: { ...pg, Pool: FakePool } }) + + fastify.ready((err) => { + t.error(err) + + fastify.pg.transact( + (client) => {}, + function (err, result) { + t.ok(err) + t.equal(err.message, 'Boom') + t.equal(result, undefined) + } + ) + }) + }) + + t.test('Should trigger a rollback when it is impossible to commit transaction', (t) => { + t.plan(3) + + const fastify = Fastify() + t.teardown(() => fastify.close()) + + class FakeClient extends pg.Client { + query (queryText, values, cb) { + if (values && typeof values === 'function') { + cb = values + } + if (queryText === 'COMMIT') { + return cb(new Error('Boom')) + } + return cb(null, {}) + } + } + class FakePool extends pg.Pool { + constructor (options) { + super() + this.Client = new FakeClient(options) + this.options = Object.assign({}, options) + } + + connect (cb) { + cb(null, this.Client, () => {}) + } + } + + fastify.register(fastifyPostgres, { connectionString, pg: { ...pg, Pool: FakePool } }) + + fastify.ready((err) => { + t.error(err) + + fastify.pg.transact( + (client, commit) => { + return client.query('INSERT INTO users(username) VALUES($1) RETURNING id', [ + 'root-rollback-commit' + ], (err, res) => { + commit(err, res) + }) + } + ).then((result) => { + t.equal(result, undefined) + }).catch(err => { + t.ok(err) + t.equal(err.message, 'Boom') + }) + }) + }) + t.end() }) @@ -277,14 +361,14 @@ test('When fastify.pg.test namespace is used:', (t) => { ]), function (err, result) { t.error(err) - t.is(result.rows.length, 1) + t.equal(result.rows.length, 1) const userId = result.rows[0].id fastify.pg.test .query('SELECT * FROM users WHERE id = $1', [userId]) .then((result) => { - t.is(result.rows[0].username, 'namespace-with-callback') + t.equal(result.rows[0].username, 'namespace-with-callback') }) .catch((err) => { t.fail(err) @@ -315,14 +399,14 @@ test('When fastify.pg.test namespace is used:', (t) => { ]) ) .then((result) => { - t.is(result.rows.length, 1) + t.equal(result.rows.length, 1) const userId = result.rows[0].id fastify.pg.test .query('SELECT * FROM users WHERE id = $1', [userId]) .then((result) => { - t.is(result.rows[0].username, 'namespace-with-promise') + t.equal(result.rows[0].username, 'namespace-with-promise') }) .catch((err) => { t.fail(err) @@ -360,14 +444,14 @@ test('When fastify.pg.test namespace is used:', (t) => { }, function (err, result) { t.error(err) - t.is(result.rows.length, 1) + t.equal(result.rows.length, 1) const userId = result.rows[0].id fastify.pg.test .query('SELECT * FROM users WHERE id = $1', [userId]) .then((result) => { - t.is(result.rows[0].username, 'namespace-commit-callback') + t.equal(result.rows[0].username, 'namespace-commit-callback') }) .catch((err) => { t.fail(err) @@ -406,7 +490,7 @@ test('When fastify.pg.test namespace is used:', (t) => { .query('SELECT * FROM users WHERE id = $1', [userId]) .then((result) => { t.ok(result) - t.is(result.rows[0].username, 'namespace-rollback-user-callback') + t.equal(result.rows[0].username, 'namespace-rollback-user-callback') }) .then(() => { throw new Error('We make it throw on purpose to trigger a rollback') @@ -415,14 +499,14 @@ test('When fastify.pg.test namespace is used:', (t) => { }, function (err, result) { t.ok(err) - t.is(err.message, 'We make it throw on purpose to trigger a rollback') - t.is(result, undefined) + t.equal(err.message, 'We make it throw on purpose to trigger a rollback') + t.equal(result, undefined) fastify.pg.test .query('SELECT * FROM users WHERE username = \'namespace-rollback-user-callback\'') .then((result) => { t.ok(result) - t.is(result.rows.length, 0) + t.equal(result.rows.length, 0) }) .catch((err) => { t.fail(err) @@ -461,7 +545,7 @@ test('When fastify.pg.test namespace is used:', (t) => { .query('SELECT * FROM users WHERE id = $1', [userId]) .then((result) => { t.ok(result) - t.is(result.rows[0].username, 'namespace-rollback-user-promise') + t.equal(result.rows[0].username, 'namespace-rollback-user-promise') }) .then(() => { throw new Error('We make it throw on purpose to trigger a rollback') @@ -470,13 +554,13 @@ test('When fastify.pg.test namespace is used:', (t) => { ) .catch((err) => { t.ok(err) - t.is(err.message, 'We make it throw on purpose to trigger a rollback') + t.equal(err.message, 'We make it throw on purpose to trigger a rollback') fastify.pg.test .query('SELECT * FROM users WHERE username = \'namespace-rollback-user-promise\'') .then((result) => { t.ok(result) - t.is(result.rows.length, 0) + t.equal(result.rows.length, 0) }) .catch((err) => { t.fail(err) @@ -501,7 +585,7 @@ test('When fastify.pg.test namespace is used:', (t) => { fastify.pg.test.transact((client) => client.query('SELECT NOW()')) .catch((err) => { t.ok(err) - t.is(err.message, `database "${BAD_DB_NAME}" does not exist`) + t.equal(err.message, `database "${BAD_DB_NAME}" does not exist`) }) }) })