Last active
June 13, 2023 06:59
-
-
Save cpsubrian/b4820b475e7262251a16fb286606e4f7 to your computer and use it in GitHub Desktop.
Revisions
-
cpsubrian revised this gist
Feb 8, 2018 . 1 changed file with 17 additions and 21 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -5,13 +5,10 @@ import fs from 'fs' import callsites from 'callsites' import knex from 'knex' import hash from 'object-hash' import conf from '<conf>' // Get the db config. const config = conf.get('database') // Track jasmine suites and specs. let suites = [] @@ -66,14 +63,11 @@ const db = new Proxy(function (...args) { } }) // Destroy any accidentally open databases. afterAll(() => { while (stack.length) { stack.shift().destroy() } }) // Mock the db.client and run tests with overridable mocks. @@ -113,18 +107,20 @@ function withTestDatabase (tests) { beforeAll(() => { return db .raw(` CREATE DATABASE :target: WITH TEMPLATE :template: OWNER :user: `, { template: 'ac_template', target: name, user: config.connection.user }) .then(() => { let _config = _.cloneDeep(config) _config.connection.database = name stack.unshift(knex(_config)) }) }) tests(name) @@ -248,4 +244,4 @@ global.describe.withQuerySnapshots = function (description, tests) { }) } export default db -
cpsubrian revised this gist
Mar 13, 2017 . 1 changed file with 31 additions and 11 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -4,6 +4,7 @@ import path from 'path' import fs from 'fs' import callsites from 'callsites' import knex from 'knex' import hash from 'object-hash' import conf from 'api/conf' const config = conf.getProperties().database @@ -46,7 +47,10 @@ stack.ensure = function () { // Create the knex proxy. This will treat whichever db is at the front // of the stack as the active one. const db = new Proxy(function (...args) { stack.ensure() return stack[0].apply(stack[0], args) }, { get (target, name) { stack.ensure() if (!(name in stack[0])) { @@ -147,11 +151,18 @@ function withQuerySnapshots (_filename, tests) { let cached = require(filepath) withMockDatabase((mocks) => { mocks._query.mockImplementation((conn, obj) => { let specName = getSpecName() let queryHash = hash(obj.sql) let querySnaps = _.get(cached, [specName, queryHash]) || [] let snapshot = querySnaps.shift() if (snapshot) { if (snapshot.error) { throw _.extend(new Error(snapshot.error.message), snapshot.error.data) } else { return Promise.resolve(_.extend({}, obj, snapshot)) } } else { throw _.extend(new Error('Could not find snapshot for query'), {obj}) } }) tests() @@ -160,12 +171,21 @@ function withQuerySnapshots (_filename, tests) { withTestDatabase(() => { beforeAll(() => { db.on('query-response', function captureSnapshot (rows, obj) { obj.sql = obj.sql.replace(/\$\d+/g, '?') let specName = getSpecName() let queryHash = hash(obj.sql) let querySnaps = _.get(snapshots, [filepath, specName, queryHash]) || [] let snapshot = _.cloneDeep(_.pick(obj, 'sql', 'bindings', 'response')) _.set(snapshots, [filepath, specName, queryHash, querySnaps.length], snapshot) }) db.on('query-error', function captureSnapshot (err, obj) { obj.sql = obj.sql.replace(/\$\d+/g, '?') let specName = getSpecName() let queryHash = hash(obj.sql) let querySnaps = _.get(snapshots, [filepath, specName, queryHash]) || [] let snapshot = _.cloneDeep(_.pick(obj, 'sql', 'bindings')) snapshot.error = {message: err.message, data: err} _.set(snapshots, [filepath, specName, queryHash, querySnaps.length], snapshot) }) }) @@ -195,7 +215,7 @@ function withQuerySnapshots (_filename, tests) { const counts = {} function appendCount (str) { counts[str] = counts[str] ? ++counts[str] : 0 return str + (counts[str] ? `(${counts[str]})` : '') } // Extend the global describe object. -
cpsubrian revised this gist
Mar 3, 2017 . 1 changed file with 36 additions and 12 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -26,10 +26,13 @@ global.jasmine.getEnv().addReporter({ // suite. function getSpecName () { let spec = _.last(specs) let suite = _.last(suites) if (spec) { return spec.fullName } else if (suite) { return suite.description } else { throw new Error('Not currently in a spec or a suite') } } @@ -144,7 +147,7 @@ function withQuerySnapshots (_filename, tests) { let cached = require(filepath) withMockDatabase((mocks) => { mocks._query.mockImplementation((conn, obj) => { let snapshot = cached[getSpecName()] && cached[getSpecName()].shift() if (snapshot) { return Promise.resolve(_.extend({}, obj, snapshot)) } else { @@ -169,11 +172,20 @@ function withQuerySnapshots (_filename, tests) { tests() afterAll(() => { if (_.isEmpty(snapshots[filepath])) { if (exists) { fs.unlinkSync(filepath) if (fs.existsSync(dir) && !fs.readdirSync(dir).length) { fs.rmdirSync(dir) } } } else { let obj = JSON.stringify(snapshots[filepath] || {}, null, 2) if (!fs.existsSync(dir)) { fs.mkdirSync(dir) } fs.writeFileSync(filepath, `module.exports = ${obj};`) } }) }) } @@ -187,19 +199,31 @@ function appendCount (str) { } // Extend the global describe object. global.describe.withMockDatabase = function (description, tests) { if (typeof description === 'function') { tests = description description = 'with mock database' } describe(appendCount(description), () => { withMockDatabase(tests) }) } global.describe.withTestDatabase = function (description, tests) { if (typeof description === 'function') { tests = description description = 'with test database' } describe(appendCount(description), () => { withTestDatabase(tests) }) } global.describe.withQuerySnapshots = function (description, tests) { const caller = callsites()[1] if (typeof description === 'function') { tests = description description = 'with query snapshots' } describe(appendCount(description), () => { withQuerySnapshots(caller.getFileName(), tests) }) } -
cpsubrian revised this gist
Feb 26, 2017 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -182,7 +182,7 @@ function withQuerySnapshots (_filename, tests) { // Return a string with an incrementing count appended. const counts = {} function appendCount (str) { counts[str] = counts[str] ? ++counts[str] : 0 return str + `(${counts[str]})` } -
cpsubrian revised this gist
Feb 26, 2017 . 1 changed file with 10 additions and 5 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -179,24 +179,29 @@ function withQuerySnapshots (_filename, tests) { } } // Return a string with an incrementing count appended. const counts = {} function appendCount (str) { counts[str] = counts[str] ? counts[str]++ : 0 return str + `(${counts[str]})` } // Extend the global describe object. global.describe.withMockDatabase = function (tests) { describe(appendCount('with mock database'), () => { withMockDatabase(tests) }) } global.describe.withTestDatabase = function (tests) { describe(appendCount('with test database'), () => { withTestDatabase(tests) }) } global.describe.withQuerySnapshots = function (tests) { const caller = callsites()[1] describe(appendCount('with query snapshots'), () => { withQuerySnapshots(caller.getFileName(), tests) }) } export default db -
cpsubrian revised this gist
Feb 26, 2017 . 1 changed file with 1 addition and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -193,8 +193,7 @@ global.describe.withTestDatabase = function (tests) { global.describe.withQuerySnapshots = function (tests) { const caller = callsites()[1] const count = withQuerySnapshots.count++ describe(`with query snapshots(${count})`, () => { withQuerySnapshots(caller.getFileName(), tests) }) } -
cpsubrian revised this gist
Feb 26, 2017 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -69,7 +69,7 @@ afterAll(() => { }) }) // Mock the db.client and run tests with overridable mocks. function withMockDatabase (tests) { let mocks = { _query: jest.fn((conn, obj) => { -
cpsubrian revised this gist
Feb 26, 2017 . 1 changed file with 60 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,60 @@ /* eslint-env jest */ import User from './User' const values = { brian: { username: 'brian', email: '[email protected]', password: 'my-password' }, nopass: { username: 'nopass', email: '[email protected]' } } const models = { brian: null, nopass: null } describe.withQuerySnapshots(() => { test('can insert users', () => { return Promise.all([ User.query().insert(values.brian), User.query().insert(values.nopass) ]) }) test('can fetch the user', () => { return User .query() .where('username', '<>', 'admin') .orderBy('username') .then((res) => { models.brian = res[0] models.nopass = res[1] expect(res.length).toBe(2) expect(models.brian.email).toBe('[email protected]') expect(models.nopass.email).toBe('[email protected]') expect(User.checkPassword('my-password', models.brian.auth)).toBe(true) expect(User.checkPassword('wrong', models.brian.auth)).toBe(false) }) }) test('can update a user', () => { models.brian.role = 'admin' return User .query() .updateAndFetchById(models.brian.id, models.brian) .then((updated) => { expect(updated.role).toBe('admin') }) }) test('can patch a user', () => { return User .query() .patchAndFetchById(models.nopass.id, {password: 'easy'}) .then((patched) => { expect(User.checkPassword('easy', patched.auth)).toBe(true) expect(User.checkPassword('wrong', patched.auth)).toBe(false) }) }) }) -
cpsubrian revised this gist
Feb 26, 2017 . 1 changed file with 124 additions and 20 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,9 +1,9 @@ /* eslint-env jest */ import _ from 'lodash' import path from 'path' import fs from 'fs' import callsites from 'callsites' import knex from 'knex' import conf from 'api/conf' const config = conf.getProperties().database @@ -12,7 +12,26 @@ const dirs = { seeds: path.resolve(__dirname, '../../../../seeds') } // Track jasmine suites and specs. let suites = [] let specs = [] global.jasmine.getEnv().addReporter({ suiteStarted: (suite) => suites.push(suite), suiteDone: (suite) => suites.pop(), specStarted: (spec) => specs.push(spec), specDone: (spec) => specs.pop() }) // Helper to get the current spec's fullname, or in absense, the nearest // suite. function getSpecName () { let spec = _.last(specs) if (spec) { return spec.fullName } else { return _.last(suites).description } } // Test dbs will be stored here. const stack = [] @@ -51,36 +70,47 @@ afterAll(() => { }) // Mock the db and return a tracker. function withMockDatabase (tests) { let mocks = { _query: jest.fn((conn, obj) => { return Promise.reject(new Error('Not implemented')) }), _stream: jest.fn((conn, obj, stream, options) => { return Promise.reject(new Error('Not implemented')) }), acquireConnection: jest.fn(() => Promise.resolve({})), releaseConnection: jest.fn(() => Promise.resolve()) } beforeAll(() => { stack.ensure() // Override prototype methods with instance properties. _.each(mocks, (val, key) => { db.client[key] = val }) }) tests(mocks) afterAll(() => { // Remove instance properties to restore prototype versions. _.each(mocks, (val, key) => { delete db.client[key] }) }) } // Inject a real test database for the current test scenario. function withTestDatabase (tests) { const name = `ac_test__${Date.now()}_${Math.floor(Math.random() * 100)}` beforeAll(() => { return db .raw('CREATE DATABASE ??', [name]) .then(() => { let _config = _.cloneDeep(config) _config.connection.database = name stack.unshift(knex(_config)) }) .then(() => { return db.migrate.latest({directory: dirs.migrations}) @@ -90,10 +120,84 @@ global.test.injectTestDatabase = function () { }) }) tests(name) afterAll(() => { return stack.shift().destroy().then(() => { return db.raw('DROP DATABASE ??', [name]) }) }) } // Store snapshots created in this test run. const snapshots = {} // Cache query responses and mock them on subsequent test runs. function withQuerySnapshots (_filename, tests) { let dir = path.resolve(path.dirname(_filename), '__fixtures__') let filename = path.basename(_filename) + '.queries' let filepath = path.join(dir, filename) let exists = fs.existsSync(filepath) let update = typeof process.env.REQUERY !== 'undefined' if (exists && !update) { let cached = require(filepath) withMockDatabase((mocks) => { mocks._query.mockImplementation((conn, obj) => { let snapshot = cached[getSpecName()].shift() if (snapshot) { return Promise.resolve(_.extend({}, obj, snapshot)) } else { return Promise.reject(new Error('Could not find snapshot for query')) } }) tests() }) } else { withTestDatabase(() => { beforeAll(() => { db.on('query-response', function captureSnapshot (rows, obj) { let specSnapshots = _.get(snapshots, [filepath, getSpecName()]) || [] _.set( snapshots, [filepath, getSpecName(), specSnapshots.length], _.cloneDeep(_.pick(obj, 'sql', 'bindings', 'response')) ) }) }) tests() afterAll(() => { let obj = JSON.stringify(snapshots[filepath] || {}, null, 2) if (!fs.existsSync(dir)) { fs.mkdirSync(dir) } fs.writeFileSync(filepath, `module.exports = ${obj};`) }) }) } } // Extend the global describe object. global.describe.withMockDatabase = function (tests) { describe('with mock database', () => { withMockDatabase(tests) }) } global.describe.withTestDatabase = function (tests) { describe('with test database', () => { withTestDatabase(tests) }) } global.describe.withQuerySnapshots = function (tests) { const caller = callsites()[1] const count = withQuerySnapshots.count++ const description = `with query snapshots(${count})` describe(description, () => { withQuerySnapshots(caller.getFileName(), tests) }) } withQuerySnapshots.count = 0 export default db -
cpsubrian created this gist
Feb 22, 2017 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,99 @@ // lib/__mocks__/db.js /* eslint-env jest */ import path from 'path' import knex from 'knex' import mockDb from 'mock-knex' import conf from 'api/conf' const config = conf.getProperties().database const dirs = { migrations: path.resolve(__dirname, '../../../../migrations'), seeds: path.resolve(__dirname, '../../../../seeds') } // console.log(global) // Test dbs will be stored here. const stack = [] stack.ensure = function () { if (!stack.length) { stack.unshift(knex(config)) } } // Create the knex proxy. This will treat whichever db is at the front // of the stack as the active one. const db = new Proxy({}, { get (target, name) { stack.ensure() if (!(name in stack[0])) { console.warn("Getting non-existant property '" + name + "'") return undefined } return stack[0][name] }, set (target, name, value) { stack.ensure() stack[0][name] = value return true } }) // Destroy any open databases. afterAll(() => { // We need to do this after other afterAll() handlers run. setImmediate(() => { while (stack.length) { stack.shift().destroy() } }) }) // Mock the db and return a tracker. global.test.mockDatabase = function (onQuery) { const tracker = mockDb.getTracker() beforeAll(() => { stack.ensure() mockDb.mock(stack[0]) tracker.install() if (onQuery) { tracker.on('query', onQuery) } }) afterAll(() => { tracker.uninstall() mockDb.unmock(stack[0]) }) return tracker } // Inject a real test database for the current test scenario. global.test.injectTestDatabase = function () { const name = `ac_test__${Date.now()}_${Math.floor(Math.random() * 100)}` beforeAll(() => { return db .raw('CREATE DATABASE ??', [name]) .then(() => { config.connection.database = name stack.unshift(knex(config)) }) .then(() => { return db.migrate.latest({directory: dirs.migrations}) }) .then(() => { return db.seed.run({directory: dirs.seeds}) }) }) afterAll(() => { stack.shift().destroy() return db.raw('DROP DATABASE ??', [name]) }) } export default db