diff --git a/lib/resolve/resolvers/GitResolver.js b/lib/resolve/resolvers/GitResolver.js index a440651f..9f90ae6e 100644 --- a/lib/resolve/resolvers/GitResolver.js +++ b/lib/resolve/resolvers/GitResolver.js @@ -36,14 +36,12 @@ GitResolver.prototype.hasNew = function (canonicalPkg) { return true; } - // If resolved to a tag, there is new content - // if the tags are not equal - if (resolution.type === 'tag') { - return semver.neq(resolution.tag, oldResolution.tag); + // If resolved to a tag, there is new content if the tags are not equal + if (resolution.type === 'tag' && semver.neq(resolution.tag, oldResolution.tag)) { + return true; } - // If resolved to a commit hash, just check if they are different - // Use the same strategy if it the resolution is to a branch + // As last check, we compare both commit hashes return resolution.commit !== oldResolution.commit; }); }; @@ -51,8 +49,12 @@ GitResolver.prototype.hasNew = function (canonicalPkg) { // ----------------- // Abstract functions that should be implemented by concrete git resolvers -GitResolver.prototype._checkout = function () {}; -GitResolver.fetchRefs = function (source) {}; +GitResolver.prototype._checkout = function () { + throw new Error('_checkout not implemented'); +}; +GitResolver.fetchRefs = function (source) { + throw new Error('fetchRefs not implemented'); +}; // ----------------- @@ -74,18 +76,18 @@ GitResolver.prototype._findResolution = function (target) { // Find the highest one that satisfies the target var version = mout.array.find(versions, function (version) { - return semver.satisfies(version, target); + return semver.satisfies(version.version, target); }, this); if (!version) { throw createError('No tag found that was able to satisfy "' + target + '"', 'ENORESTARGET', { details: !versions.length ? - 'No tags found in "' + this._source + '"' : - 'Available tags in "' + this._source + '" are: ' + versions.join(', ') + 'No versions found in "' + this._source + '"' : + 'Available versions in "' + this._source + '" are: ' + versions.map(function (version) { return version.version; }).join(', ') }); } - return this._resolution = { type: 'tag', tag: version }; + return this._resolution = { type: 'tag', tag: version.tag, commit: version.commit }; }.bind(this)); } @@ -100,7 +102,7 @@ GitResolver.prototype._findResolution = function (target) { return self.fetchHeads(this._source) .then(function (heads) { // Use hasOwn because a branch could have a name like "hasOwnProperty" - if (mout.object.hasOwn(heads, target)) { + if (!mout.object.hasOwn(heads, target)) { branches = Object.keys(heads); throw createError('Branch "' + target + '" does not exist', 'ENORESTARGET', { details: !branches.length ? @@ -123,6 +125,12 @@ GitResolver.prototype._cleanup = function () { return Q.nfcall(chmodr, gitFolder, 0777) .then(function () { return Q.nfcall(rimraf, gitFolder); + }, function (err) { + // If .git does not exist, chmodr returns ENOENT + // so, we ignore that error code + if (err.code !== 'ENOENT') { + throw err; + } }); } else { return Q.nfcall(rimraf, gitFolder); @@ -149,21 +157,23 @@ GitResolver.fetchVersions = function (source) { // Foreach line in the refs, match only the tags refs.forEach(function (line) { - var match = line.match(/^[a-f0-9]{40}\s+refs\/tags\/(\S+)/), - cleaned; + var match = line.match(/^([a-f0-9]{40})\s+refs\/tags\/(\S+)/), + tag, + version; // Ensure it's valid if (match) { - cleaned = semver.clean(match[1]); - if (cleaned) { - versions.push(cleaned); + tag = match[2]; + version = semver.clean(tag); + if (version) { + versions.push({ version: version, tag: tag, commit: match[1] }); } } }); // Sort them by desc order versions = versions.sort(function (a, b) { - return semver.gt(a, b) ? -1 : 1; + return semver.gt(a.version, b.version) ? -1 : 1; }); this._versions = this._versions || {}; diff --git a/test/resolve/resolver.js b/test/resolve/resolver.js index 6f59b43e..2b007272 100644 --- a/test/resolve/resolver.js +++ b/test/resolve/resolver.js @@ -9,6 +9,8 @@ var cmd = require('../../lib/util/cmd'); var Resolver = require('../../lib/resolve/Resolver'); describe('Resolver', function () { + var tempDir = path.resolve(__dirname, '../assets/tmp'); + describe('.getSource', function () { it('should return the resolver source', function () { var resolver = new Resolver('foo'); @@ -46,15 +48,6 @@ describe('Resolver', function () { }); describe('.hasNew', function () { - it('should return a promise', function (done) { - var resolver = new Resolver('foo'), - promise = resolver.hasNew('.'); - - expect(promise).to.be.an('object'); - expect(promise.then).to.be.an('function'); - promise.then(done.bind(done, null), done.bind(done, null)); - }); - it('should resolve to true by default', function (next) { var resolver = new Resolver('foo'); @@ -68,26 +61,18 @@ describe('Resolver', function () { }); describe('.resolve', function () { - it('should return a promise', function (done) { - var resolver = new Resolver('foo'), - promise = resolver.resolve(); - - expect(promise).to.be.an('object'); - expect(promise.then).to.be.an('function'); - promise.then(done.bind(done, null), done.bind(done, null)); - }); - - it('should reject the promise if _resolveSelf is not implemented', function (done) { + it('should reject the promise if _resolveSelf is not implemented', function (next) { var resolver = new Resolver('foo'); resolver.resolve() .then(function () { - done(new Error('Should have rejected the promise')); + next(new Error('Should have rejected the promise')); }, function (err) { expect(err).to.be.an(Error); - expect(err.message).to.contain('not implemented'); - done(); - }); + expect(err.message).to.contain('_resolveSelf not implemented'); + next(); + }) + .done(); }); it('should call all the functions necessary to resolve by the correct order', function (next) { @@ -253,9 +238,8 @@ describe('Resolver', function () { }); }); - describe('_createTempDir', function () { - var tempDir = path.normalize(__dirname + '/../assets/tmp'), - dirMode0777; + describe('._createTempDir', function () { + var dirMode0777; before(function () { var stat; @@ -269,15 +253,6 @@ describe('Resolver', function () { rimraf(tempDir, next); }); - it('should return a promise', function (done) { - var resolver = new Resolver('foo'), - promise = resolver._createTempDir(); - - expect(promise).to.be.an('object'); - expect(promise.then).to.be.an('function'); - promise.then(done.bind(done, null), done.bind(done, null)); - }); - it('should create a directory inside a bower folder, located within the OS temp folder', function (next) { var resolver = new Resolver('foo'); @@ -319,7 +294,7 @@ describe('Resolver', function () { rimraf(bowerOsTempDir, function (err) { if (err) return next(err); - cmd('node', ['test/assets/test-temp-dir/test.js'], { cwd: __dirname + '/../../' }) + cmd('node', ['test/assets/test-temp-dir/test.js'], { cwd: path.resolve(__dirname, '../..') }) .then(function () { expect(fs.existsSync(bowerOsTempDir)).to.be(true); expect(fs.readdirSync(bowerOsTempDir)).to.eql([]); @@ -337,7 +312,7 @@ describe('Resolver', function () { rimraf(bowerOsTempDir, function (err) { if (err) return next(err); - cmd('node', ['test/assets/test-temp-dir/test-exception.js'], { cwd: __dirname + '/../../' }) + cmd('node', ['test/assets/test-temp-dir/test-exception.js'], { cwd: path.resolve(__dirname, '../..') }) .then(function () { next(new Error('The command should have failed')); }, function () { @@ -362,9 +337,7 @@ describe('Resolver', function () { }); }); - describe('_readJson', function () { - var tempDir = path.normalize(__dirname + '/../assets/tmp'); - + describe('._readJson', function () { beforeEach(function (next) { mkdirp(tempDir, next); }); @@ -373,15 +346,6 @@ describe('Resolver', function () { rimraf(tempDir, next); }); - it('should return a promise', function (done) { - var resolver = new Resolver('foo'), - promise = resolver._readJson(); - - expect(promise).to.be.an('object'); - expect(promise.then).to.be.an('function'); - promise.then(done.bind(done, null), done.bind(done, null)); - }); - it('should read the bower.json file', function (next) { var resolver = new Resolver('foo'); @@ -435,20 +399,7 @@ describe('Resolver', function () { it.skip('should apply normalisation, defaults and validation to the json object'); }); - describe('_applyPkgMeta', function () { - var tempDir = path.normalize(__dirname + '/../assets/tmp'); - - it('should return a promise', function (done) { - var resolver = new Resolver('foo'), - promise = resolver._applyPkgMeta({ name: 'foo' }); - - resolver._tempDir = tempDir; - - expect(promise).to.be.an('object'); - expect(promise.then).to.be.an('function'); - promise.then(done.bind(done, null), done.bind(done, null)); - }); - + describe('._applyPkgMeta', function () { it('should resolve with the same package meta', function (next) { var resolver = new Resolver('foo'), meta = { name: 'foo' }; @@ -556,9 +507,7 @@ describe('Resolver', function () { }); }); - describe('_savePkgMeta', function () { - var tempDir = path.normalize(__dirname + '/../assets/tmp'); - + describe('._savePkgMeta', function () { beforeEach(function (next) { mkdirp(tempDir, next); }); @@ -567,18 +516,6 @@ describe('Resolver', function () { rimraf(tempDir, next); }); - it('should return a promise', function (done) { - var resolver = new Resolver('foo'), - promise; - - resolver._tempDir = tempDir; - promise = resolver._savePkgMeta({ name: 'foo' }); - - expect(promise).to.be.an('object'); - expect(promise.then).to.be.an('function'); - promise.then(done.bind(done, null), done.bind(done, null)); - }); - it('should resolve with the same package meta', function (next) { var resolver = new Resolver('foo'), meta = { name: 'foo' }; diff --git a/test/resolve/resolvers/gitFsResolver.js b/test/resolve/resolvers/gitFsResolver.js index 764918da..9bd84a24 100644 --- a/test/resolve/resolvers/gitFsResolver.js +++ b/test/resolve/resolvers/gitFsResolver.js @@ -1,19 +1,27 @@ describe('GitFsResolver', function () { - describe('.resolve', function () { - it.skip('should resolve to the latest commit if a repository has no tags'); - it.skip('should resolve to the specified range'); - it.skip('should resolve to the specified version'); - it.skip('should resolve to the specified commit'); - it.skip('should resolve to the specified branch'); - it.skip('should resolve to the specified commit'); - it.skip('should remove the .git folder'); - it.skip('should not copy the ignored files to the temp directory'); + describe('._resolveSelf', function () { + it.skip('should call all the functions necessary to resolve by the correct order'); }); - describe('.hasNew', function () { - it.skip('should detect a new version if the resolution type changed'); - it.skip('should detect a new version if the resolved version changed'); - it.skip('should detect a new version if the resolved commit changed (branch)'); - it.skip('should detect a new version if the resolved commit changed (commit)'); + describe('._copy', function () { + it.skip('should copy files from the source to the temporary directory'); + it.skip('should not copy over the files specified in the ignore list'); + }); + + describe('._checkout', function () { + it.skip('should checkout correctly if resolution is a branch'); + it.skip('should checkout correctly if resolution is a tag'); + it.skip('should checkout correctly if resolution is a commit'); + it.skip('should remove any untracked files and directories'); + }); + + describe('#fetchRefs', function () { + it('should resolve to the references of the local repository', function () { + + }); + + it('should cache the results', function () { + + }); }); }); \ No newline at end of file diff --git a/test/resolve/resolvers/gitRemoteResolver.js b/test/resolve/resolvers/gitRemoteResolver.js new file mode 100644 index 00000000..1da37617 --- /dev/null +++ b/test/resolve/resolvers/gitRemoteResolver.js @@ -0,0 +1,17 @@ +describe('GitRemoteResolver', function () { + describe('._checkout', function () { + it.skip('should checkout correctly if resolution is a branch'); + it.skip('should checkout correctly if resolution is a tag'); + it.skip('should checkout correctly if resolution is a commit'); + }); + + describe('#fetchRefs', function () { + it('should resolve to the references of the remote repository', function () { + + }); + + it('should cache the results', function () { + + }); + }); +}); \ No newline at end of file diff --git a/test/resolve/resolvers/gitResolver.js b/test/resolve/resolvers/gitResolver.js index c918d2cf..5ca780cd 100644 --- a/test/resolve/resolvers/gitResolver.js +++ b/test/resolve/resolvers/gitResolver.js @@ -1,47 +1,675 @@ var expect = require('expect.js'); +var util = require('util'); +var path = require('path'); +var fs = require('fs'); +var mkdirp = require('mkdirp'); +var chmodr = require('chmodr'); +var rimraf = require('rimraf'); +var ncp = require('ncp'); var Q = require('q'); +var mout = require('mout'); var GitResolver = require('../../../lib/resolve/resolvers/GitResolver'); describe('GitResolver', function () { - beforeEach(function () { - GitResolver.fetchRefs = function () {}; + var tempDir = path.resolve(__dirname, '../../assets/tmp'), + originalFetchRefs = GitResolver.fetchRefs; + + function cleanInternalResolverCache() { + GitResolver.fetchRefs = originalFetchRefs; delete GitResolver._versions; delete GitResolver._heads; delete GitResolver._refs; + } + + describe('.hasNew', function () { + beforeEach(function (next) { + cleanInternalResolverCache(); + mkdirp(tempDir, next); + }); + + afterEach(function (next) { + rimraf(tempDir, next); + }); + + it('should be true when the resolution type is different', function () { + var resolver; + + fs.writeFileSync(path.join(tempDir, 'bower.json'), JSON.stringify({ + name: 'foo', + version: '0.0.0', + _resolution: { + type: 'tag', + tag: '0.0.0', + commit: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' + } + })); + GitResolver.fetchRefs = function () { + return Q.resolve([ + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa refs/heads/master' + ]); + }; + + resolver = new GitResolver('foo'); + resolver.hasNew(tempDir) + .then(function (hasNew) { + expect(hasNew).to.be(true); + }) + .done(); + }); + + it('should be true when a higher version for a range is available', function () { + var resolver; + + fs.writeFileSync(path.join(tempDir, 'bower.json'), JSON.stringify({ + name: 'foo', + version: '1.0.0', + _resolution: { + type: 'tag', + tag: '1.0.0', + commit: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' + } + })); + GitResolver.fetchRefs = function () { + return Q.resolve([ + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa refs/heads/master', + 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb refs/tags/1.0.0', + 'cccccccccccccccccccccccccccccccccccccccc refs/tags/1.0.1' + ]); + }; + + resolver = new GitResolver('foo'); + resolver.hasNew(tempDir) + .then(function (hasNew) { + expect(hasNew).to.be(true); + }) + .done(); + }); + + it('should be true when a resolved to a lower version of a range', function () { + var resolver; + + fs.writeFileSync(path.join(tempDir, 'bower.json'), JSON.stringify({ + name: 'foo', + version: '1.0.1', + _resolution: { + type: 'tag', + tag: '1.0.1', + commit: 'cccccccccccccccccccccccccccccccccccccccc' + } + })); + GitResolver.fetchRefs = function () { + return Q.resolve([ + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa refs/heads/master', + 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb refs/tags/1.0.0' + ]); + }; + + resolver = new GitResolver('foo'); + resolver.hasNew(tempDir) + .then(function (hasNew) { + expect(hasNew).to.be(true); + }) + .done(); + }); + + it('should be false when resolved to the same tag (with same commit hash) for a given range', function () { + var resolver; + + fs.writeFileSync(path.join(tempDir, 'bower.json'), JSON.stringify({ + name: 'foo', + version: '1.0.1', + _resolution: { + type: 'tag', + tag: '1.0.1', + commit: 'cccccccccccccccccccccccccccccccccccccccc' + } + })); + GitResolver.fetchRefs = function () { + return Q.resolve([ + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa refs/heads/master', + 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb refs/tags/1.0.0', + 'cccccccccccccccccccccccccccccccccccccccc refs/tags/1.0.1' + ]); + }; + + resolver = new GitResolver('foo'); + resolver.hasNew(tempDir) + .then(function (hasNew) { + expect(hasNew).to.be(false); + }) + .done(); + }); + + it('should be true when resolved to the same tag (with different commit hash) for a given range', function () { + var resolver; + + fs.writeFileSync(path.join(tempDir, 'bower.json'), JSON.stringify({ + name: 'foo', + version: '1.0.1', + _resolution: { + type: 'tag', + tag: '1.0.1', + commit: 'cccccccccccccccccccccccccccccccccccccccc' + } + })); + GitResolver.fetchRefs = function () { + return Q.resolve([ + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa refs/heads/master', + 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb refs/tags/1.0.0', + 'dddddddddddddddddddddddddddddddddddddddd refs/tags/1.0.1' + ]); + }; + + resolver = new GitResolver('foo'); + resolver.hasNew(tempDir) + .then(function (hasNew) { + expect(hasNew).to.be(true); + }) + .done(); + }); + + it('should be true when a different commit hash for a given branch is available', function () { + var resolver; + + fs.writeFileSync(path.join(tempDir, 'bower.json'), JSON.stringify({ + name: 'foo', + _resolution: { + type: 'branch', + branch: 'master', + commit: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + } + })); + GitResolver.fetchRefs = function () { + return Q.resolve([ + 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb refs/heads/master' + ]); + }; + + resolver = new GitResolver('foo'); + resolver.hasNew(tempDir) + .then(function (hasNew) { + expect(hasNew).to.be(true); + }) + .done(); + }); + + it('should be false when resolved to the the same commit hash for a given branch', function () { + var resolver; + + fs.writeFileSync(path.join(tempDir, 'bower.json'), JSON.stringify({ + name: 'foo', + _resolution: { + type: 'branch', + branch: 'master', + commit: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + } + })); + GitResolver.fetchRefs = function () { + return Q.resolve([ + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa refs/heads/master' + ]); + }; + + resolver = new GitResolver('foo'); + resolver.hasNew(tempDir) + .then(function (hasNew) { + expect(hasNew).to.be(false); + }) + .done(); + }); + + it('should be false when targeting commit hashes', function () { + var resolver; + + fs.writeFileSync(path.join(tempDir, 'bower.json'), JSON.stringify({ + name: 'foo', + _resolution: { + type: 'commit', + commit: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + } + })); + GitResolver.fetchRefs = function () { + return Q.resolve([ + 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb refs/heads/master' + ]); + }; + + resolver = new GitResolver('foo'); + resolver.hasNew(tempDir) + .then(function (hasNew) { + expect(hasNew).to.be(true); + }) + .done(); + }); }); - describe('hasNew', function () { - it('should return a promise'); - it('should be true when the resolution type is different'); - it('should be true when a different tag (higher/lower) for a range is available'); - it('should be false when resolved to the same tag for a given range'); - it('should be true when a different commit hash for a given branch is available'); - it('should be false when resolved to the the same commit hash for a given branch'); - it('should be false when targeting commit hashes'); - it('should resolve to the master branch when the target is *'); - }); + describe('._resolveSelf', function () { + beforeEach(cleanInternalResolverCache); - describe('resolve', function () { - it('should return a promise'); - it('should call the necessary functions by thee correct order'); + it('should call the necessary functions by the correct order', function (next) { + function DummyResolver() { + GitResolver.apply(this, arguments); + this._stack = []; + } + + util.inherits(DummyResolver, GitResolver); + mout.object.mixIn(DummyResolver, GitResolver); + + DummyResolver.prototype.getStack = function () { + return this._stack; + }; + + DummyResolver.fetchRefs = function () { + return Q.resolve([ + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa refs/heads/master' + ]); + }; + + DummyResolver.prototype.resolve = function () { + this._stack = []; + return GitResolver.prototype.resolve.apply(this, arguments); + }; + + DummyResolver.prototype._findResolution = function () { + this._stack.push('before _findResolution'); + return GitResolver.prototype._findResolution.apply(this, arguments) + .then(function (val) { + this._stack.push('after _findResolution'); + return val; + }.bind(this)); + }; + DummyResolver.prototype._checkout = function () { + this._stack.push('before _checkout'); + return Q.resolve() + .then(function (val) { + this._stack.push('after _checkout'); + return val; + }.bind(this)); + }; + DummyResolver.prototype._cleanup = function () { + this._stack.push('before _cleanup'); + return GitResolver.prototype._cleanup.apply(this, arguments) + .then(function (val) { + this._stack.push('after _cleanup'); + return val; + }.bind(this)); + }; + + var resolver = new DummyResolver('foo', { target: 'master' }); + + resolver.resolve() + .then(function () { + expect(resolver.getStack()).to.eql([ + 'before _findResolution', + 'after _findResolution', + 'before _checkout', + 'after _checkout', + 'before _cleanup', + 'after _cleanup' + ]); + next(); + }) + .done(); + }); + + it('should reject the promise if _checkout is not implemented', function (next) { + var resolver = new GitResolver('foo'); + + GitResolver.fetchRefs = function () { + return Q.resolve([ + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa refs/heads/master' + ]); + }; + + resolver.resolve() + .then(function () { + next(new Error('Should have rejected the promise')); + }, function (err) { + expect(err).to.be.an(Error); + expect(err.message).to.contain('_checkout not implemented'); + next(); + }) + .done(); + }); + + it('should reject the promise if #fetchRefs is not implemented', function (next) { + var resolver = new GitResolver('foo'); + + resolver._checkout = function () { + return Q.resolve(); + }; + + resolver.resolve() + .then(function () { + next(new Error('Should have rejected the promise')); + }, function (err) { + expect(err).to.be.an(Error); + expect(err.message).to.contain('fetchRefs not implemented'); + next(); + }) + .done(); + }); }); describe('._findResolution', function () { - it('should return a promise'); + beforeEach(cleanInternalResolverCache); + + it('should resolve to an object', function () { + var resolver; + + GitResolver.fetchRefs = function () { + return Q.resolve([ + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa refs/heads/master' + ]); + }; + + resolver = new GitResolver('foo'); + resolver._findResolution('*') + .then(function (resolution) { + expect(resolution).to.be.an('object'); + }) + .done(); + }); + + it('should fail to resolve * if no tags/heads are found', function (next) { + var resolver; + + GitResolver.fetchRefs = function () { + return Q.resolve([]); + }; + + resolver = new GitResolver('foo'); + resolver._findResolution('*') + .then(function () { + next(new Error('Should have failed')); + }, function (err) { + expect(err).to.be.an(Error); + expect(err.message).to.match(/branch "master" does not exist/i); + expect(err.details).to.match(/no branches found/i); + expect(err.code).to.equal('ENORESTARGET'); + next(); + }) + .done(); + }); + + it('should resolve "*" to the latest commit on master if a repository has no tags', function (next) { + var resolver; + + GitResolver.fetchRefs = function () { + return Q.resolve([ + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa refs/heads/master', + 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb refs/heads/some-branch' + ]); + }; + + resolver = new GitResolver('foo'); + resolver._findResolution('*') + .then(function (resolution) { + expect(resolution).to.eql({ + type: 'branch', + branch: 'master', + commit: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + }); + next(); + }) + .done(); + }); + + it('should resolve "*" to the latest version if a repository has valid semver tags', function (next) { + var resolver; + + GitResolver.fetchRefs = function () { + return Q.resolve([ + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa refs/heads/master', + 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb refs/tags/0.1.0', + 'cccccccccccccccccccccccccccccccccccccccc refs/tags/v0.1.1' + ]); + }; + + resolver = new GitResolver('foo'); + resolver._findResolution('*') + .then(function (resolution) { + expect(resolver._resolution).to.equal(resolution); + expect(resolution).to.eql({ + type: 'tag', + tag: 'v0.1.1', + commit: 'cccccccccccccccccccccccccccccccccccccccc' + }); + next(); + }) + .done(); + }); + + it('should resolve to the latest version that matches a range/version', function (next) { + var resolver; + + GitResolver.fetchRefs = function () { + return Q.resolve([ + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa refs/heads/master', + 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb refs/tags/0.1.0', + 'cccccccccccccccccccccccccccccccccccccccc refs/tags/v0.1.1', + 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee refs/tags/0.2.0', + 'ffffffffffffffffffffffffffffffffffffffff refs/tags/v0.2.1' + ]); + }; + + resolver = new GitResolver('foo'); + resolver._findResolution('~0.2.0') + .then(function (resolution) { + expect(resolver._resolution).to.equal(resolution); + expect(resolution).to.eql({ + type: 'tag', + tag: 'v0.2.1', + commit: 'ffffffffffffffffffffffffffffffffffffffff' + }); + next(); + }) + .done(); + }); + + it('should fail to resolve if none of the tags matched a range/version', function (next) { + var resolver; + + GitResolver.fetchRefs = function () { + return Q.resolve([ + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa refs/heads/master', + 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb refs/tags/0.1.0', + 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb refs/tags/v0.1.1' + ]); + }; + + resolver = new GitResolver('foo'); + resolver._findResolution('~0.2.0') + .then(function () { + next(new Error('Should have failed')); + }, function (err) { + expect(err).to.be.an(Error); + expect(err.message).to.match(/was able to satisfy "~0.2.0"/i); + expect(err.details).to.match(/available versions in "foo" are: 0\.1\.1, 0\.1\.0/i); + expect(err.code).to.equal('ENORESTARGET'); + next(); + }) + .done(); + }); + + it('should fail to resolve if there are no tags to match a range/version', function (next) { + var resolver; + + GitResolver.fetchRefs = function () { + return Q.resolve([ + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa refs/heads/master' + ]); + }; + + resolver = new GitResolver('foo'); + + resolver._findResolution('~0.2.0') + .then(function () { + next(new Error('Should have failed')); + }, function (err) { + expect(err).to.be.an(Error); + expect(err.message).to.match(/was able to satisfy "~0.2.0"/i); + expect(err.details).to.match(/no versions found in "foo"/i); + expect(err.code).to.equal('ENORESTARGET'); + next(); + }) + .done(); + }); + + it('should resolve to the specified commit', function (next) { + var resolver; + + GitResolver.fetchRefs = function () { + return Q.resolve([ + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa refs/heads/master' + ]); + }; + + resolver = new GitResolver('foo'); + resolver._findResolution('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb') + .then(function (resolution) { + expect(resolver._resolution).to.equal(resolution); + expect(resolution).to.eql({ + type: 'commit', + commit: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' + }); + next(); + }) + .done(); + }); + + it('should resolve to the specified branch if it exists', function (next) { + var resolver; + + GitResolver.fetchRefs = function () { + return Q.resolve([ + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa refs/heads/master', + 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb refs/heads/some-branch' + ]); + }; + + resolver = new GitResolver('foo'); + resolver._findResolution('some-branch') + .then(function (resolution) { + expect(resolver._resolution).to.equal(resolution); + expect(resolution).to.eql({ + type: 'branch', + branch: 'some-branch', + commit: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' + }); + next(); + }) + .done(); + }); + + it('should fail to resolve to the specified branch if it doesn\'t exists', function (next) { + var resolver; + + GitResolver.fetchRefs = function () { + return Q.resolve([ + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa refs/heads/master' + ]); + }; + + resolver = new GitResolver('foo'); + resolver._findResolution('some-branch') + .then(function () { + next(new Error('Should have failed')); + }, function (err) { + expect(err).to.be.an(Error); + expect(err.message).to.match(/branch "some-branch" does not exist/i); + expect(err.details).to.match(/available branches in "foo" are: master/i); + expect(err.code).to.equal('ENORESTARGET'); + next(); + }) + .done(); + }); }); describe('._cleanup', function () { - it('should return a promise'); - it('should remove the .git folder from the temp dir'); + beforeEach(function (next) { + mkdirp(tempDir, next); + }); + + afterEach(function (next) { + // Need to chmodr before removing..at least on windows + // because .git has some read only files + chmodr(tempDir, 0777, function () { + rimraf(tempDir, next); + }); + }); + + it('should remove the .git folder from the temp dir', function (next) { + var resolver = new GitResolver('foo'), + dest = path.join(tempDir, '.git'); + + // Copy .git folder to the tempDir + ncp(path.resolve(__dirname, '../../../.git'), dest, function (err) { + if (err) return next(err); + + resolver._tempDir = tempDir; + + resolver._cleanup() + .then(function () { + expect(fs.existsSync(dest)).to.be(false); + next(); + }) + .done(); + }); + }); + + it('should not fail if .git does not exist for some reason', function (next) { + var resolver = new GitResolver('foo'), + dest = path.join(tempDir, '.git'); + + resolver._tempDir = tempDir; + + resolver._cleanup() + .then(function () { + expect(fs.existsSync(dest)).to.be(false); + next(); + }) + .done(); + }); }); describe('._savePkgMeta', function () { - it('should return a promise'); - it('should save the resolution to the json to be used later by .hasNew'); + beforeEach(function (next) { + mkdirp(tempDir, next); + }); + + afterEach(function (next) { + rimraf(tempDir, next); + }); + + it('should save the resolution to the .bower.json to be used later by .hasNew', function (next) { + var resolver = new GitResolver('foo'); + + resolver._resolution = { type: 'tag', tag: '0.0.1' }; + resolver._tempDir = tempDir; + + resolver._savePkgMeta({ name: 'foo', version: '0.0.1' }) + .then(function () { + return Q.nfcall(fs.readFile, path.join(tempDir, '.bower.json')); + }) + .then(function (contents) { + var json = JSON.parse(contents.toString()); + + expect(json).to.eql({ + name: 'foo', + version: '0.0.1', + _resolution: resolver._resolution + }); + + next(); + }) + .done(); + }); }); describe('#fetchHeads', function () { - it('should return a promise'); + beforeEach(cleanInternalResolverCache); + it('should resolve to an empty object if no heads are found', function (next) { GitResolver.fetchRefs = function () { return Q.resolve([]); @@ -140,7 +768,8 @@ describe('GitResolver', function () { }); describe('#fetchVersions', function () { - it('should return a promise'); + beforeEach(cleanInternalResolverCache); + it('should resolve to an empty array if no tags are found', function (next) { GitResolver.fetchRefs = function () { return Q.resolve([]); @@ -172,9 +801,9 @@ describe('GitResolver', function () { GitResolver.fetchVersions('foo') .then(function (versions) { expect(versions).to.eql([ - '0.2.1', - '0.1.1', - '0.1.0' + { version: '0.2.1', tag: '0.2.1', commit: 'cccccccccccccccccccccccccccccccccccccccc' }, + { version: '0.1.1', tag: 'v0.1.1', commit: 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' }, + { version: '0.1.0', tag: '0.1.0', commit: 'dddddddddddddddddddddddddddddddddddddddd' } ]); next(); }) @@ -187,10 +816,10 @@ describe('GitResolver', function () { // See: https://github.com/isaacs/node-semver/issues/16 return Q.resolve([ 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa refs/tags/0.1.0', - //'cccccccccccccccccccccccccccccccccccccccc refs/tags/0.1.1+build.11', - //'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb refs/tags/0.1.1+build.100', - //'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee refs/tags/0.1.1-rc.22', - //'dddddddddddddddddddddddddddddddddddddddd refs/tags/0.1.1-rc.200', + //'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb refs/tags/0.1.1+build.11', + //'cccccccccccccccccccccccccccccccccccccccc refs/tags/0.1.1+build.100', + //'dddddddddddddddddddddddddddddddddddddddd refs/tags/0.1.1-rc.22', + //'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee refs/tags/0.1.1-rc.200', 'ffffffffffffffffffffffffffffffffffffffff refs/tags/0.1.1', 'abbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb refs/tags/v0.2.1' ]); @@ -199,13 +828,13 @@ describe('GitResolver', function () { GitResolver.fetchVersions('foo') .then(function (versions) { expect(versions).to.eql([ - '0.2.1', - //'0.1.1+build.100', - //'0.1.1+build.11', - '0.1.1', - //'0.1.1-rc.200', - //'0.1.1-rc.22', - '0.1.0' + { version: '0.2.1', tag: 'v0.2.1', commit: 'abbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' }, + //{ version: '0.1.1+build.100', tag: '0.1.1+build.100', commit: 'cccccccccccccccccccccccccccccccccccccccc' }, + //{ version: '0.1.1+build.11', tag: '0.1.1+build.11', commit: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' }, + { version: '0.1.1', tag: '0.1.1', commit: 'ffffffffffffffffffffffffffffffffffffffff' }, + //{ version: '0.1.1-rc.200', tag: '0.1.1-rc.200', commit: 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' }, + //{ version: '0.1.1-rc.22', tag: '0.1.1-rc.22', commit: 'dddddddddddddddddddddddddddddddddddddddd' }, + { version: '0.1.0', tag: '0.1.0', commit: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' } ]); next(); }) @@ -229,12 +858,18 @@ describe('GitResolver', function () { GitResolver.fetchVersions('foo') .then(function (versions) { - expect(versions).to.eql(['0.2.1', '0.1.0']); + expect(versions).to.eql([ + { version: '0.2.1', tag: '0.2.1', commit: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' }, + { version: '0.1.0', tag: '0.1.0', commit: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' } + ]); return GitResolver.fetchVersions('bar'); }) .then(function (versions) { - expect(versions).to.eql(['0.3.1', '0.3.0']); + expect(versions).to.eql([ + { version: '0.3.1', tag: '0.3.1', commit: 'cccccccccccccccccccccccccccccccccccccccc' }, + { version: '0.3.0', tag: '0.3.0', commit: 'dddddddddddddddddddddddddddddddddddddddd' } + ]); // Test for the cache expect(GitResolver._versions).to.be.an('object'); @@ -248,12 +883,16 @@ describe('GitResolver', function () { return GitResolver.fetchVersions('foo'); }) .then(function (versions) { - expect(versions).to.eql(['0.2.1']); + expect(versions).to.eql([ + { version: '0.2.1', tag: '0.2.1', commit: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' } + ]); return GitResolver.fetchVersions('bar'); }) .then(function (versions) { - expect(versions).to.eql(['0.3.1']); + expect(versions).to.eql([ + { version: '0.3.1', tag: '0.3.1', commit: 'cccccccccccccccccccccccccccccccccccccccc' } + ]); next(); }) diff --git a/test/resolve/worker.js b/test/resolve/worker.js index af71164b..893b6de0 100644 --- a/test/resolve/worker.js +++ b/test/resolve/worker.js @@ -55,7 +55,7 @@ describe('Worker', function () { timeout = setTimeout(function () { expect(calls).to.equal(1); next(); - }, 100); + }, 25); }); it('should assume the default concurrency when a type is not known', function (next) { @@ -68,7 +68,7 @@ describe('Worker', function () { timeout = setTimeout(function () { expect(calls).to.equal(1); next(); - }, 100); + }, 25); }); it('should have different slots when type is not passed or is not known', function (next) { @@ -83,7 +83,7 @@ describe('Worker', function () { timeout = setTimeout(function () { expect(calls).to.equal(2); next(); - }, 100); + }, 25); }); it('should use the configured concurrency for the type', function (next) { @@ -111,7 +111,7 @@ describe('Worker', function () { expect(calls.foo).to.equal(2); expect(calls.bar).to.equal(3); next(); - }, 100); + }, 25); }); }); @@ -135,7 +135,7 @@ describe('Worker', function () { timeout = setTimeout(function () { expect(calls).to.equal(4); next(); - }, 100); + }, 25); }); it('should wait for currently running functions to finish', function (next) { @@ -152,7 +152,7 @@ describe('Worker', function () { setTimeout(function () { calls.push(3); deferred.resolve(); - }, 100); + }, 25); return deferred.promise; }, 'foo'); @@ -183,7 +183,7 @@ describe('Worker', function () { timeout = setTimeout(function () { expect(calls).to.equal(5); next(); - }, 100); + }, 25); }); it('should respect the enqueue order', function (next) { @@ -225,7 +225,7 @@ describe('Worker', function () { expect(defCalls).to.eql([1, 2, 3]); expect(fooCalls).to.eql([1, 2, 3]); next(); - }, 100); + }, 25); }); it('should wait for one slot in every type on a multi-type function', function (next) { @@ -246,7 +246,7 @@ describe('Worker', function () { timeout = setTimeout(function () { expect(calls).to.equal(2); next(); - }, 100); + }, 25); }); it('should free all type slots when finished running a function', function (next) { @@ -264,7 +264,7 @@ describe('Worker', function () { timeout = setTimeout(function () { expect(calls).to.equal(3); next(); - }, 100); + }, 25); }); }); }); \ No newline at end of file diff --git a/test/test.js b/test/test.js index 3ef02d19..106fafd9 100644 --- a/test/test.js +++ b/test/test.js @@ -16,7 +16,7 @@ function testGitRemoteResolver() { } function testGitFsResolver() { - var bowerResolver = new GitFsResolver('.', { + var bowerResolver = new GitFsResolver(__dirname + '/..', { name: 'bower', target: 'rewrite' }); @@ -56,6 +56,6 @@ if (process.argv[1] && !/mocha/.test(process.argv[1])) { require('./resolve/resolver'); require('./resolve/resolvers/gitResolver'); - //require('./resolve/resolvers/gitFsResolver'); + require('./resolve/resolvers/gitFsResolver'); require('./resolve/worker'); } \ No newline at end of file