bricks-cli
Version:
Command line tool for developing ambitious ember.js apps
693 lines (594 loc) • 29.7 kB
JavaScript
var expect = require('expect.js');
var fs = require('graceful-fs');
var path = require('path');
var mkdirp = require('mkdirp');
var mout = require('mout');
var Q = require('q');
var rimraf = require('rimraf');
var RegistryClient = require('bower-registry-client');
var Logger = require('bower-logger');
var resolverFactory = require('../../lib/core/resolverFactory');
var resolvers = require('../../lib/core/resolvers');
var defaultConfig = require('../../lib/config');
describe('resolverFactory', function () {
var tempSource;
var logger = new Logger();
var registryClient = new RegistryClient(mout.object.fillIn({
cache: defaultConfig._registry
}, defaultConfig));
afterEach(function (next) {
logger.removeAllListeners();
if (tempSource) {
rimraf(tempSource, next);
tempSource = null;
} else {
next();
}
});
after(function (next) {
rimraf('dejavu', next);
});
function callFactory(decEndpoint, config) {
return resolverFactory(decEndpoint, config || defaultConfig, logger, registryClient);
}
it('should recognize git remote endpoints correctly', function (next) {
var promise = Q.resolve();
var endpoints;
endpoints = {
// git:
'git://hostname.com/user/project': 'git://hostname.com/user/project',
'git://hostname.com/user/project/': 'git://hostname.com/user/project',
'git://hostname.com/user/project.git': 'git://hostname.com/user/project.git',
'git://hostname.com/user/project.git/': 'git://hostname.com/user/project.git',
// git@:
'git@hostname.com:user/project': 'git@hostname.com:user/project',
'git@hostname.com:user/project/': 'git@hostname.com:user/project',
'git@hostname.com:user/project.git': 'git@hostname.com:user/project.git',
'git@hostname.com:user/project.git/': 'git@hostname.com:user/project.git',
// git+ssh:
'git+ssh://user@hostname.com:project': 'ssh://user@hostname.com:project',
'git+ssh://user@hostname.com:project/': 'ssh://user@hostname.com:project',
'git+ssh://user@hostname.com:project.git': 'ssh://user@hostname.com:project.git',
'git+ssh://user@hostname.com:project.git/': 'ssh://user@hostname.com:project.git',
'git+ssh://user@hostname.com/project': 'ssh://user@hostname.com/project',
'git+ssh://user@hostname.com/project/': 'ssh://user@hostname.com/project',
'git+ssh://user@hostname.com/project.git': 'ssh://user@hostname.com/project.git',
'git+ssh://user@hostname.com/project.git/': 'ssh://user@hostname.com/project.git',
// git+http
'git+http://hostname.com/project/blah': 'http://hostname.com/project/blah',
'git+http://hostname.com/project/blah/': 'http://hostname.com/project/blah',
'git+http://hostname.com/project/blah.git': 'http://hostname.com/project/blah.git',
'git+http://hostname.com/project/blah.git/': 'http://hostname.com/project/blah.git',
'git+http://user@hostname.com/project/blah': 'http://user@hostname.com/project/blah',
'git+http://user@hostname.com/project/blah/': 'http://user@hostname.com/project/blah',
'git+http://user@hostname.com/project/blah.git': 'http://user@hostname.com/project/blah.git',
'git+http://user@hostname.com/project/blah.git/': 'http://user@hostname.com/project/blah.git',
// git+https
'git+https://hostname.com/project/blah': 'https://hostname.com/project/blah',
'git+https://hostname.com/project/blah/': 'https://hostname.com/project/blah',
'git+https://hostname.com/project/blah.git': 'https://hostname.com/project/blah.git',
'git+https://hostname.com/project/blah.git/': 'https://hostname.com/project/blah.git',
'git+https://user@hostname.com/project/blah': 'https://user@hostname.com/project/blah',
'git+https://user@hostname.com/project/blah/': 'https://user@hostname.com/project/blah',
'git+https://user@hostname.com/project/blah.git': 'https://user@hostname.com/project/blah.git',
'git+https://user@hostname.com/project/blah.git/': 'https://user@hostname.com/project/blah.git',
// ssh .git$
'ssh://user@hostname.com:project.git': 'ssh://user@hostname.com:project.git',
'ssh://user@hostname.com:project.git/': 'ssh://user@hostname.com:project.git',
'ssh://user@hostname.com/project.git': 'ssh://user@hostname.com/project.git',
'ssh://user@hostname.com/project.git/': 'ssh://user@hostname.com/project.git',
// http .git$
'http://hostname.com/project.git': 'http://hostname.com/project.git',
'http://hostname.com/project.git/': 'http://hostname.com/project.git',
'http://user@hostname.com/project.git': 'http://user@hostname.com/project.git',
'http://user@hostname.com/project.git/': 'http://user@hostname.com/project.git',
// https .git$
'https://hostname.com/project.git': 'https://hostname.com/project.git',
'https://hostname.com/project.git/': 'https://hostname.com/project.git',
'https://user@hostname.com/project.git': 'https://user@hostname.com/project.git',
'https://user@hostname.com/project.git/': 'https://user@hostname.com/project.git',
// shorthand
'bower/bower': 'git://github.com/bower/bower.git'
};
mout.object.forOwn(endpoints, function (value, key) {
// Test without name and target
promise = promise.then(function () {
return callFactory({ source: key });
})
.then(function (resolver) {
expect(resolver).to.be.a(resolvers.GitRemote);
expect(resolver).to.not.be(resolvers.GitHub);
expect(resolver.getSource()).to.equal(value);
expect(resolver.getTarget()).to.equal('*');
});
// Test with target
promise = promise.then(function () {
return callFactory({ source: key, target: 'commit-ish' });
})
.then(function (resolver) {
expect(resolver).to.be.a(resolvers.GitRemote);
expect(resolver).to.not.be(resolvers.GitHub);
expect(resolver.getSource()).to.equal(value);
expect(resolver.getTarget()).to.equal('commit-ish');
});
// Test with name
promise = promise.then(function () {
return callFactory({ name: 'foo', source: key });
})
.then(function (resolver) {
expect(resolver).to.be.a(resolvers.GitRemote);
expect(resolver).to.not.be(resolvers.GitHub);
expect(resolver.getSource()).to.equal(value);
expect(resolver.getName()).to.equal('foo');
expect(resolver.getTarget()).to.equal('*');
});
});
promise
.then(next.bind(next, null))
.done();
});
it('should recognize GitHub endpoints correctly', function (next) {
var promise = Q.resolve();
var gitHub;
var nonGitHub;
gitHub = {
// git:
'git://github.com/user/project': 'git://github.com/user/project.git',
'git://github.com/user/project/': 'git://github.com/user/project.git',
'git://github.com/user/project.git': 'git://github.com/user/project.git',
'git://github.com/user/project.git/': 'git://github.com/user/project.git',
// git@:
'git@github.com:user/project': 'git@github.com:user/project.git',
'git@github.com:user/project/': 'git@github.com:user/project.git',
'git@github.com:user/project.git': 'git@github.com:user/project.git',
'git@github.com:user/project.git/': 'git@github.com:user/project.git',
// git+ssh:
'git+ssh://git@github.com:project/blah': 'ssh://git@github.com:project/blah.git',
'git+ssh://git@github.com:project/blah/': 'ssh://git@github.com:project/blah.git',
'git+ssh://git@github.com:project/blah.git': 'ssh://git@github.com:project/blah.git',
'git+ssh://git@github.com:project/blah.git/': 'ssh://git@github.com:project/blah.git',
'git+ssh://git@github.com/project/blah': 'ssh://git@github.com/project/blah.git',
'git+ssh://git@github.com/project/blah/': 'ssh://git@github.com/project/blah.git',
'git+ssh://git@github.com/project/blah.git': 'ssh://git@github.com/project/blah.git',
'git+ssh://git@github.com/project/blah.git/': 'ssh://git@github.com/project/blah.git',
// git+http
'git+http://github.com/project/blah': 'http://github.com/project/blah.git',
'git+http://github.com/project/blah/': 'http://github.com/project/blah.git',
'git+http://github.com/project/blah.git': 'http://github.com/project/blah.git',
'git+http://github.com/project/blah.git/': 'http://github.com/project/blah.git',
'git+http://user@github.com/project/blah': 'http://user@github.com/project/blah.git',
'git+http://user@github.com/project/blah/': 'http://user@github.com/project/blah.git',
'git+http://user@github.com/project/blah.git': 'http://user@github.com/project/blah.git',
'git+http://user@github.com/project/blah.git/': 'http://user@github.com/project/blah.git',
// git+https
'git+https://github.com/project/blah': 'https://github.com/project/blah.git',
'git+https://github.com/project/blah/': 'https://github.com/project/blah.git',
'git+https://github.com/project/blah.git': 'https://github.com/project/blah.git',
'git+https://github.com/project/blah.git/': 'https://github.com/project/blah.git',
'git+https://user@github.com/project/blah': 'https://user@github.com/project/blah.git',
'git+https://user@github.com/project/blah/': 'https://user@github.com/project/blah.git',
'git+https://user@github.com/project/blah.git': 'https://user@github.com/project/blah.git',
'git+https://user@github.com/project/blah.git/': 'https://user@github.com/project/blah.git',
// ssh .git$
'ssh://git@github.com:project/blah.git': 'ssh://git@github.com:project/blah.git',
'ssh://git@github.com:project/blah.git/': 'ssh://git@github.com:project/blah.git',
'ssh://git@github.com/project/blah.git': 'ssh://git@github.com/project/blah.git',
'ssh://git@github.com/project/blah.git/': 'ssh://git@github.com/project/blah.git',
// http .git$
'http://github.com/project/blah.git': 'http://github.com/project/blah.git',
'http://github.com/project/blah.git/': 'http://github.com/project/blah.git',
'http://user@github.com/project/blah.git': 'http://user@github.com/project/blah.git',
'http://user@github.com/project/blah.git/': 'http://user@github.com/project/blah.git',
// https
'https://github.com/project/blah.git': 'https://github.com/project/blah.git',
'https://github.com/project/blah.git/': 'https://github.com/project/blah.git',
'https://user@github.com/project/blah.git': 'https://user@github.com/project/blah.git',
'https://user@github.com/project/blah.git/': 'https://user@github.com/project/blah.git',
// shorthand
'bower/bower': 'git://github.com/bower/bower.git'
};
nonGitHub = [
'git://github.com/user/project/bleh.git',
'git://xxxxgithub.com/user/project.git',
'git@xxxxgithub.com:user:project.git',
'git@xxxxgithub.com:user/project.git',
'git+ssh://git@xxxxgithub.com:user/project',
'git+ssh://git@xxxxgithub.com/user/project',
'git+http://user@xxxxgithub.com/user/project',
'git+https://user@xxxxgithub.com/user/project',
'ssh://git@xxxxgithub.com:user/project.git',
'ssh://git@xxxxgithub.com/user/project.git',
'http://xxxxgithub.com/user/project.git',
'https://xxxxgithub.com/user/project.git',
'http://user@xxxxgithub.com/user/project.git',
'https://user@xxxxgithub.com/user/project.git'
];
// Test GitHub ones
mout.object.forOwn(gitHub, function (value, key) {
// Test without name and target
promise = promise.then(function () {
return callFactory({ source: key });
})
.then(function (resolver) {
expect(resolver).to.be.a(resolvers.GitHub);
expect(resolver.getSource()).to.equal(value);
expect(resolver.getTarget()).to.equal('*');
});
// Test with target
promise = promise.then(function () {
return callFactory({ source: key, target: 'commit-ish' });
})
.then(function (resolver) {
if (value) {
expect(resolver).to.be.a(resolvers.GitHub);
expect(resolver.getSource()).to.equal(value);
expect(resolver.getTarget()).to.equal('commit-ish');
} else {
expect(resolver).to.not.be.a(resolvers.GitHub);
}
});
// Test with name
promise = promise.then(function () {
return callFactory({ name: 'foo', source: key });
})
.then(function (resolver) {
if (value) {
expect(resolver).to.be.a(resolvers.GitHub);
expect(resolver.getSource()).to.equal(value);
expect(resolver.getName()).to.equal('foo');
expect(resolver.getTarget()).to.equal('*');
} else {
expect(resolver).to.not.be.a(resolvers.GitHub);
}
});
});
// Test similar to GitHub but not real GitHub
nonGitHub.forEach(function (value) {
promise = promise.then(function () {
return callFactory({ source: value });
})
.then(function (resolver) {
expect(resolver).to.not.be.a(resolvers.GitHub);
expect(resolver).to.be.a(resolvers.GitRemote);
});
});
promise
.then(next.bind(next, null))
.done();
});
it('should recognize local fs git endpoints correctly', function (next) {
var promise = Q.resolve();
var endpoints;
var temp;
endpoints = {};
// Absolute path
temp = path.resolve(__dirname, '../assets/package-a');
endpoints[temp] = temp;
// Absolute path that ends with a /
// See: https://github.com/bower/bower/issues/898
temp = path.resolve(__dirname, '../assets/package-a') + '/';
endpoints[temp] = temp;
// Relative path
endpoints[__dirname + '/../assets/package-a'] = temp;
// TODO: test with backslashes on windows and ~/ on unix
mout.object.forOwn(endpoints, function (value, key) {
// Test without name
promise = promise.then(function () {
return callFactory({ source: key });
})
.then(function (resolver) {
expect(resolver).to.be.a(resolvers.GitFs);
expect(resolver.getTarget()).to.equal('*');
});
// Test with name
promise = promise.then(function () {
return callFactory({ name: 'foo', source: key });
})
.then(function (resolver) {
expect(resolver).to.be.a(resolvers.GitFs);
expect(resolver.getName()).to.equal('foo');
expect(resolver.getTarget()).to.equal('*');
});
});
promise
.then(next.bind(next, null))
.done();
});
it('should recognize svn remote endpoints correctly', function (next) {
var promise = Q.resolve();
var endpoints;
endpoints = {
// svn:
'svn://hostname.com/user/project': 'http://hostname.com/user/project',
'svn://hostname.com/user/project/': 'http://hostname.com/user/project',
// svn@:
'svn://svn@hostname.com:user/project': 'http://svn@hostname.com:user/project',
'svn://svn@hostname.com:user/project/': 'http://svn@hostname.com:user/project',
// svn+http
'svn+http://hostname.com/project/blah': 'http://hostname.com/project/blah',
'svn+http://hostname.com/project/blah/': 'http://hostname.com/project/blah',
'svn+http://user@hostname.com/project/blah': 'http://user@hostname.com/project/blah',
'svn+http://user@hostname.com/project/blah/': 'http://user@hostname.com/project/blah',
// svn+https
'svn+https://hostname.com/project/blah': 'https://hostname.com/project/blah',
'svn+https://hostname.com/project/blah/': 'https://hostname.com/project/blah',
'svn+https://user@hostname.com/project/blah': 'https://user@hostname.com/project/blah',
'svn+https://user@hostname.com/project/blah/': 'https://user@hostname.com/project/blah',
// svn+ssh
'svn+ssh://hostname.com/project/blah': 'svn+ssh://hostname.com/project/blah',
'svn+ssh://hostname.com/project/blah/': 'svn+ssh://hostname.com/project/blah',
'svn+ssh://user@hostname.com/project/blah': 'svn+ssh://user@hostname.com/project/blah',
'svn+ssh://user@hostname.com/project/blah/': 'svn+ssh://user@hostname.com/project/blah',
// svn+file
'svn+file:///project/blah': 'file:///project/blah',
'svn+file:///project/blah/': 'file:///project/blah'
};
mout.object.forOwn(endpoints, function (value, key) {
// Test without name and target
promise = promise.then(function () {
return callFactory({ source: key });
})
.then(function (resolver) {
expect(resolver).to.be.a(resolvers.Svn);
expect(resolver).to.not.be(resolvers.GitHub);
expect(resolvers.Svn.getSource(resolver.getSource())).to.equal(value);
expect(resolver.getTarget()).to.equal('*');
});
// Test with target
promise = promise.then(function () {
return callFactory({ source: key, target: 'commit-ish' });
})
.then(function (resolver) {
expect(resolver).to.be.a(resolvers.Svn);
expect(resolver).to.not.be(resolvers.GitHub);
expect(resolvers.Svn.getSource(resolver.getSource())).to.equal(value);
expect(resolver.getTarget()).to.equal('commit-ish');
});
// Test with name
promise = promise.then(function () {
return callFactory({ name: 'foo', source: key });
})
.then(function (resolver) {
expect(resolver).to.be.a(resolvers.Svn);
expect(resolver).to.not.be(resolvers.GitHub);
expect(resolvers.Svn.getSource(resolver.getSource())).to.equal(value);
expect(resolver.getName()).to.equal('foo');
expect(resolver.getTarget()).to.equal('*');
});
});
promise
.then(next.bind(next, null))
.done();
});
it('should recognize local fs files/folder endpoints correctly', function (next) {
var promise = Q.resolve();
var endpoints;
var temp;
tempSource = path.resolve(__dirname, '../tmp/tmp');
mkdirp.sync(tempSource);
fs.writeFileSync(path.join(tempSource, '.git'), 'foo');
fs.writeFileSync(path.join(tempSource, 'file.with.multiple.dots'), 'foo');
endpoints = {};
// Absolute path to folder with .git file
endpoints[tempSource] = tempSource;
// Relative path to folder with .git file
endpoints[__dirname + '/../tmp/tmp'] = tempSource;
// Absolute path to folder
temp = path.resolve(__dirname, '../assets/test-temp-dir');
endpoints[temp] = temp;
// Absolute + relative path to folder
endpoints[__dirname + '/../assets/test-temp-dir'] = temp;
// Absolute path to file
temp = path.resolve(__dirname, '../assets/package-zip.zip');
endpoints[temp] = temp;
// Absolute + relative path to file
endpoints[__dirname + '/../assets/package-zip.zip'] = temp;
// Relative ../
endpoints['../'] = path.normalize(__dirname + '/../../..');
// Relative ./
endpoints['./test/assets'] = path.join(__dirname, '../assets');
// Relative with just one slash, to test fs resolution
// priority against shorthands
endpoints['./test'] = path.join(__dirname, '..');
// Test files with multiple dots (PR #474)
temp = path.join(tempSource, 'file.with.multiple.dots');
endpoints[temp] = temp;
mout.object.forOwn(endpoints, function (value, key) {
// Test without name
promise = promise.then(function () {
return callFactory({ source: key });
})
.then(function (resolver) {
expect(resolver.getSource()).to.equal(value);
expect(resolver).to.be.a(resolvers.Fs);
expect(resolver.getTarget()).to.equal('*');
});
// Test with name
promise = promise.then(function () {
return callFactory({ name: 'foo', source: key });
})
.then(function (resolver) {
expect(resolver).to.be.a(resolvers.Fs);
expect(resolver.getName()).to.equal('foo');
expect(resolver.getTarget()).to.equal('*');
expect(resolver.getSource()).to.equal(value);
});
});
promise
.then(next.bind(next, null))
.done();
});
it('should recognize URL endpoints correctly', function (next) {
var promise = Q.resolve();
var endpoints;
endpoints = [
'http://bower.io/foo.js',
'https://bower.io/foo.js'
];
endpoints.forEach(function (source) {
// Test without name
promise = promise.then(function () {
return callFactory({ source: source });
})
.then(function (resolver) {
expect(resolver).to.be.a(resolvers.Url);
expect(resolver.getSource()).to.equal(source);
});
// Test with name
promise = promise.then(function () {
return callFactory({ name: 'foo', source: source });
})
.then(function (resolver) {
expect(resolver).to.be.a(resolvers.Url);
expect(resolver.getName()).to.equal('foo');
expect(resolver.getSource()).to.equal(source);
});
});
promise
.then(next.bind(next, null))
.done();
});
it('should recognize registry endpoints correctly', function (next) {
// Create a 'dejavu' file at the root to prevent regressions of #666
fs.writeFileSync('dejavu', 'foo');
callFactory({ source: 'dejavu' })
.then(function (resolver) {
expect(resolver).to.be.a(resolvers.GitRemote);
expect(resolver.getSource()).to.equal('git://github.com/IndigoUnited/dejavu.git');
expect(resolver.getTarget()).to.equal('*');
})
.then(function () {
// Test with name
return callFactory({ source: 'dejavu', name: 'foo' })
.then(function (resolver) {
expect(resolver).to.be.a(resolvers.GitRemote);
expect(resolver.getSource()).to.equal('git://github.com/IndigoUnited/dejavu.git');
expect(resolver.getName()).to.equal('foo');
expect(resolver.getTarget()).to.equal('*');
});
})
.then(function () {
// Test with target
return callFactory({ source: 'dejavu', target: '~2.0.0' })
.then(function (resolver) {
expect(resolver).to.be.a(resolvers.GitRemote);
expect(resolver.getTarget()).to.equal('~2.0.0');
next();
});
})
.done();
});
it('should error out if the package was not found in the registry', function (next) {
callFactory({ source: 'some-package-that-will-never-exist' })
.then(function () {
throw new Error('Should have failed');
}, function (err) {
expect(err).to.be.an(Error);
expect(err.code).to.equal('ENOTFOUND');
expect(err.message).to.contain('some-package-that-will-never-exist');
next();
})
.done();
});
it('should set registry to true on the decomposed endpoint if fetched from the registry', function (next) {
var decEndpoint = { source: 'dejavu' };
callFactory(decEndpoint)
.then(function () {
expect(decEndpoint.registry).to.be(true);
next();
})
.done();
});
it('should use the configured shorthand resolver', function (next) {
callFactory({ source: 'bower/bower' })
.then(function (resolver) {
var config;
expect(resolver.getSource()).to.equal('git://github.com/bower/bower.git');
config = mout.object.fillIn({
shorthandResolver: 'git://bower.io/{{owner}}/{{package}}/{{shorthand}}'
}, defaultConfig);
return callFactory({ source: 'IndigoUnited/promptly' }, config);
})
.then(function (resolver) {
expect(resolver.getSource()).to.equal('git://bower.io/IndigoUnited/promptly/IndigoUnited/promptly');
next();
})
.done();
});
it('should not expand using the shorthand resolver if it looks like a SSH URL', function (next) {
callFactory({ source: 'bleh@xxx.com:foo/bar' })
.then(function (resolver) {
throw new Error('Should have failed');
}, function (err) {
expect(err).to.be.an(Error);
expect(err.code).to.equal('ENOTFOUND');
expect(err.message).to.contain('bleh@xxx.com:foo/bar');
next();
})
.done();
});
it('should error out if there\'s no suitable resolver for a given source', function (next) {
resolverFactory({ source: 'some-package-that-will-never-exist' }, defaultConfig, logger)
.then(function () {
throw new Error('Should have failed');
}, function (err) {
expect(err).to.be.an(Error);
expect(err.code).to.be('ENORESOLVER');
expect(err.message).to.contain('appropriate resolver');
next();
})
.done();
});
it.skip('should use config.cwd when resolving relative paths');
it('should not swallow constructor errors when instantiating resolvers', function (next) {
var promise = Q.resolve();
var endpoints;
// TODO: test with others
endpoints = [
'http://bower.io/foo.js',
path.resolve(__dirname, '../assets/test-temp-dir')
];
endpoints.forEach(function (source) {
promise = promise.then(function () {
return callFactory({ source: source, target: 'bleh' });
})
.then(function () {
throw new Error('Should have failed');
}, function (err) {
expect(err).to.be.an(Error);
expect(err.message).to.match(/can't resolve targets/i);
expect(err.code).to.equal('ENORESTARGET');
});
});
promise
.then(next.bind(next, null))
.done();
});
describe('.clearRuntimeCache', function () {
it('should call every resolver static method that clears the runtime cache', function () {
var originalMethods = {};
var called = [];
var error;
mout.object.forOwn(resolvers, function (ConcreteResolver, key) {
originalMethods[key] = ConcreteResolver.clearRuntimeCache;
ConcreteResolver.clearRuntimeCache = function () {
called.push(key);
return originalMethods[key].apply(this, arguments);
};
});
try {
resolverFactory.clearRuntimeCache();
} catch (e) {
error = e;
} finally {
mout.object.forOwn(resolvers, function (ConcreteResolver, key) {
ConcreteResolver.clearRuntimeCache = originalMethods[key];
});
}
if (error) {
throw error;
}
expect(called.sort()).to.eql(Object.keys(resolvers).sort());
});
});
});