diff --git a/gulpfile.coffee b/gulpfile.coffee index 314befa6..3fd920c2 100644 --- a/gulpfile.coffee +++ b/gulpfile.coffee @@ -10,7 +10,7 @@ OPTIONS = files: coffee: [ 'lib/**/*.coffee', 'gulpfile.coffee' ] app: 'lib/**/*.coffee' - tests: 'tests/**/*.spec.coffee' + tests: 'tests/**/*.spec.js' pages: 'lib/auth/pages/*.ejs' directories: build: 'build/' diff --git a/tests/auth/server.spec.coffee b/tests/auth/server.spec.coffee deleted file mode 100644 index d71fb2de..00000000 --- a/tests/auth/server.spec.coffee +++ /dev/null @@ -1,128 +0,0 @@ -chai = require('chai') -chaiAsPromised = require('chai-as-promised') -request = require('request') -sinon = require('sinon') -Promise = require('bluebird') -path = require('path') -fs = require('fs') -ejs = require('ejs') -server = require('../../build/auth/server') -utils = require('../../build/auth/utils') -tokens = require('./tokens.json') - -chai.use(chaiAsPromised) - -options = - port: 3000 - path: '/auth' - -getPage = (name) -> - pagePath = path.join(__dirname, '..', '..', 'build', 'auth', 'pages', "#{name}.ejs") - tpl = fs.readFileSync(pagePath, encoding: 'utf8') - compiledTpl = ejs.compile(tpl) - return server.getContext(name) - .then (context) -> - compiledTpl(context) - -describe 'Server:', -> - - it 'should get 404 if posting to an unknown path', (done) -> - promise = server.awaitForToken(options) - chai.expect(promise).to.be.rejectedWith('Unknown path or verb') - - request.post "http://localhost:#{options.port}/foobarbaz", - form: - token: tokens.johndoe.token - , (error, response, body) -> - chai.expect(error).to.not.exist - chai.expect(response.statusCode).to.equal(404) - chai.expect(body).to.equal('Not found') - done() - - it 'should get 404 if not using the correct verb', (done) -> - promise = server.awaitForToken(options) - chai.expect(promise).to.be.rejectedWith('Unknown path or verb') - - request.get "http://localhost:#{options.port}#{options.path}", - form: - token: tokens.johndoe.token - , (error, response, body) -> - chai.expect(error).to.not.exist - chai.expect(response.statusCode).to.equal(404) - chai.expect(body).to.equal('Not found') - done() - - describe 'given the token authenticates with the server', -> - - beforeEach -> - @loginIfTokenValidStub = sinon.stub(utils, 'loginIfTokenValid') - @loginIfTokenValidStub.returns(Promise.resolve(true)) - - afterEach -> - @loginIfTokenValidStub.restore() - - it 'should eventually be the token', (done) -> - promise = server.awaitForToken(options) - chai.expect(promise).to.eventually.equal(tokens.johndoe.token) - - request.post "http://localhost:#{options.port}#{options.path}", - form: - token: tokens.johndoe.token - , (error, response, body) -> - chai.expect(error).to.not.exist - chai.expect(response.statusCode).to.equal(200) - getPage('success').then (expectedBody) -> - chai.expect(body).to.equal(expectedBody) - done() - - describe 'given the token does not authenticate with the server', -> - - beforeEach -> - @loginIfTokenValidStub = sinon.stub(utils, 'loginIfTokenValid') - @loginIfTokenValidStub.returns(Promise.resolve(false)) - - afterEach -> - @loginIfTokenValidStub.restore() - - it 'should be rejected', (done) -> - promise = server.awaitForToken(options) - chai.expect(promise).to.be.rejectedWith('Invalid token') - - request.post "http://localhost:#{options.port}#{options.path}", - form: - token: tokens.johndoe.token - , (error, response, body) -> - chai.expect(error).to.not.exist - chai.expect(response.statusCode).to.equal(401) - getPage('error').then (expectedBody) -> - chai.expect(body).to.equal(expectedBody) - done() - - it 'should be rejected if no token', (done) -> - promise = server.awaitForToken(options) - chai.expect(promise).to.be.rejectedWith('No token') - - request.post "http://localhost:#{options.port}#{options.path}", - form: - token: '' - , (error, response, body) -> - chai.expect(error).to.not.exist - chai.expect(response.statusCode).to.equal(401) - getPage('error').then (expectedBody) -> - chai.expect(body).to.equal(expectedBody) - done() - - it 'should be rejected if token is malformed', (done) -> - promise = server.awaitForToken(options) - chai.expect(promise).to.be.rejectedWith('Invalid token') - - request.post "http://localhost:#{options.port}#{options.path}", - form: - token: 'asdf' - , (error, response, body) -> - chai.expect(error).to.not.exist - chai.expect(response.statusCode).to.equal(401) - getPage('error').then (expectedBody) -> - chai.expect(body).to.equal(expectedBody) - done() - diff --git a/tests/auth/server.spec.js b/tests/auth/server.spec.js new file mode 100644 index 00000000..9d92e1fe --- /dev/null +++ b/tests/auth/server.spec.js @@ -0,0 +1,164 @@ +const chai = require('chai'); +const chaiAsPromised = require('chai-as-promised'); +const request = require('request'); +const sinon = require('sinon'); +const Promise = require('bluebird'); +const path = require('path'); +const fs = require('fs'); +const ejs = require('ejs'); +const server = require('../../build/auth/server'); +const utils = require('../../build/auth/utils'); +const tokens = require('./tokens.json'); + +chai.use(chaiAsPromised); + +let options = { + port: 3000, + path: '/auth' +}; + +let getPage = function(name) { + let pagePath = path.join(__dirname, '..', '..', 'build', 'auth', 'pages', `${name}.ejs`); + let tpl = fs.readFileSync(pagePath, {encoding: 'utf8'}); + let compiledTpl = ejs.compile(tpl); + return server.getContext(name) + .then(context => compiledTpl(context)); +}; + +describe('Server:', function() { + + it('should get 404 if posting to an unknown path', function(done) { + let promise = server.awaitForToken(options); + chai.expect(promise).to.be.rejectedWith('Unknown path or verb'); + + return request.post(`http://localhost:${options.port}/foobarbaz`, { + form: { + token: tokens.johndoe.token + } + } + , function(error, response, body) { + chai.expect(error).to.not.exist; + chai.expect(response.statusCode).to.equal(404); + chai.expect(body).to.equal('Not found'); + return done(); + }); + }); + + it('should get 404 if not using the correct verb', function(done) { + let promise = server.awaitForToken(options); + chai.expect(promise).to.be.rejectedWith('Unknown path or verb'); + + return request.get(`http://localhost:${options.port}${options.path}`, { + form: { + token: tokens.johndoe.token + } + } + , function(error, response, body) { + chai.expect(error).to.not.exist; + chai.expect(response.statusCode).to.equal(404); + chai.expect(body).to.equal('Not found'); + return done(); + }); + }); + + describe('given the token authenticates with the server', function() { + + beforeEach(function() { + this.loginIfTokenValidStub = sinon.stub(utils, 'loginIfTokenValid'); + return this.loginIfTokenValidStub.returns(Promise.resolve(true)); + }); + + afterEach(function() { + return this.loginIfTokenValidStub.restore(); + }); + + return it('should eventually be the token', function(done) { + let promise = server.awaitForToken(options); + chai.expect(promise).to.eventually.equal(tokens.johndoe.token); + + return request.post(`http://localhost:${options.port}${options.path}`, { + form: { + token: tokens.johndoe.token + } + } + , function(error, response, body) { + chai.expect(error).to.not.exist; + chai.expect(response.statusCode).to.equal(200); + return getPage('success').then(function(expectedBody) { + chai.expect(body).to.equal(expectedBody); + return done(); + }); + }); + }); + }); + + return describe('given the token does not authenticate with the server', function() { + + beforeEach(function() { + this.loginIfTokenValidStub = sinon.stub(utils, 'loginIfTokenValid'); + return this.loginIfTokenValidStub.returns(Promise.resolve(false)); + }); + + afterEach(function() { + return this.loginIfTokenValidStub.restore(); + }); + + it('should be rejected', function(done) { + let promise = server.awaitForToken(options); + chai.expect(promise).to.be.rejectedWith('Invalid token'); + + return request.post(`http://localhost:${options.port}${options.path}`, { + form: { + token: tokens.johndoe.token + } + } + , function(error, response, body) { + chai.expect(error).to.not.exist; + chai.expect(response.statusCode).to.equal(401); + return getPage('error').then(function(expectedBody) { + chai.expect(body).to.equal(expectedBody); + return done(); + }); + }); + }); + + it('should be rejected if no token', function(done) { + let promise = server.awaitForToken(options); + chai.expect(promise).to.be.rejectedWith('No token'); + + return request.post(`http://localhost:${options.port}${options.path}`, { + form: { + token: '' + } + } + , function(error, response, body) { + chai.expect(error).to.not.exist; + chai.expect(response.statusCode).to.equal(401); + return getPage('error').then(function(expectedBody) { + chai.expect(body).to.equal(expectedBody); + return done(); + }); + }); + }); + + return it('should be rejected if token is malformed', function(done) { + let promise = server.awaitForToken(options); + chai.expect(promise).to.be.rejectedWith('Invalid token'); + + return request.post(`http://localhost:${options.port}${options.path}`, { + form: { + token: 'asdf' + } + } + , function(error, response, body) { + chai.expect(error).to.not.exist; + chai.expect(response.statusCode).to.equal(401); + return getPage('error').then(function(expectedBody) { + chai.expect(body).to.equal(expectedBody); + return done(); + }); + }); + }); + }); +}); + diff --git a/tests/auth/utils.spec.coffee b/tests/auth/utils.spec.coffee deleted file mode 100644 index d487214b..00000000 --- a/tests/auth/utils.spec.coffee +++ /dev/null @@ -1,112 +0,0 @@ -chai = require('chai') -sinon = require('sinon') -url = require('url') -Promise = require('bluebird') - -tokens = require('./tokens.json') - -rewire = require('rewire') -utils = rewire('../../build/auth/utils') -balena = utils.__get__('balena') - -describe 'Utils:', -> - - describe '.getDashboardLoginURL()', -> - - it 'should eventually be a valid url', -> - utils.getDashboardLoginURL('https://127.0.0.1:3000/callback').then (loginUrl) -> - chai.expect -> - url.parse(loginUrl) - .to.not.throw(Error) - - - it 'should eventually contain an https protocol', -> - Promise.props - dashboardUrl: balena.settings.get('dashboardUrl') - loginUrl: utils.getDashboardLoginURL('https://127.0.0.1:3000/callback') - .then ({ dashboardUrl, loginUrl }) -> - protocol = url.parse(loginUrl).protocol - chai.expect(protocol).to.equal(url.parse(dashboardUrl).protocol) - - it 'should correctly escape a callback url without a path', -> - Promise.props - dashboardUrl: balena.settings.get('dashboardUrl') - loginUrl: utils.getDashboardLoginURL('http://127.0.0.1:3000') - .then ({ dashboardUrl, loginUrl }) -> - expectedUrl = "#{dashboardUrl}/login/cli/http%253A%252F%252F127.0.0.1%253A3000" - chai.expect(loginUrl).to.equal(expectedUrl) - - it 'should correctly escape a callback url with a path', -> - Promise.props - dashboardUrl: balena.settings.get('dashboardUrl') - loginUrl: utils.getDashboardLoginURL('http://127.0.0.1:3000/callback') - .then ({ dashboardUrl, loginUrl }) -> - expectedUrl = "#{dashboardUrl}/login/cli/http%253A%252F%252F127.0.0.1%253A3000%252Fcallback" - chai.expect(loginUrl).to.equal(expectedUrl) - - describe '.loginIfTokenValid()', -> - - it 'should eventually be false if token is undefined', -> - promise = utils.loginIfTokenValid(undefined) - chai.expect(promise).to.eventually.be.false - - it 'should eventually be false if token is null', -> - promise = utils.loginIfTokenValid(null) - chai.expect(promise).to.eventually.be.false - - it 'should eventually be false if token is an empty string', -> - promise = utils.loginIfTokenValid('') - chai.expect(promise).to.eventually.be.false - - it 'should eventually be false if token is a string containing only spaces', -> - promise = utils.loginIfTokenValid(' ') - chai.expect(promise).to.eventually.be.false - - describe 'given the token does not authenticate with the server', -> - - beforeEach -> - @balenaAuthIsLoggedInStub = sinon.stub(balena.auth, 'isLoggedIn') - @balenaAuthIsLoggedInStub.returns(Promise.resolve(false)) - - afterEach -> - @balenaAuthIsLoggedInStub.restore() - - it 'should eventually be false', -> - promise = utils.loginIfTokenValid(tokens.johndoe.token) - chai.expect(promise).to.eventually.be.false - - describe 'given there was a token already', -> - - beforeEach -> - balena.auth.loginWithToken(tokens.janedoe.token) - - it 'should preserve the old token', -> - balena.auth.getToken().then (originalToken) -> - chai.expect(originalToken).to.equal(tokens.janedoe.token) - return utils.loginIfTokenValid(tokens.johndoe.token) - .then(balena.auth.getToken).then (currentToken) -> - chai.expect(currentToken).to.equal(tokens.janedoe.token) - - describe 'given there was no token', -> - - beforeEach -> - balena.auth.logout() - - it 'should stay without a token', -> - utils.loginIfTokenValid(tokens.johndoe.token).then -> - balena.auth.isLoggedIn() - .then (isLoggedIn) -> - chai.expect(isLoggedIn).to.equal(false) - - describe 'given the token does authenticate with the server', -> - - beforeEach -> - @balenaAuthIsLoggedInStub = sinon.stub(balena.auth, 'isLoggedIn') - @balenaAuthIsLoggedInStub.returns(Promise.resolve(true)) - - afterEach -> - @balenaAuthIsLoggedInStub.restore() - - it 'should eventually be true', -> - promise = utils.loginIfTokenValid(tokens.johndoe.token) - chai.expect(promise).to.eventually.be.true diff --git a/tests/auth/utils.spec.js b/tests/auth/utils.spec.js new file mode 100644 index 00000000..fb3a8667 --- /dev/null +++ b/tests/auth/utils.spec.js @@ -0,0 +1,127 @@ +const chai = require('chai'); +const sinon = require('sinon'); +const url = require('url'); +const Promise = require('bluebird'); + +const tokens = require('./tokens.json'); + +const rewire = require('rewire'); +let utils = rewire('../../build/auth/utils'); +let balena = utils.__get__('balena'); + +describe('Utils:', function() { + + describe('.getDashboardLoginURL()', function() { + + it('should eventually be a valid url', () => + utils.getDashboardLoginURL('https://127.0.0.1:3000/callback').then(loginUrl => + chai.expect(() => url.parse(loginUrl)).to.not.throw(Error) + ) + ); + + + it('should eventually contain an https protocol', () => + Promise.props({ + dashboardUrl: balena.settings.get('dashboardUrl'), + loginUrl: utils.getDashboardLoginURL('https://127.0.0.1:3000/callback')}).then(function({ dashboardUrl, loginUrl }) { + let { protocol } = url.parse(loginUrl); + return chai.expect(protocol).to.equal(url.parse(dashboardUrl).protocol); + }) + ); + + it('should correctly escape a callback url without a path', () => + Promise.props({ + dashboardUrl: balena.settings.get('dashboardUrl'), + loginUrl: utils.getDashboardLoginURL('http://127.0.0.1:3000')}).then(function({ dashboardUrl, loginUrl }) { + let expectedUrl = `${dashboardUrl}/login/cli/http%253A%252F%252F127.0.0.1%253A3000`; + return chai.expect(loginUrl).to.equal(expectedUrl); + }) + ); + + return it('should correctly escape a callback url with a path', () => + Promise.props({ + dashboardUrl: balena.settings.get('dashboardUrl'), + loginUrl: utils.getDashboardLoginURL('http://127.0.0.1:3000/callback')}).then(function({ dashboardUrl, loginUrl }) { + let expectedUrl = `${dashboardUrl}/login/cli/http%253A%252F%252F127.0.0.1%253A3000%252Fcallback`; + return chai.expect(loginUrl).to.equal(expectedUrl); + }) + ); + }); + + return describe('.loginIfTokenValid()', function() { + + it('should eventually be false if token is undefined', function() { + let promise = utils.loginIfTokenValid(undefined); + return chai.expect(promise).to.eventually.be.false; + }); + + it('should eventually be false if token is null', function() { + let promise = utils.loginIfTokenValid(null); + return chai.expect(promise).to.eventually.be.false; + }); + + it('should eventually be false if token is an empty string', function() { + let promise = utils.loginIfTokenValid(''); + return chai.expect(promise).to.eventually.be.false; + }); + + it('should eventually be false if token is a string containing only spaces', function() { + let promise = utils.loginIfTokenValid(' '); + return chai.expect(promise).to.eventually.be.false; + }); + + describe('given the token does not authenticate with the server', function() { + + beforeEach(function() { + this.balenaAuthIsLoggedInStub = sinon.stub(balena.auth, 'isLoggedIn'); + return this.balenaAuthIsLoggedInStub.returns(Promise.resolve(false)); + }); + + afterEach(function() { + return this.balenaAuthIsLoggedInStub.restore(); + }); + + it('should eventually be false', function() { + let promise = utils.loginIfTokenValid(tokens.johndoe.token); + return chai.expect(promise).to.eventually.be.false; + }); + + describe('given there was a token already', function() { + + beforeEach(() => balena.auth.loginWithToken(tokens.janedoe.token)); + + return it('should preserve the old token', () => + balena.auth.getToken().then(function(originalToken) { + chai.expect(originalToken).to.equal(tokens.janedoe.token); + return utils.loginIfTokenValid(tokens.johndoe.token);}).then(balena.auth.getToken).then(currentToken => chai.expect(currentToken).to.equal(tokens.janedoe.token)) + ); + }); + + return describe('given there was no token', function() { + + beforeEach(() => balena.auth.logout()); + + return it('should stay without a token', () => + utils.loginIfTokenValid(tokens.johndoe.token).then(() => balena.auth.isLoggedIn()).then(isLoggedIn => chai.expect(isLoggedIn).to.equal(false)) + ); + }); + }); + + return describe('given the token does authenticate with the server', function() { + + beforeEach(function() { + this.balenaAuthIsLoggedInStub = sinon.stub(balena.auth, 'isLoggedIn'); + return this.balenaAuthIsLoggedInStub.returns(Promise.resolve(true)); + }); + + afterEach(function() { + return this.balenaAuthIsLoggedInStub.restore(); + }); + + return it('should eventually be true', function() { + let promise = utils.loginIfTokenValid(tokens.johndoe.token); + return chai.expect(promise).to.eventually.be.true; + }); + }); + }); +}); diff --git a/tests/utils/ignore.spec.coffee b/tests/utils/ignore.spec.coffee deleted file mode 100644 index a0c4a65f..00000000 --- a/tests/utils/ignore.spec.coffee +++ /dev/null @@ -1,84 +0,0 @@ -chai = require 'chai' -_ = require 'lodash' -path = require('path') - -expect = chai.expect - -{ FileIgnorer, IgnoreFileType } = require '../../build/utils/ignore' - -describe 'File ignorer', -> - - it 'should detect ignore files', -> - f = new FileIgnorer('.' + path.sep) - expect(f.getIgnoreFileType('.gitignore')).to.equal(IgnoreFileType.GitIgnore) - expect(f.getIgnoreFileType('.dockerignore')).to.equal(IgnoreFileType.DockerIgnore) - expect(f.getIgnoreFileType('./.gitignore')).to.equal(IgnoreFileType.GitIgnore) - expect(f.getIgnoreFileType('./.dockerignore')).to.equal(IgnoreFileType.DockerIgnore) - - # gitignore files can appear in subdirectories, but dockerignore files cannot - expect(f.getIgnoreFileType('./subdir/.gitignore')).to.equal(IgnoreFileType.GitIgnore) - expect(f.getIgnoreFileType('./subdir/.dockerignore')).to.equal(null) - expect(f.getIgnoreFileType('./subdir/subdir2/.gitignore')).to.equal(IgnoreFileType.GitIgnore) - - expect(f.getIgnoreFileType('file')).to.equal(null) - expect(f.getIgnoreFileType('./file')).to.equal(null) - - it 'should filter files from the root directory', -> - - ignore = new FileIgnorer('.' + path.sep) - ignore.gitIgnoreEntries = [ - { pattern: '*.ignore', filePath: '.gitignore' } - ] - ignore.dockerIgnoreEntries = [ - { pattern: '*.ignore2', filePath: '.dockerignore' } - ] - files = [ - 'a' - 'a/b' - 'a/b/c' - 'file.ignore' - 'file2.ignore' - 'file.ignore2' - 'file2.ignore' - ] - - expect(_.filter(files, ignore.filter.bind(ignore))).to.deep.equal([ - 'a' - 'a/b' - 'a/b/c' - ]) - - it 'should filter files from subdirectories', -> - - ignore = new FileIgnorer('.' + path.sep) - ignore.gitIgnoreEntries = [ - { pattern: '*.ignore', filePath: 'lib/.gitignore' } - ] - files = [ - 'test.ignore' - 'root.ignore' - 'lib/normal-file' - 'lib/should.ignore' - 'lib/thistoo.ignore' - ] - expect(_.filter(files, ignore.filter.bind(ignore))).to.deep.equal([ - 'test.ignore' - 'root.ignore' - 'lib/normal-file' - ]) - - ignore.gitIgnoreEntries = [ - { pattern: '*.ignore', filePath: './lib/.gitignore' } - ] - files = [ - 'test.ignore' - 'root.ignore' - 'lib/normal-file' - 'lib/should.ignore' - 'lib/thistoo.ignore' - ] - expect(_.filter(files, ignore.filter.bind(ignore))).to.deep.equal([ - 'test.ignore' - 'root.ignore' - 'lib/normal-file' - ]) diff --git a/tests/utils/ignore.spec.js b/tests/utils/ignore.spec.js new file mode 100644 index 00000000..b7000eb4 --- /dev/null +++ b/tests/utils/ignore.spec.js @@ -0,0 +1,88 @@ +const chai = require('chai'); +const _ = require('lodash'); +const path = require('path'); + +let { expect } = chai; + +const { FileIgnorer, IgnoreFileType } = require('../../build/utils/ignore'); + +describe('File ignorer', function() { + + it('should detect ignore files', function() { + let f = new FileIgnorer(`.${path.sep}`); + expect(f.getIgnoreFileType('.gitignore')).to.equal(IgnoreFileType.GitIgnore); + expect(f.getIgnoreFileType('.dockerignore')).to.equal(IgnoreFileType.DockerIgnore); + expect(f.getIgnoreFileType('./.gitignore')).to.equal(IgnoreFileType.GitIgnore); + expect(f.getIgnoreFileType('./.dockerignore')).to.equal(IgnoreFileType.DockerIgnore); + + // gitignore files can appear in subdirectories, but dockerignore files cannot + expect(f.getIgnoreFileType('./subdir/.gitignore')).to.equal(IgnoreFileType.GitIgnore); + expect(f.getIgnoreFileType('./subdir/.dockerignore')).to.equal(null); + expect(f.getIgnoreFileType('./subdir/subdir2/.gitignore')).to.equal(IgnoreFileType.GitIgnore); + + expect(f.getIgnoreFileType('file')).to.equal(null); + return expect(f.getIgnoreFileType('./file')).to.equal(null); + }); + + it('should filter files from the root directory', function() { + + let ignore = new FileIgnorer(`.${path.sep}`); + ignore.gitIgnoreEntries = [ + { pattern: '*.ignore', filePath: '.gitignore' } + ]; + ignore.dockerIgnoreEntries = [ + { pattern: '*.ignore2', filePath: '.dockerignore' } + ]; + let files = [ + 'a', + 'a/b', + 'a/b/c', + 'file.ignore', + 'file2.ignore', + 'file.ignore2', + 'file2.ignore' + ]; + + return expect(_.filter(files, ignore.filter.bind(ignore))).to.deep.equal([ + 'a', + 'a/b', + 'a/b/c' + ]); + }); + + return it('should filter files from subdirectories', function() { + + let ignore = new FileIgnorer(`.${path.sep}`); + ignore.gitIgnoreEntries = [ + { pattern: '*.ignore', filePath: 'lib/.gitignore' } + ]; + let files = [ + 'test.ignore', + 'root.ignore', + 'lib/normal-file', + 'lib/should.ignore', + 'lib/thistoo.ignore' + ]; + expect(_.filter(files, ignore.filter.bind(ignore))).to.deep.equal([ + 'test.ignore', + 'root.ignore', + 'lib/normal-file' + ]); + + ignore.gitIgnoreEntries = [ + { pattern: '*.ignore', filePath: './lib/.gitignore' } + ]; + files = [ + 'test.ignore', + 'root.ignore', + 'lib/normal-file', + 'lib/should.ignore', + 'lib/thistoo.ignore' + ]; + return expect(_.filter(files, ignore.filter.bind(ignore))).to.deep.equal([ + 'test.ignore', + 'root.ignore', + 'lib/normal-file' + ]); + }); +});