diff --git a/.gitignore b/.gitignore index e69de29..54ea7ff 100644 --- a/.gitignore +++ b/.gitignore @@ -0,0 +1 @@ +/caching-transform-1.0.1.tgz diff --git a/nodejs-caching-transform.spec b/nodejs-caching-transform.spec new file mode 100644 index 0000000..105be53 --- /dev/null +++ b/nodejs-caching-transform.spec @@ -0,0 +1,74 @@ +%{?nodejs_find_provides_and_requires} + +%global packagename caching-transform +%global enable_tests 0 +# Tests disabled due to missing 'ava' in Fedora + +Name: nodejs-caching-transform +Version: 1.0.1 +Release: 1%{?dist} +Summary: Wraps a transform and provides caching + +License: MIT +URL: https://github.com/jamestalmage/caching-transform.git +Source0: https://registry.npmjs.org/%{packagename}/-/%{packagename}-%{version}.tgz +# The test files are not included in the npm tarball. +Source1: https://raw.githubusercontent.com/jamestalmage/caching-transform/v%{version}/test.js + + +BuildArch: noarch +%if 0%{?fedora} >= 19 +ExclusiveArch: %{nodejs_arches} noarch +%else +ExclusiveArch: %{ix86} x86_64 %{arm} noarch +%endif + +BuildRequires: nodejs-packaging +BuildRequires: npm(md5-hex) +BuildRequires: npm(mkdirp) +BuildRequires: npm(write-file-atomic) +%if 0%{?enable_tests} +BuildRequires: ava +%endif + +%description +Wraps a transform and provides caching + + +%prep +%setup -q -n package +# setup the tests +cp -p %{SOURCE1} . + + + +%build +# nothing to do! + +%install +mkdir -p %{buildroot}%{nodejs_sitelib}/%{packagename} +cp -pr package.json index.js \ + %{buildroot}%{nodejs_sitelib}/%{packagename} + +%nodejs_symlink_deps + +%check +%nodejs_symlink_deps --check +%{__nodejs} -e 'require("./")' +%if 0%{?enable_tests} +%{_bindir}/ava +%else +%{_bindir}/echo -e "\e[101m -=#=- Tests disabled -=#=- \e[0m" +%endif + + +%files +%{!?_licensedir:%global license %doc} +%doc *.md +%license license +%{nodejs_sitelib}/%{packagename} + + +%changelog +* Tue Feb 9 2016 Jared Smith - 1.0.1-1 +- Initial packaging diff --git a/sources b/sources index e69de29..ac9ea09 100644 --- a/sources +++ b/sources @@ -0,0 +1 @@ +fea1314a38ae921960df22b74f7ceaa1 caching-transform-1.0.1.tgz diff --git a/test.js b/test.js new file mode 100644 index 0000000..e766e4a --- /dev/null +++ b/test.js @@ -0,0 +1,346 @@ +import test from 'ava'; +import proxyquire from 'proxyquire'; +import mockfs from 'mock-fs'; +import md5Hex from 'md5-hex'; +import path from 'path'; +import sinon from 'sinon'; + +function withMockedFs(fsConfig) { + const fs = mockfs.fs(fsConfig || {}); + fs['@global'] = true; + const mkdirp = sinon.spy(proxyquire('mkdirp', {fs})); + mkdirp.sync = sinon.spy(mkdirp.sync); + var cachingTransform = proxyquire('./', {fs, mkdirp}); + cachingTransform.fs = fs; + cachingTransform.mkdirp = mkdirp; + + return cachingTransform; +} + +function wrap(opts, fsConfig) { + if (typeof opts === 'function') { + opts = { + transform: opts, + cacheDir: '/cacheDir' + }; + } + var cachingTransform = withMockedFs(fsConfig); + var wrapped = cachingTransform(opts); + wrapped.fs = cachingTransform.fs; + wrapped.mkdirp = cachingTransform.mkdirp; + return wrapped; +} + +function append(val) { + return input => input + ' ' + val; +} + +test('saves transform result to cache directory', t => { + const transform = wrap(append('bar')); + + t.is(transform('foo'), 'foo bar'); + t.is(transform('FOO'), 'FOO bar'); + + const filename1 = path.join('/cacheDir', 'acbd18db4cc2f85cedef654fccc4a4d8'); + const filename2 = path.join('/cacheDir', '901890a8e9c8cf6d5a1a542b229febff'); + + t.is(transform.fs.readFileSync(filename1, 'utf8'), 'foo bar'); + t.is(transform.fs.readFileSync(filename2, 'utf8'), 'FOO bar'); +}); + +test('skips transform if cache file exists', t => { + const transform = wrap( + () => t.fail(), + { + '/cacheDir/acbd18db4cc2f85cedef654fccc4a4d8': 'foo bar' + } + ); + + t.is(transform('foo'), 'foo bar'); +}); + +test('able to specify alternate cacheDir', t => { + const transform = wrap({ + transform: append('bar'), + cacheDir: '/alternateDir' + }); + + t.is(transform('foo'), 'foo bar'); + + const filename = path.join('/alternateDir', 'acbd18db4cc2f85cedef654fccc4a4d8'); + + t.is(transform.fs.readFileSync(filename, 'utf8'), 'foo bar'); +}); + +test('able to specify alternate extension', t => { + const transform = wrap({ + transform: append('bar'), + ext: '.js', + cacheDir: '/cacheDir' + }); + + t.is(transform('foo'), 'foo bar'); + + const filename = path.join('/cacheDir', 'acbd18db4cc2f85cedef654fccc4a4d8.js'); + + t.is(transform.fs.readFileSync(filename, 'utf8'), 'foo bar'); +}); + +test('mkdirp is only called once', t => { + const transform = wrap( + { + transform: append('bar'), + cacheDir: '/someDir' + } + ); + + t.is(transform.mkdirp.sync.callCount, 0); + t.is(transform('foo'), 'foo bar'); + t.is(transform.mkdirp.sync.callCount, 1); + t.is(transform('bar'), 'bar bar'); + t.is(transform.mkdirp.sync.callCount, 1); +}); + +test('mkdirp is only called once, with factory', t => { + const transform = wrap( + { + factory: () => append('bar'), + cacheDir: '/someDir' + } + ); + + t.is(transform.mkdirp.sync.callCount, 0); + t.is(transform('foo'), 'foo bar'); + t.is(transform.mkdirp.sync.callCount, 1); + t.is(transform('bar'), 'bar bar'); + t.is(transform.mkdirp.sync.callCount, 1); +}); + +test('mkdirp is never called if `createCacheDir === false`', t => { + const transform = wrap( + { + transform: append('bar'), + createCacheDir: false, + cacheDir: '/someDir' + }, + { + '/someDir': {} + } + ); + + t.is(transform.mkdirp.sync.callCount, 0); + t.is(transform('foo'), 'foo bar'); + t.is(transform.mkdirp.sync.callCount, 0); +}); + +test('mkdirp is never called if `createCacheDir === false`, with factory', t => { + const transform = wrap( + { + factory: () => append('bar'), + createCacheDir: false, + cacheDir: '/someDir' + }, + { + '/someDir': {} + } + ); + + t.is(transform.mkdirp.sync.callCount, 0); + t.is(transform('foo'), 'foo bar'); + t.is(transform.mkdirp.sync.callCount, 0); +}); + +test('additional opts are passed to transform', t => { + const transform = wrap((input, additionalOpts) => { + t.is(input, 'foo'); + t.same(additionalOpts, {bar: 'baz'}); + return 'FOO!' + }); + + t.is(transform('foo', {bar: 'baz'}), 'FOO!'); +}); + +test('filename is generated from the md5 hash of the input content and the salt', t => { + const transform = wrap ( + { + transform: append('bar'), + salt: 'baz', + cacheDir: '/someDir' + } + ); + + transform('FOO'); + + const filename = path.join('/someDir', md5Hex(['FOO', 'baz'])); + + t.is(transform.fs.readFileSync(filename, 'utf8'), 'FOO bar'); +}); + +test('factory is only called once', t => { + const factory = sinon.spy(() => append('foo')); + + const transform = wrap( + { + factory, + cacheDir: '/cacheDir' + } + ); + + t.is(factory.callCount, 0); + t.is(transform('bar'), 'bar foo'); + t.is(factory.callCount, 1); + t.same(factory.firstCall.args, ['/cacheDir']); + t.is(transform('baz'), 'baz foo'); + t.is(factory.callCount, 1); +}); + +test('checks for sensible options', t => { + const transform = append('bar'); + const factory = () => transform; + const cacheDir = '/someDir'; + t.throws(() => wrap({factory, transform, cacheDir})); + t.throws(() => wrap({cacheDir})); + t.throws(() => wrap({factory})); + t.throws(() => wrap({transform})); + + t.doesNotThrow(() => { + wrap({factory, cacheDir}); + wrap({transform, cacheDir}); + }); +}); + +test('cacheDir is only required if caching is enabled', t => { + t.doesNotThrow(() => { + wrap({transform: append('bar'), disableCache: true}); + }); + t.throws(() => { + wrap({transform: append('bar')}); + }); +}); + +test('shouldTransform can bypass transform', t => { + const transform = wrap({ + shouldTransform: (code, file) => { + t.is(code, 'baz'); + t.is(file, '/baz.js'); + return false; + }, + transform: () => t.fail(), + cacheDir: '/someDir' + }); + + t.is(transform('baz', '/baz.js'), 'baz'); +}); + +test('shouldTransform can enable transform', t => { + const transform = wrap({ + shouldTransform: (code, file) => { + t.is(code, 'foo'); + t.is(file, '/foo.js'); + return true; + }, + transform: append('bar'), + cacheDir: '/someDir' + }); + + t.is(transform('foo', '/foo.js'), 'foo bar'); +}); + +test('disableCache:true, disables cache - transform is called multiple times', t => { + const transformSpy = sinon.spy(append('bar')); + const transform = wrap({ + disableCache: true, + transform: transformSpy, + cacheDir: '/someDir' + }); + + t.is(transformSpy.callCount, 0); + t.is(transform('foo'), 'foo bar'); + t.is(transformSpy.callCount, 1); + t.is(transform('foo'), 'foo bar'); + t.is(transformSpy.callCount, 2); +}); + +test('disableCache:default, enables cache - transform is called once per hashed input', t => { + const transformSpy = sinon.spy(append('bar')); + const transform = wrap({ + transform: transformSpy, + cacheDir: '/someDir' + }); + + t.is(transformSpy.callCount, 0); + t.is(transform('foo'), 'foo bar'); + t.is(transformSpy.callCount, 1); + t.is(transform('foo'), 'foo bar'); + t.is(transformSpy.callCount, 1); +}); + +test('can provide custom hash function', t => { + t.plan(5); + + const hash = sinon.spy(function (code, filename, salt) { + t.is(code, 'foo'); + t.is(filename, '/foo.js'); + t.is(salt, 'this is salt'); + return 'foo-hash'; + }); + + const transform = wrap({ + salt: 'this is salt', + cacheDir: '/cacheDir', + transform: append('bar'), + hash + }); + + t.is(transform('foo', '/foo.js'), 'foo bar'); + t.is(transform.fs.readFileSync('/cacheDir/foo-hash', 'utf8'), 'foo bar'); +}); + +test('custom encoding changes value loaded from disk', t => { + const transform = wrap({ + transform: () => t.fail(), + encoding: 'hex', + cacheDir: '/cacheDir' + }, { + ['/cacheDir/' + md5Hex('foo')]: 'foo bar' + }); + + t.is(transform('foo'), new Buffer('foo bar').toString('hex')); +}); + +test('custom encoding changes the value stored to disk', t => { + const transform = wrap({ + transform: code => new Buffer(code + ' bar').toString('hex'), + encoding: 'hex', + cacheDir: '/cacheDir' + }); + + t.is(transform('foo'), new Buffer('foo bar').toString('hex')); + t.is(transform.fs.readFileSync('/cacheDir/' + md5Hex('foo'), 'utf8'), 'foo bar'); +}); + +test('buffer encoding returns a buffer', t => { + const transform = wrap({ + transform: () => t.fail(), + encoding: 'buffer', + cacheDir: '/cacheDir' + }, { + ['/cacheDir/' + md5Hex('foo')]: 'foo bar' + }); + + var result = transform('foo'); + t.true(Buffer.isBuffer(result)); + t.is(result.toString(), 'foo bar'); +}); + +test('salt can be a buffer', t => { + const transform = wrap({ + transform: () => t.fail(), + salt: new Buffer('some-salt'), + cacheDir: '/cacheDir' + }, { + ['/cacheDir/' + md5Hex(['foo', new Buffer('some-salt', 'utf8')])]: 'foo bar' + }); + + t.is(transform('foo'), 'foo bar'); +});