UNPKG

filehound

Version:
1,172 lines (989 loc) 30.3 kB
import assert from 'assert'; import fs from 'fs'; import path from 'path'; import FileHound from '../lib/filehound'; import moment from 'moment'; import sinon from 'sinon'; import File from 'file-js'; import Promise from 'bluebird'; const justFiles = qualifyNames(['/justFiles/a.json', '/justFiles/b.json', '/justFiles/dummy.txt']); const nestedFiles = qualifyNames(['/nested/c.json', 'nested/d.json', '/nested/mydir/e.json']); const textFiles = qualifyNames(['/justFiles/dummy.txt']); const mixedExtensions = qualifyNames(['/ext/dummy.json', '/ext/dummy.txt']); const matchFiles = qualifyNames(['/mixed/aabbcc.json', '/mixed/ab.json']); const sandbox = sinon.createSandbox(); function getAbsolutePath(file) { return path.join(__dirname + '/fixtures/', file); } function qualifyNames(names) { return names.map(getAbsolutePath); } function createFile(fname, opts) { const time = new Date(moment().subtract(opts.duration, opts.modifier)); const fd = fs.openSync(fname, 'w+'); fs.futimesSync(fd, time, time); fs.closeSync(fd); } function deleteFile(fname) { return fs.unlinkSync(fname); } describe('FileHound', () => { const fixtureDir = __dirname + '/fixtures'; describe('.socket', () => { const file = { isSocket: () => { return true; }, isDirectorySync: () => { return false; }, getName: () => { return getAbsolutePath('/types/socket1'); } }; beforeEach(() => { const root = { getDepthSync: () => { return 0; }, getFiles: () => { return Promise.resolve().then(() => { return [file]; }); } }; sandbox.stub(File, 'create').returns(root); }); afterEach(() => { sandbox.restore(); }); it('filters by socket type files', () => { const query = FileHound.create() .paths(fixtureDir + '/types') .socket() .find(); return query .then((sockets) => { assert.deepEqual(sockets, [file.getName()]); }); }); }); describe('.directory', () => { it('returns sub-directories of a given directory', () => { const expectedDirectories = qualifyNames([ '/deeplyNested/mydir', '/deeplyNested/mydir/mydir2', '/deeplyNested/mydir/mydir2/mydir3', '/deeplyNested/mydir/mydir2/mydir3/mydir4']); const query = FileHound.create() .paths(fixtureDir + '/deeplyNested') .directory() .find(); return query .then((directories) => { assert.deepEqual(directories, expectedDirectories); }); }); it('ignores hidden directories', () => { const expectedDirectories = qualifyNames([ '/deeplyNestedWithHiddenDir/mydir', '/deeplyNestedWithHiddenDir/mydir/mydir2', '/deeplyNestedWithHiddenDir/mydir/mydir2/mydir3', '/deeplyNestedWithHiddenDir/mydir/mydir2/mydir3/mydir4']); const query = FileHound.create() .paths(fixtureDir + '/deeplyNestedWithHiddenDir') .directory() .ignoreHiddenDirectories() .find(); return query .then((directories) => { assert.deepEqual(directories, expectedDirectories); }); }); it('filters matching directories', () => { const expectedDirectories = qualifyNames([ '/deeplyNested/mydir', '/deeplyNested/mydir/mydir2', '/deeplyNested/mydir/mydir2/mydir3']); const query = FileHound.create() .paths(fixtureDir + '/deeplyNested') .directory() .match('*dir4*') .not() .find(); return query .then((directories) => { assert.deepEqual(directories, expectedDirectories); }); }); }); describe('.depth', () => { it('only returns files in the current directory', () => { const query = FileHound.create() .paths(fixtureDir + '/deeplyNested') .depth(0) .find(); return query .then((files) => { assert.deepEqual(files, qualifyNames(['/deeplyNested/c.json', 'deeplyNested/d.json'])); }); }); it('only returns files one level deep', () => { const query = FileHound.create() .paths(fixtureDir + '/deeplyNested') .depth(1) .find(); return query .then((files) => { assert.deepEqual(files, qualifyNames([ '/deeplyNested/c.json', 'deeplyNested/d.json', 'deeplyNested/mydir/e.json'])); }); }); it('returns files n level deep', () => { const query = FileHound.create() .paths(fixtureDir + '/deeplyNested') .depth(3) .find(); return query .then((files) => { files.sort(); assert.deepEqual(files, qualifyNames([ 'deeplyNested/c.json', 'deeplyNested/d.json', 'deeplyNested/mydir/e.json', 'deeplyNested/mydir/mydir2/f.json', 'deeplyNested/mydir/mydir2/mydir3/z.json', 'deeplyNested/mydir/mydir2/y.json' ])); }); }); it('returns files n level deep relative to path', () => { const query = FileHound.create() .paths(fixtureDir + '/deeplyNested', fixtureDir + '/deeplyNested/mydir') .depth(0) .find(); return query .then((files) => { files.sort(); assert.deepEqual(files, qualifyNames([ 'deeplyNested/c.json', 'deeplyNested/d.json', 'deeplyNested/mydir/e.json', ])); }); }); }); describe('.path', () => { it('returns all files in a given directory', () => { const query = FileHound.create() .path(fixtureDir + '/justFiles') .find(); return query .then((files) => { assert.deepEqual(files, justFiles); }); }); it('ignores all paths except the first', () => { const location1 = fixtureDir + '/justFiles'; const location2 = fixtureDir + '/nested'; const query = FileHound.create() .path(location1, location2) .find(); return query .then((files) => { assert.deepEqual(files, justFiles); }); }); it('returns an error when a given path is invalid', () => { const badLocation = fixtureDir + '/justBad'; const query = FileHound.create() .path(badLocation) .find(); return query .catch((err) => { assert.ok(err); }); }); }); describe('.includeFileStats', () => { function includeFileStats(file) { return { path: file.path, stats: fs.statSync(file.path) }; } it('returns a file object containing a path and file stats', () => { const query = FileHound.create() .path(fixtureDir + '/justFiles') .includeFileStats() .find(); return query .then((files) => { const expected = files.map(includeFileStats); assert.deepEqual(files, expected); }); }); it('returns file stats for `findSync`', () => { const files = FileHound.create() .path(fixtureDir + '/justFiles') .includeFileStats() .findSync(); const expected = files.map(includeFileStats); assert.deepEqual(files, expected); }); }); describe('.paths', () => { it('returns all files in a given directory', () => { const query = FileHound.create() .paths(fixtureDir + '/justFiles') .find(); return query .then((files) => { assert.deepEqual(files, justFiles); }); }); it('returns files performing a recursive search', () => { const query = FileHound.create() .paths(fixtureDir + '/nested') .find(); return query .then((files) => { assert.deepEqual(files, nestedFiles); }); }); it('returns matching files from multiple search paths', () => { const location1 = fixtureDir + '/nested'; const location2 = fixtureDir + '/justFiles'; const query = FileHound.create() .paths(location1, location2) .find(); return query.then((files) => { const expected = nestedFiles.concat(justFiles).sort(); assert.deepEqual(files, expected); }); }); it('returns matching files given a array of paths', () => { const location1 = fixtureDir + '/nested'; const location2 = fixtureDir + '/justFiles'; const query = FileHound.create() .paths([location1, location2]) .find(); return query.then((files) => { const expected = nestedFiles.concat(justFiles).sort(); assert.deepEqual(files, expected); }); }); it('removes duplicate paths', () => { const location1 = fixtureDir + '/nested'; const fh = FileHound.create(); fh.paths(location1, location1); assert.deepEqual(fh.getSearchPaths(), [location1]); }); it('returns a defensive copy of the search directories', () => { const fh = FileHound.create(); fh.paths('a', 'b', 'c'); const directories = fh.getSearchPaths(); directories.push('d'); assert.equal(fh.getSearchPaths().length, 3); }); it('normalises paths', () => { const location1 = fixtureDir + '/nested'; const location2 = fixtureDir + '/nested/mydir'; const location3 = fixtureDir + '/justFiles/moreFiles'; const location4 = fixtureDir + '/justFiles'; const fh = FileHound.create(); fh.paths(location2, location1, location4, location3); assert.deepEqual(fh.getSearchPaths(), [location4, location1]); }); }); describe('.discard', () => { it('ignores matching sub-directories', () => { const query = FileHound.create() .paths(fixtureDir + '/nested') .discard('mydir') .find(); return query .then((files) => { assert.deepEqual(files, qualifyNames(['/nested/c.json', '/nested/d.json'])); }); }); it('ignores files', () => { const query = FileHound.create() .paths(fixtureDir + '/nested') .discard('c\.json') .find(); return query .then((files) => { assert.deepEqual(files, qualifyNames(['/nested/d.json', '/nested/mydir/e.json'])); }); }); it('ignores everything using a greedy match', () => { const query = FileHound.create() .paths(fixtureDir + '/nested') .discard('.*') .find(); return query .then((files) => { assert.deepEqual(files, []); }); }); it('matches all files after being negated', () => { const query = FileHound.create() .paths(fixtureDir + '/nested') .discard('.*') .not() .find(); return query .then((files) => { assert.deepEqual(files, nestedFiles); }); }); it('applies multiple discard filters as variable aruments', () => { const query = FileHound.create() .paths(fixtureDir + '/mixed') .discard('a\.json', 'z\.json') .find(); return query .then((files) => { assert.deepEqual(files, qualifyNames(['/mixed/aabbcc.json', '/mixed/ab.json', '/mixed/acdc.json'])); }); }); it('applies an array of discard filters', () => { const query = FileHound.create() .paths(fixtureDir + '/mixed') .discard(['a\.json', 'z\.json']) .find(); return query .then((files) => { assert.deepEqual(files, qualifyNames(['/mixed/aabbcc.json', '/mixed/ab.json', '/mixed/acdc.json'])); }); }); }); describe('callbacks', () => { it('supports callbacks', (done) => { FileHound.create() .paths(fixtureDir + '/justFiles') .find((err, files) => { assert.ifError(err); assert.deepEqual(files, justFiles); done(); }); }); it('returns an error when a given path is invalid', (done) => { FileHound.create() .paths(fixtureDir + '/justBad') .find((err) => { assert.ok(err); done(); }); }); }); describe('.findSync', () => { it('returns an array of matching files', () => { const files = FileHound.create() .paths(fixtureDir + '/justFiles') .findSync(); assert.deepEqual(files, justFiles); }); it('filters matching directories', () => { const expectedDirectories = qualifyNames([ '/deeplyNested/mydir', '/deeplyNested/mydir/mydir2', '/deeplyNested/mydir/mydir2/mydir3']); const directories = FileHound.create() .paths(fixtureDir + '/deeplyNested') .directory() .match('*dir4*') .not() .findSync(); assert.deepEqual(directories, expectedDirectories); }); it('filters matching files', () => { const files = FileHound.create() .paths(fixtureDir + '/justFiles') .ext('txt') .findSync(); assert.deepEqual(files, textFiles); }); }); describe('.ext', () => { it('returns files for a given ext', () => { const query = FileHound.create() .ext('txt') .paths(fixtureDir + '/justFiles') .find(); return query .then((files) => { assert.deepEqual(files, textFiles); }); }); it('returns files for a given ext including a period', () => { const query = FileHound.create() .ext('.txt') .paths(fixtureDir + '/justFiles') .find(); return query .then((files) => { assert.deepEqual(files, textFiles); }); }); it('returns files for all matching extensions', () => { const query = FileHound.create() .ext(['txt', '.json']) .paths(fixtureDir + '/ext') .find(); return query .then((files) => { assert.deepEqual(files, mixedExtensions); }); }); it('supports var args', () => { const query = FileHound.create() .ext('.txt', 'json') .paths(fixtureDir + '/ext') .find(); return query .then((files) => { assert.deepEqual(files, mixedExtensions); }); }); }); describe('.match', () => { it('returns files for given match name', () => { const query = FileHound.create() .match('*ab*.json') .paths(fixtureDir + '/mixed') .find(); return query .then((files) => { assert.deepEqual(files.sort(), matchFiles); }); }); it('returns files for given match names', () => { const query = FileHound.create() .match(['*b.json', '*bcc.json']) .paths(fixtureDir + '/mixed') .find(); return query .then((files) => { assert.deepEqual(files.sort(), matchFiles); }); }); describe('.glob', () => { it('returns files using glob method with a single glob', () => { const query = FileHound.create() .glob('*ab*.json') .paths(fixtureDir + '/mixed') .find(); return query .then((files) => { assert.deepEqual(files.sort(), matchFiles); }); }); it('returns files using glob method with multiple globs', () => { const matchingFiles = qualifyNames(['/mixed/aabbcc.json', '/mixed/ab.json', '/mixed/acdc.json']); const query = FileHound.create() .glob(['*ab*.json', '*cd*.json']) .paths(fixtureDir + '/mixed') .find(); return query .then((files) => { assert.deepEqual(files.sort(), matchingFiles); }); }); it('returns files using glob method with variable args', () => { const matchingFiles = qualifyNames(['/mixed/aabbcc.json', '/mixed/ab.json', '/mixed/acdc.json', '/mixed/z.json']); const query = FileHound.create() .glob('*ab*.json', '*cd*.json', '*z*') .paths(fixtureDir + '/mixed') .find(); return query .then((files) => { assert.deepEqual(files.sort(), matchingFiles); }); }); }); it('performs recursive search using matching on a given pattern', () => { const query = FileHound.create() .match('*.json') .paths(fixtureDir + '/nested') .find(); return query .then((files) => { assert.deepEqual(files.sort(), nestedFiles); }); }); }); describe('.not', () => { it('returns files not matching the given query', () => { const notJsonStartingWithZ = FileHound.create() .match('*.json') .paths(fixtureDir + '/justFiles') .not() .find(); return notJsonStartingWithZ .then((files) => { assert.deepEqual(files, qualifyNames(['/justFiles/dummy.txt'])); }); }); }); describe('.any', () => { it('returns matching files for any query', () => { const jsonStartingWithZ = FileHound.create() .match('*.json') .paths(fixtureDir + '/justFiles') .find(); const onlyTextFles = FileHound.create() .ext('txt') .paths(fixtureDir + '/justFiles') .find(); const results = FileHound.any(jsonStartingWithZ, onlyTextFles); return results .then((files) => { assert.deepEqual(files, justFiles); }); }); }); describe('.size', () => { it('returns files matched using the equality operator by default', () => { const sizeFile10Bytes = FileHound.create() .size(20) .paths(fixtureDir + '/justFiles') .find(); return sizeFile10Bytes .then((files) => { assert.deepEqual(files, qualifyNames(['/justFiles/b.json'])); }); }); it('returns files that equal a given number of bytes', () => { const sizeFile10Bytes = FileHound.create() .size('==20') .paths(fixtureDir + '/justFiles') .find(); return sizeFile10Bytes .then((files) => { assert.deepEqual(files, qualifyNames(['/justFiles/b.json'])); }); }); it('returns files greater than a given size', () => { const sizeGreaterThan1k = FileHound.create() .size('>1024') .paths(fixtureDir + '/sizes') .find(); return sizeGreaterThan1k .then((files) => { assert.deepEqual(files, qualifyNames(['/sizes/2k.txt'])); }); }); it('returns files less than a given size', () => { const sizeLessThan1k = FileHound.create() .size('<1024') .paths(fixtureDir + '/sizes') .find(); return sizeLessThan1k .then((files) => { assert.deepEqual(files, qualifyNames(['/sizes/10b.txt', '/sizes/1b.txt'])); }); }); it('returns files using file size units', () => { const sizeLessThan15bytes = FileHound.create() .size('<15b') .paths(fixtureDir + '/sizes') .find(); return sizeLessThan15bytes .then((files) => { assert.deepEqual(files, qualifyNames(['/sizes/10b.txt', '/sizes/1b.txt'])); }); }); it('returns files less than or equal to a given size', () => { const lessThanOrEqualTo1k = FileHound.create() .size('<=1024') .paths(fixtureDir + '/sizes') .find(); return lessThanOrEqualTo1k .then((files) => { assert.deepEqual(files, qualifyNames( ['/sizes/10b.txt', '/sizes/1b.txt', '/sizes/1k.txt'])); }); }); it('returns files greater than or equal to a given size', () => { const greaterThanOrEqualTo1k = FileHound.create() .size('>=1024') .paths(fixtureDir + '/sizes') .find(); return greaterThanOrEqualTo1k .then((files) => { assert.deepEqual(files, qualifyNames( ['/sizes/1k.txt', '/sizes/2k.txt'])); }); }); it('returns files within a given size range', () => { const range = FileHound.create() .size('>0') .size('<=1024') .paths(fixtureDir + '/sizes') .find(); return range .then((files) => { assert.deepEqual(files, qualifyNames( ['/sizes/10b.txt', '/sizes/1b.txt', '/sizes/1k.txt'])); }); }); }); describe('.isEmpty()', () => { it('returns zero length files', () => { const allEmpty = FileHound.create() .isEmpty(20) .paths(fixtureDir + '/justFiles') .find(); return allEmpty .then((files) => { assert.deepEqual(files, qualifyNames(['/justFiles/a.json', '/justFiles/dummy.txt'])); }); }); }); describe('.ignoreHiddenFiles()', () => { it('ignores hidden files', () => { const noHiddenFiles = FileHound.create() .ignoreHiddenFiles() .paths(fixtureDir + '/visibility') .find(); noHiddenFiles.then((files) => { assert.equal(files.length, 2); assert.deepEqual(files, qualifyNames(['/visibility/.hidden/visible.json', '/visibility/visible.json'])); }); }); it('ignores files within hidden directories', () => { const noHiddenFiles = FileHound.create() .ignoreHiddenDirectories() .ignoreHiddenFiles() .paths(fixtureDir + '/visibility') .find(); noHiddenFiles.then((files) => { assert.equal(files.length, 1); assert.deepEqual(files, qualifyNames(['/visibility/visible.json'])); }); }); }); describe('.addFilter', () => { it('returns files based on a custom filter', () => { const customFilter = FileHound.create() .addFilter((file) => { return file.sizeSync() === 1024; }) .paths(fixtureDir + '/custom') .find(); return customFilter .then((files) => { assert.deepEqual(files, qualifyNames(['/custom/passed.txt'])); }); }); }); describe('.modified', () => { before(() => { fs.mkdirSync(getAbsolutePath('dates')); }); after(() => { fs.rmdirSync(getAbsolutePath('dates')); }); const files = [ { name: getAbsolutePath('dates/a.txt'), modified: 10 }, { name: getAbsolutePath('dates/w.txt'), modified: 9 }, { name: getAbsolutePath('dates/x.txt'), modified: 2 }, { name: getAbsolutePath('dates/y.txt'), modified: 1 }, { name: getAbsolutePath('dates/z.txt'), modified: 0 } ]; beforeEach(() => { files.forEach((file) => { createFile(file.name, { duration: file.modified, modifier: 'days' }); }); }); afterEach(() => { files.forEach((file) => { deleteFile(file.name); }); }); it('returns files modified exactly n days', () => { const modifiedNDaysAgo = FileHound.create() .paths(fixtureDir + '/dates') .modified(10) .find(); return modifiedNDaysAgo .then((files) => { assert.deepEqual(files, qualifyNames(['/dates/a.txt'])); }); }); it('returns files greater than n days', () => { const modifiedNDaysAgo = FileHound.create() .paths(fixtureDir + '/dates') .modified('>2 days') .find(); return modifiedNDaysAgo .then((files) => { assert.deepEqual(files, qualifyNames([ '/dates/a.txt', '/dates/w.txt' ])); }); }); it('returns files less than n days', () => { const modifiedNDaysAgo = FileHound.create() .paths(fixtureDir + '/dates') .modified('<10 days') .find(); return modifiedNDaysAgo .then((files) => { assert.deepEqual(files, qualifyNames([ '/dates/w.txt', '/dates/x.txt', '/dates/y.txt', '/dates/z.txt' ])); }); }); }); describe('.accessed', () => { before(() => { fs.mkdirSync(getAbsolutePath('dates')); }); after(() => { fs.rmdirSync(getAbsolutePath('dates')); }); const files = [ { name: getAbsolutePath('dates/a.txt'), accessed: 10 }, { name: getAbsolutePath('dates/w.txt'), accessed: 9 }, { name: getAbsolutePath('dates/x.txt'), accessed: 2 }, { name: getAbsolutePath('dates/y.txt'), accessed: 1 }, { name: getAbsolutePath('dates/z.txt'), accessed: 0 } ]; beforeEach(() => { files.forEach((file) => { createFile(file.name, { duration: file.accessed, modifier: 'hours' }); }); }); afterEach(() => { files.forEach((file) => { deleteFile(file.name); }); }); it('returns files accessed > 8 hours ago', () => { const accessedFiles = FileHound.create() .paths(fixtureDir + '/dates') .accessed('>8h') .find(); return accessedFiles .then((files) => { assert.deepEqual(files, qualifyNames(['/dates/a.txt', '/dates/w.txt'])); }); }); it('returns files accessed < 3 hours ago', () => { const accessedFiles = FileHound.create() .paths(fixtureDir + '/dates') .accessed('<3h') .find(); return accessedFiles .then((files) => { assert.deepEqual(files, qualifyNames(['/dates/x.txt', '/dates/y.txt', '/dates/z.txt'])); }); }); it('returns files accessed 1 hour ago', () => { const accessedFiles = FileHound.create() .paths(fixtureDir + '/dates') .accessed('=1h') .find(); return accessedFiles .then((files) => { assert.deepEqual(files, qualifyNames(['/dates/y.txt'])); }); }); }); describe('.changed', () => { const sandbox = sinon.createSandbox(); let statSync; before(() => { fs.mkdirSync(getAbsolutePath('dates')); statSync = sandbox.stub(fs, 'statSync'); statSync.returns({ isDirectory: function () { return true; } }); }); after(() => { fs.rmdirSync(getAbsolutePath('dates')); sandbox.restore(); }); const files = [ { name: getAbsolutePath('dates/a.txt'), changed: 10 }, { name: getAbsolutePath('dates/w.txt'), changed: 9 }, { name: getAbsolutePath('dates/x.txt'), changed: 2 }, { name: getAbsolutePath('dates/y.txt'), changed: 1 }, { name: getAbsolutePath('dates/z.txt'), changed: 0 } ]; beforeEach(() => { files.forEach((file) => { createFile(file.name, { duration: file.changed, modifier: 'hours' }); statSync.withArgs(file.name).returns({ ctime: moment().subtract(file.changed, 'hours'), isDirectory: function () { return false; } }); }); }); afterEach(() => { files.forEach((file) => { deleteFile(file.name); }); }); it('returns files changed > 8 hours ago', () => { const changedFiles = FileHound.create() .paths(fixtureDir + '/dates') .changed('>8h') .find(); return changedFiles .then((files) => { assert.deepEqual(files, qualifyNames(['/dates/a.txt', '/dates/w.txt'])); }); }); it('returns files changed < 3 hours ago', () => { const changedFiles = FileHound.create() .paths(fixtureDir + '/dates') .changed('<3h') .find(); return changedFiles .then((files) => { assert.deepEqual(files, qualifyNames(['/dates/x.txt', '/dates/y.txt', '/dates/z.txt'])); }); }); it('returns files changed 1 hour ago', () => { const changedFiles = FileHound.create() .paths(fixtureDir + '/dates') .changed('=1h') .find(); return changedFiles .then((files) => { assert.deepEqual(files, qualifyNames(['/dates/y.txt'])); }); }); }); it('emits a match event for each file matched', () => { const fh = FileHound.create(); fh.path(fixtureDir + '/justFiles'); const spy = sinon.spy(); fh.on('match', spy); const query = fh.find(); return query .then(() => { sinon.assert.callCount(spy, 3); sinon.assert.calledWithMatch(spy, 'dummy.txt'); sinon.assert.calledWithMatch(spy, 'a.json'); sinon.assert.calledWithMatch(spy, 'b.json'); }); }); it('emits an end event when the search is complete', () => { const fh = FileHound.create(); fh.path(fixtureDir + '/justFiles'); const spy = sinon.spy(); fh.on('end', spy); const query = fh.find(); return query .then(() => { sinon.assert.callCount(spy, 1); }); }); it('emits an error event', () => { const fh = FileHound.create(); fh.path(fixtureDir + '/justBad'); const spy = sinon.spy(); fh.on('error', spy); const query = fh.find(); return query .catch((e) => { assert.ok(e); sinon.assert.callCount(spy, 1); }); }); it('ignores errors thrown for invalid symlinks', () => { const target = getAbsolutePath('broken.txt'); const path = getAbsolutePath('brokenLink'); fs.symlinkSync(target, path); const pending = FileHound.create() .paths(fixtureDir) .ext('java') .find(); return pending .then((files) => { assert.equal(files.length, 1); }) .catch((err) => { assert.fail(null, null, `Unexpected exception raised: ${err}`); }) .finally(() => { fs.unlinkSync(path); }); }); });