Skip to content

Instantly share code, notes, and snippets.

@cpsubrian
Last active June 13, 2023 06:59
Show Gist options
  • Save cpsubrian/b4820b475e7262251a16fb286606e4f7 to your computer and use it in GitHub Desktop.
Save cpsubrian/b4820b475e7262251a16fb286606e4f7 to your computer and use it in GitHub Desktop.

Revisions

  1. cpsubrian revised this gist Feb 8, 2018. 1 changed file with 17 additions and 21 deletions.
    38 changes: 17 additions & 21 deletions db.js
    Original 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 'api/conf'
    import conf from '<conf>'

    const config = conf.getProperties().database
    const dirs = {
    migrations: path.resolve(__dirname, '../../../../migrations'),
    seeds: path.resolve(__dirname, '../../../../seeds')
    }
    // 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 open databases.
    // Destroy any accidentally open databases.
    afterAll(() => {
    // We need to do this after other afterAll() handlers run.
    setImmediate(() => {
    while (stack.length) {
    stack.shift().destroy()
    }
    })
    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 ??', [name])
    .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))
    })
    .then(() => {
    return db.migrate.latest({directory: dirs.migrations})
    })
    .then(() => {
    return db.seed.run({directory: dirs.seeds})
    })
    })

    tests(name)
    @@ -248,4 +244,4 @@ global.describe.withQuerySnapshots = function (description, tests) {
    })
    }

    export default db
    export default db
  2. cpsubrian revised this gist Mar 13, 2017. 1 changed file with 31 additions and 11 deletions.
    42 changes: 31 additions & 11 deletions db.js
    Original 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({}, {
    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 snapshot = cached[getSpecName()] && cached[getSpecName()].shift()
    let specName = getSpecName()
    let queryHash = hash(obj.sql)
    let querySnaps = _.get(cached, [specName, queryHash]) || []
    let snapshot = querySnaps.shift()
    if (snapshot) {
    return Promise.resolve(_.extend({}, obj, snapshot))
    if (snapshot.error) {
    throw _.extend(new Error(snapshot.error.message), snapshot.error.data)
    } else {
    return Promise.resolve(_.extend({}, obj, snapshot))
    }
    } else {
    return Promise.reject(new Error('Could not find snapshot for query'))
    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) {
    let specSnapshots = _.get(snapshots, [filepath, getSpecName()]) || []
    _.set(
    snapshots,
    [filepath, getSpecName(), specSnapshots.length],
    _.cloneDeep(_.pick(obj, 'sql', 'bindings', 'response'))
    )
    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]})`
    return str + (counts[str] ? `(${counts[str]})` : '')
    }

    // Extend the global describe object.
  3. cpsubrian revised this gist Mar 3, 2017. 1 changed file with 36 additions and 12 deletions.
    48 changes: 36 additions & 12 deletions db.js
    Original 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 {
    return _.last(suites).description
    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()].shift()
    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(() => {
    let obj = JSON.stringify(snapshots[filepath] || {}, null, 2)
    if (!fs.existsSync(dir)) {
    fs.mkdirSync(dir)
    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};`)
    }
    fs.writeFileSync(filepath, `module.exports = ${obj};`)
    })
    })
    }
    @@ -187,19 +199,31 @@ function appendCount (str) {
    }

    // Extend the global describe object.
    global.describe.withMockDatabase = function (tests) {
    describe(appendCount('with mock database'), () => {
    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 (tests) {
    describe(appendCount('with test database'), () => {
    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 (tests) {
    global.describe.withQuerySnapshots = function (description, tests) {
    const caller = callsites()[1]
    describe(appendCount('with query snapshots'), () => {
    if (typeof description === 'function') {
    tests = description
    description = 'with query snapshots'
    }
    describe(appendCount(description), () => {
    withQuerySnapshots(caller.getFileName(), tests)
    })
    }
  4. cpsubrian revised this gist Feb 26, 2017. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion db.js
    Original 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
    counts[str] = counts[str] ? ++counts[str] : 0
    return str + `(${counts[str]})`
    }

  5. cpsubrian revised this gist Feb 26, 2017. 1 changed file with 10 additions and 5 deletions.
    15 changes: 10 additions & 5 deletions db.js
    Original 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('with mock database', () => {
    describe(appendCount('with mock database'), () => {
    withMockDatabase(tests)
    })
    }
    global.describe.withTestDatabase = function (tests) {
    describe('with test database', () => {
    describe(appendCount('with test database'), () => {
    withTestDatabase(tests)
    })
    }
    global.describe.withQuerySnapshots = function (tests) {
    const caller = callsites()[1]
    const count = withQuerySnapshots.count++
    describe(`with query snapshots(${count})`, () => {
    describe(appendCount('with query snapshots'), () => {
    withQuerySnapshots(caller.getFileName(), tests)
    })
    }
    withQuerySnapshots.count = 0

    export default db
  6. cpsubrian revised this gist Feb 26, 2017. 1 changed file with 1 addition and 2 deletions.
    3 changes: 1 addition & 2 deletions db.js
    Original 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++
    const description = `with query snapshots(${count})`
    describe(description, () => {
    describe(`with query snapshots(${count})`, () => {
    withQuerySnapshots(caller.getFileName(), tests)
    })
    }
  7. cpsubrian revised this gist Feb 26, 2017. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion db.js
    Original file line number Diff line number Diff line change
    @@ -69,7 +69,7 @@ afterAll(() => {
    })
    })

    // Mock the db and return a tracker.
    // Mock the db.client and run tests with overridable mocks.
    function withMockDatabase (tests) {
    let mocks = {
    _query: jest.fn((conn, obj) => {
  8. cpsubrian revised this gist Feb 26, 2017. 1 changed file with 60 additions and 0 deletions.
    60 changes: 60 additions & 0 deletions example.test.js
    Original 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)
    })
    })
    })
  9. cpsubrian revised this gist Feb 26, 2017. 1 changed file with 124 additions and 20 deletions.
    144 changes: 124 additions & 20 deletions db.js
    Original file line number Diff line number Diff line change
    @@ -1,9 +1,9 @@
    // lib/__mocks__/db.js

    /* eslint-env jest */
    import _ from 'lodash'
    import path from 'path'
    import fs from 'fs'
    import callsites from 'callsites'
    import knex from 'knex'
    import mockDb from 'mock-knex'
    import conf from 'api/conf'

    const config = conf.getProperties().database
    @@ -12,7 +12,26 @@ const dirs = {
    seeds: path.resolve(__dirname, '../../../../seeds')
    }

    // console.log(global)
    // 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.
    global.test.mockDatabase = function (onQuery) {
    const tracker = mockDb.getTracker()
    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()
    mockDb.mock(stack[0])
    tracker.install()
    if (onQuery) {
    tracker.on('query', onQuery)
    }
    // Override prototype methods with instance properties.
    _.each(mocks, (val, key) => {
    db.client[key] = val
    })
    })

    tests(mocks)

    afterAll(() => {
    tracker.uninstall()
    mockDb.unmock(stack[0])
    // Remove instance properties to restore prototype versions.
    _.each(mocks, (val, key) => {
    delete db.client[key]
    })
    })

    return tracker
    }

    // Inject a real test database for the current test scenario.
    global.test.injectTestDatabase = function () {
    function withTestDatabase (tests) {
    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))
    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(() => {
    stack.shift().destroy()
    return db.raw('DROP DATABASE ??', [name])
    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
  10. cpsubrian created this gist Feb 22, 2017.
    99 changes: 99 additions & 0 deletions db.js
    Original 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