Skip to content

Commit

Permalink
refacor: Use new lib functions to clean up main
Browse files Browse the repository at this point in the history
- removes lab and sinon devDependencies
- rework test to focus on main flow
- update scripts to use tap and require 100% coverage
  • Loading branch information
jackboberg committed Apr 20, 2017
1 parent 2a06623 commit 22bc183
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 215 deletions.
56 changes: 18 additions & 38 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,58 +1,38 @@
const Assert = require('assert')
const Child = require('child_process')
const FS = require('fs')
const Parallel = require('run-parallel')
const Path = require('path')
const Parallel = require('run-parallel')
const { command, project } = require('./lib')

// get details about package and environment
module.exports = (options, done) => {
if (typeof options === 'function') {
done = options
options = {}
module.exports = (commands, done) => {
if (typeof commands === 'function') {
done = commands
commands = {}
}

Assert.equal(typeof options, 'object', 'Options must be an object')
Assert.equal(typeof done, 'function', 'Must pass in a callback function')
Assert(isObject(commands), '`commands` must be an object')
Assert(isFunction(done), 'Must pass in a callback function')

Parallel([
packageDetails,
(next) => process.nextTick(next, null, { env: process.env.NODE_ENV }),
(next) => {
const cmds = Object.assign({ node: 'node -v', npm: 'npm -v' }, options)

commandDetails(cmds, next)
}
(next) => project(Path.dirname(require.main.filename), next),
(next) => commandDetails(commands, next)
], (err, results) => {
if (err) done(err)
else done(null, Object.assign({}, ...results))
done(err, Object.assign({}, ...results))
})
}

// get name and version from package
const packageDetails = (done) => {
const packagePath = Path.join(process.cwd(), 'package.json')

FS.readFile(packagePath, 'utf8', (err, data) => {
if (err) return done(err)

const { name, version } = JSON.parse(data)
const isObject = (value) => typeof value === 'object' && !Array.isArray(value)

done(null, { name, version })
})
}
const isFunction = (value) => typeof value === 'function'

// execute all passed commands and yield results
const commandDetails = (cmds, done) => {
const commandDetails = (commands, done) => {
const cmds = Object.assign({ node: 'node -v', npm: 'npm -v' }, commands)
const tasks = Object.keys(cmds)
.reduce((acc, key) => Object.assign(acc, { [key]: proc(cmds[key]) }), {})
.reduce((acc, key) => Object.assign(acc, {
[key]: (next) => command(cmds[key], next)
}), {})

Parallel(tasks, done)
}

// curry command, return function that yields error string or result
const proc = (cmd) => (done) => {
Child.exec(cmd, (err, stdout) => {
if (err) done(null, err.toString())
else done(null, stdout.replace(/\n/g, ''))
})
}
8 changes: 3 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@
},
"devDependencies": {
"code": "~4.0.0",
"lab": "10.9.0",
"mkdirp": "~0.5.1",
"sinon": "1.17.2",
"standard": "~10.0.2",
"tap": "~10.3.2",
"temporary-directory": "~1.0.2",
Expand All @@ -41,9 +39,9 @@
"url": "git+https://github.com/jackboberg/knock-knock.git"
},
"scripts": {
"coverage": "lab --coverage --reporter lcov --output coverage/lcov.info",
"cov": "npm run unit -- --cov --coverage-report=lcov",
"lint": "standard -v",
"posttest": "npm run lint",
"test": "lab -cvv"
"test": "npm run lint && npm run unit",
"unit": "tap test/*.js -R spec --100"
}
}
224 changes: 52 additions & 172 deletions test/index.js
Original file line number Diff line number Diff line change
@@ -1,200 +1,80 @@
const Child = require('child_process')
const Fs = require('fs')
const Lab = require('lab')
const Path = require('path')
const Sinon = require('sinon')
const PkgFixture = require('./fixtures/package.json')
const TD = require('testdouble')
const { command, project } = TD.replace('../lib')
const { expect } = require('code')

const lab = exports.lab = Lab.script()
const { describe, after, afterEach, before, beforeEach, it } = lab
const { test: describe } = require('tap')

const KnockKnock = require('..')

describe('knock-knock', () => {
const packagePath = Path.join(process.cwd(), 'package.json')
const temp = process.env.NODE_ENV
describe('knock-knock', ({ afterEach, beforeEach, plan, test }) => {
const pkgData = { name: PkgFixture.name, version: PkgFixture.version }
const nodeVersion = 'v1.2.3'
const npmVersion = 'v4.5.6'

plan(4)

let nodeVersion = process.version
let npmVersion
let out = Object.prototype
beforeEach((done) => {
TD.when(command('node -v')).thenCallback(null, nodeVersion)
TD.when(command('npm -v')).thenCallback(null, npmVersion)
TD.when(project(__dirname)).thenCallback(null, pkgData)

before((done) => {
process.env.NODE_ENV = 'testenv'
done()
})

after((done) => {
process.env.NODE_ENV = temp
afterEach((done) => {
TD.reset()

done()
})

describe('mocking methods', () => {
beforeEach((done) => {
out = {
name: 'test',
version: '0.1.0',
env: process.env.NODE_ENV,
node: nodeVersion,
npm: '2.14.7'
}
out = JSON.stringify(out)

Sinon.stub(Child, 'exec').yields(new Error('exec'))
Sinon.stub(Fs, 'readFile').yields(new Error('readFile'))
done()
})
test('yeilds an object', (t) => {
const expected = {
name: PkgFixture.name,
version: PkgFixture.version,
node: nodeVersion,
npm: npmVersion,
env: process.env.NODE_ENV
}

afterEach((done) => {
Child.exec.restore()
Fs.readFile.restore()
KnockKnock((err, result) => {
expect(err).to.not.exist()
expect(result).to.equal(expected)

done()
t.end()
})
})

describe('when called', () => {
beforeEach((done) => {
Child.exec.onFirstCall().yields(null, nodeVersion)
.onSecondCall().yields(null, '2.14.7')
Fs.readFile.yields(null, out)
done()
})

it('yields an object', (done) => {
KnockKnock((err, results) => {
expect(err).to.be.null()
expect(Fs.readFile.calledWith(packagePath)).to.be.true()
expect(results).to.be.an.object()
done()
})
})

describe('the object', () => {
it('has a key called name', (done) => {
KnockKnock((err, results) => {
expect(err).to.be.null()
expect(results.name).to.equal('test')
done()
})
})

it('has a key called version', (done) => {
KnockKnock((err, results) => {
expect(err).to.be.null()
expect(results.version).to.equal('0.1.0')
done()
})
})

it('has a key called env', (done) => {
KnockKnock((err, results) => {
expect(err).to.be.null()
expect(results.env).to.equal('testenv')
done()
})
})

it('has a key called node', (done) => {
KnockKnock((err, results) => {
expect(err).to.be.null()
expect(results.node).to.equal(nodeVersion)
done()
})
})

it('has a key called npm', (done) => {
KnockKnock((err, results) => {
expect(err).to.be.null()
expect(results.npm).to.equal('2.14.7')
done()
})
})
})
})
test('accepts commands map', (t) => {
const commands = { 'test': 'someBin' }
const output = 'some output'

describe('when options are passed in', () => {
beforeEach((done) => {
Child.exec.onFirstCall().yields(null, nodeVersion)
.onSecondCall().yields(null, '2.14.7')
.onThirdCall().yields(null, 'value')
Fs.readFile.yields(null, out)
done()
})

it('executes all commands', (done) => {
KnockKnock({ key: 'value -v' }, (err, results) => {
expect(err).to.be.null()
expect(results.key).to.equal('value')
done()
})
})
})
TD.when(command(commands.test)).thenCallback(null, output)

describe('when package.json is not found', () => {
it('yields an error', (done) => {
KnockKnock((err, results) => {
expect(err).to.be.instanceof(Error)
expect(err.message).to.equal('readFile')
expect(results).to.be.undefined()
done()
})
})
})
KnockKnock(commands, (err, result) => {
expect(err).to.not.exist()
expect(result.test).to.equal(output)

describe('when executing a command fails', () => {
beforeEach((done) => {
Child.exec.onFirstCall().yields(new Error('exec error').toString())
.onSecondCall().yields(new Error('exec error').toString())
Fs.readFile.yields(null, out)
done()
})

it('stringifies the err', (done) => {
KnockKnock((err, results) => {
expect(err).to.be.null()
expect(results.node).to.equal('Error: exec error')
expect(results.npm).to.equal('Error: exec error')
done()
})
})
t.end()
})
})

describe('when options is not an object', () => {
it('throws an error', (done) => {
expect(() => {
return KnockKnock('string', () => {
test('throws when not passed a callback', (t) => {
const fns = [
KnockKnock,
() => KnockKnock({})
]

})
}).to.throw()
done()
})
})
fns.forEach((fn) => expect(fn).to.throw())

describe('when no callback passed', () => {
it('throws an error', (done) => {
expect(() => {
return KnockKnock({})
}).to.throw()
done()
})
})
t.end()
})

describe('handling newline characters', () => {
before((done) => {
// eslint-disable-next-line handle-callback-err
Child.exec('npm -v', (err, stdout) => {
npmVersion = stdout.replace(/\n/g, '')
done()
})
})
test('throws when commands is not an object', (t) => {
const invalid = ['', 1, [], false]
const fns = invalid.map((param) => () => KnockKnock(param, () => {}))

it('removes newline characters', (done) => {
KnockKnock((err, results) => {
expect(err).to.be.null()
expect(results.node).to.equal(nodeVersion)
expect(results.npm).to.equal(npmVersion)
done()
})
})
fns.forEach((fn) => expect(fn).to.throw())

t.end()
})
})

0 comments on commit 22bc183

Please sign in to comment.