mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-09 04:31:17 +03:00
5b9c213849
no issue - preperation for User model refactoring - the rule is: --> when calling a unit, this unit should return something new --> and NOT modifying an existing object and return it (this is an unexpected behaviour, especially for utils and libs)
437 lines
17 KiB
JavaScript
437 lines
17 KiB
JavaScript
var should = require('should'),
|
|
sinon = require('sinon'),
|
|
nock = require('nock'),
|
|
tmp = require('tmp'),
|
|
join = require('path').join,
|
|
fs = require('fs'),
|
|
configUtils = require('../utils/configUtils'),
|
|
parsePackageJson = require('../../server/utils/parse-package-json'),
|
|
readDirectory = require('../../server/utils/read-directory'),
|
|
readThemes = require('../../server/utils/read-themes'),
|
|
gravatar = require('../../server/utils/gravatar'),
|
|
utils = require('../../server/utils');
|
|
|
|
// To stop jshint complaining
|
|
should.equal(true, true);
|
|
|
|
describe('Server Utilities', function () {
|
|
describe('Safe String', function () {
|
|
var safeString = utils.safeString,
|
|
options = {};
|
|
|
|
it('should remove beginning and ending whitespace', function () {
|
|
var result = safeString(' stringwithspace ', options);
|
|
result.should.equal('stringwithspace');
|
|
});
|
|
|
|
it('should remove non ascii characters', function () {
|
|
var result = safeString('howtowin✓', options);
|
|
result.should.equal('howtowin');
|
|
});
|
|
|
|
it('should replace spaces with dashes', function () {
|
|
var result = safeString('how to win', options);
|
|
result.should.equal('how-to-win');
|
|
});
|
|
|
|
it('should replace most special characters with dashes', function () {
|
|
var result = safeString('a:b/c?d#e[f]g!h$i&j(k)l*m+n,o;{p}=q\\r%s<t>u|v^w~x£y"z@1.2`3', options);
|
|
result.should.equal('a-b-c-d-e-f-g-h-i-j-k-l-m-n-o-p-q-r-s-t-u-v-w-x-y-z-1-2-3');
|
|
});
|
|
|
|
it('should replace all of the html4 compat symbols in ascii except hyphen and underscore', function () {
|
|
// note: This is missing the soft-hyphen char that isn't much-liked by linters/browsers/etc,
|
|
// it passed the test before it was removed
|
|
var result = safeString('!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿');
|
|
result.should.equal('_-c-y-ss-c-a-r-deg-23up-1o-1-41-23-4');
|
|
});
|
|
|
|
it('should replace all of the foreign chars in ascii', function () {
|
|
var result = safeString('ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ');
|
|
result.should.equal('aaaaaaaeceeeeiiiidnoooooxouuuuuthssaaaaaaaeceeeeiiiidnooooo-ouuuuythy');
|
|
});
|
|
|
|
it('should remove special characters at the beginning of a string', function () {
|
|
var result = safeString('.Not special', options);
|
|
result.should.equal('not-special');
|
|
});
|
|
|
|
it('should remove apostrophes ', function () {
|
|
var result = safeString('how we shouldn\'t be', options);
|
|
result.should.equal('how-we-shouldnt-be');
|
|
});
|
|
|
|
it('should convert to lowercase', function () {
|
|
var result = safeString('This has Upper Case', options);
|
|
result.should.equal('this-has-upper-case');
|
|
});
|
|
|
|
it('should convert multiple dashes into a single dash', function () {
|
|
var result = safeString('This :) means everything', options);
|
|
result.should.equal('this-means-everything');
|
|
});
|
|
|
|
it('should remove trailing dashes from the result', function () {
|
|
var result = safeString('This.', options);
|
|
result.should.equal('this');
|
|
});
|
|
|
|
it('should handle pound signs', function () {
|
|
var result = safeString('WHOOPS! I spent all my £ again!', options);
|
|
result.should.equal('whoops-i-spent-all-my-again');
|
|
});
|
|
|
|
it('should properly handle unicode punctuation conversion', function () {
|
|
var result = safeString('に間違いがないか、再度確認してください。再読み込みしてください。', options);
|
|
result.should.equal('nijian-wei-iganaika-zai-du-que-ren-sitekudasai-zai-du-miip-misitekudasai');
|
|
});
|
|
|
|
it('should not lose or convert dashes if options are passed with truthy importing flag', function () {
|
|
var result,
|
|
options = {importing: true};
|
|
result = safeString('-slug-with-starting-ending-and---multiple-dashes-', options);
|
|
result.should.equal('-slug-with-starting-ending-and---multiple-dashes-');
|
|
});
|
|
|
|
it('should still remove/convert invalid characters when passed options with truthy importing flag', function () {
|
|
var result,
|
|
options = {importing: true};
|
|
result = safeString('-slug-&with-✓-invalid-characters-に\'', options);
|
|
result.should.equal('-slug--with--invalid-characters-ni');
|
|
});
|
|
});
|
|
|
|
describe('parse-package-json', function () {
|
|
it('should parse valid package.json', function (done) {
|
|
var pkgJson, tmpFile;
|
|
|
|
tmpFile = tmp.fileSync();
|
|
pkgJson = JSON.stringify({
|
|
name: 'test',
|
|
version: '0.0.0'
|
|
});
|
|
|
|
fs.writeSync(tmpFile.fd, pkgJson);
|
|
|
|
parsePackageJson(tmpFile.name)
|
|
.then(function (pkg) {
|
|
pkg.should.eql({
|
|
name: 'test',
|
|
version: '0.0.0'
|
|
});
|
|
|
|
done();
|
|
})
|
|
.catch(done)
|
|
.finally(tmpFile.removeCallback);
|
|
});
|
|
|
|
it('should fail when name is missing', function (done) {
|
|
var pkgJson, tmpFile;
|
|
|
|
tmpFile = tmp.fileSync();
|
|
pkgJson = JSON.stringify({
|
|
version: '0.0.0'
|
|
});
|
|
|
|
fs.writeSync(tmpFile.fd, pkgJson);
|
|
|
|
parsePackageJson(tmpFile.name)
|
|
.then(function () {
|
|
done(new Error('parsePackageJson succeeded, but should\'ve failed'));
|
|
})
|
|
.catch(function (err) {
|
|
err.message.should.equal('"name" or "version" is missing from theme package.json file.');
|
|
err.context.should.equal(tmpFile.name);
|
|
err.help.should.equal('This will be required in future. Please see http://docs.ghost.org/themes/');
|
|
|
|
done();
|
|
})
|
|
.catch(done)
|
|
.finally(tmpFile.removeCallback);
|
|
});
|
|
|
|
it('should fail when version is missing', function (done) {
|
|
var pkgJson, tmpFile;
|
|
|
|
tmpFile = tmp.fileSync();
|
|
pkgJson = JSON.stringify({
|
|
name: 'test'
|
|
});
|
|
|
|
fs.writeSync(tmpFile.fd, pkgJson);
|
|
|
|
parsePackageJson(tmpFile.name)
|
|
.then(function () {
|
|
done(new Error('parsePackageJson succeeded, but should\'ve failed'));
|
|
})
|
|
.catch(function (err) {
|
|
err.message.should.equal('"name" or "version" is missing from theme package.json file.');
|
|
err.context.should.equal(tmpFile.name);
|
|
err.help.should.equal('This will be required in future. Please see http://docs.ghost.org/themes/');
|
|
|
|
done();
|
|
})
|
|
.catch(done)
|
|
.finally(tmpFile.removeCallback);
|
|
});
|
|
|
|
it('should fail when JSON is invalid', function (done) {
|
|
var pkgJson, tmpFile;
|
|
|
|
tmpFile = tmp.fileSync();
|
|
pkgJson = '{name:"test"}';
|
|
|
|
fs.writeSync(tmpFile.fd, pkgJson);
|
|
|
|
parsePackageJson(tmpFile.name)
|
|
.then(function () {
|
|
done(new Error('parsePackageJson succeeded, but should\'ve failed'));
|
|
})
|
|
.catch(function (err) {
|
|
err.message.should.equal('Theme package.json file is malformed');
|
|
err.context.should.equal(tmpFile.name);
|
|
err.help.should.equal('This will be required in future. Please see http://docs.ghost.org/themes/');
|
|
|
|
done();
|
|
})
|
|
.catch(done)
|
|
.finally(tmpFile.removeCallback);
|
|
});
|
|
|
|
it('should fail when file is missing', function (done) {
|
|
var tmpFile = tmp.fileSync();
|
|
|
|
tmpFile.removeCallback();
|
|
parsePackageJson(tmpFile.name)
|
|
.then(function () {
|
|
done(new Error('parsePackageJson succeeded, but should\'ve failed'));
|
|
})
|
|
.catch(function (err) {
|
|
err.message.should.equal('Could not read package.json file');
|
|
err.context.should.equal(tmpFile.name);
|
|
|
|
done();
|
|
})
|
|
.catch(done);
|
|
});
|
|
});
|
|
|
|
describe('read-directory', function () {
|
|
it('should read directory recursively', function (done) {
|
|
var themePath = tmp.dirSync({unsafeCleanup: true});
|
|
|
|
// create example theme
|
|
fs.mkdirSync(join(themePath.name, 'partials'));
|
|
fs.writeFileSync(join(themePath.name, 'index.hbs'));
|
|
fs.writeFileSync(join(themePath.name, 'partials', 'navigation.hbs'));
|
|
|
|
readDirectory(themePath.name)
|
|
.then(function (tree) {
|
|
tree.should.eql({
|
|
partials: {
|
|
'navigation.hbs': join(themePath.name, 'partials', 'navigation.hbs')
|
|
},
|
|
'index.hbs': join(themePath.name, 'index.hbs')
|
|
});
|
|
|
|
done();
|
|
})
|
|
.catch(done)
|
|
.finally(themePath.removeCallback);
|
|
});
|
|
|
|
it('should read directory and ignore unneeded items', function (done) {
|
|
var themePath = tmp.dirSync({unsafeCleanup: true});
|
|
|
|
// create example theme
|
|
fs.mkdirSync(join(themePath.name, 'partials'));
|
|
fs.writeFileSync(join(themePath.name, 'index.hbs'));
|
|
fs.writeFileSync(join(themePath.name, 'partials', 'navigation.hbs'));
|
|
|
|
// create some trash
|
|
fs.mkdirSync(join(themePath.name, 'node_modules'));
|
|
fs.mkdirSync(join(themePath.name, 'bower_components'));
|
|
fs.mkdirSync(join(themePath.name, '.git'));
|
|
fs.writeFileSync(join(themePath.name, '.DS_Store'));
|
|
|
|
readDirectory(themePath.name, {ignore: ['.git']})
|
|
.then(function (tree) {
|
|
tree.should.eql({
|
|
partials: {
|
|
'navigation.hbs': join(themePath.name, 'partials', 'navigation.hbs')
|
|
},
|
|
'index.hbs': join(themePath.name, 'index.hbs')
|
|
});
|
|
|
|
done();
|
|
})
|
|
.catch(done)
|
|
.finally(themePath.removeCallback);
|
|
});
|
|
|
|
it('should read directory and parse package.json files', function (done) {
|
|
var themePath, pkgJson;
|
|
|
|
themePath = tmp.dirSync({unsafeCleanup: true});
|
|
pkgJson = JSON.stringify({
|
|
name: 'test',
|
|
version: '0.0.0'
|
|
});
|
|
|
|
// create example theme
|
|
fs.mkdirSync(join(themePath.name, 'partials'));
|
|
fs.writeFileSync(join(themePath.name, 'package.json'), pkgJson);
|
|
fs.writeFileSync(join(themePath.name, 'index.hbs'));
|
|
fs.writeFileSync(join(themePath.name, 'partials', 'navigation.hbs'));
|
|
|
|
readDirectory(themePath.name)
|
|
.then(function (tree) {
|
|
tree.should.eql({
|
|
partials: {
|
|
'navigation.hbs': join(themePath.name, 'partials', 'navigation.hbs')
|
|
},
|
|
'index.hbs': join(themePath.name, 'index.hbs'),
|
|
'package.json': {
|
|
name: 'test',
|
|
version: '0.0.0'
|
|
}
|
|
});
|
|
|
|
done();
|
|
})
|
|
.catch(done)
|
|
.finally(themePath.removeCallback);
|
|
});
|
|
|
|
it('should read directory and ignore invalid package.json files', function (done) {
|
|
var themePath, pkgJson;
|
|
|
|
themePath = tmp.dirSync({unsafeCleanup: true});
|
|
pkgJson = JSON.stringify({
|
|
name: 'test'
|
|
});
|
|
|
|
// create example theme
|
|
fs.mkdirSync(join(themePath.name, 'partials'));
|
|
fs.writeFileSync(join(themePath.name, 'package.json'), pkgJson);
|
|
fs.writeFileSync(join(themePath.name, 'index.hbs'));
|
|
fs.writeFileSync(join(themePath.name, 'partials', 'navigation.hbs'));
|
|
|
|
readDirectory(themePath.name)
|
|
.then(function (tree) {
|
|
tree.should.eql({
|
|
partials: {
|
|
'navigation.hbs': join(themePath.name, 'partials', 'navigation.hbs')
|
|
},
|
|
'index.hbs': join(themePath.name, 'index.hbs'),
|
|
'package.json': null
|
|
});
|
|
|
|
done();
|
|
})
|
|
.catch(done)
|
|
.finally(themePath.removeCallback);
|
|
});
|
|
});
|
|
|
|
describe('read-themes', function () {
|
|
it('should read directory and include only folders', function (done) {
|
|
var themesPath = tmp.dirSync({unsafeCleanup: true});
|
|
|
|
// create trash
|
|
fs.writeFileSync(join(themesPath.name, 'casper.zip'));
|
|
fs.writeFileSync(join(themesPath.name, '.DS_Store'));
|
|
|
|
// create actual theme
|
|
fs.mkdirSync(join(themesPath.name, 'casper'));
|
|
fs.mkdirSync(join(themesPath.name, 'casper', 'partials'));
|
|
fs.writeFileSync(join(themesPath.name, 'casper', 'index.hbs'));
|
|
fs.writeFileSync(join(themesPath.name, 'casper', 'partials', 'navigation.hbs'));
|
|
|
|
readThemes(themesPath.name)
|
|
.then(function (tree) {
|
|
tree.should.eql({
|
|
casper: {
|
|
partials: {
|
|
'navigation.hbs': join(themesPath.name, 'casper', 'partials', 'navigation.hbs')
|
|
},
|
|
'index.hbs': join(themesPath.name, 'casper', 'index.hbs')
|
|
}
|
|
});
|
|
|
|
done();
|
|
})
|
|
.catch(done)
|
|
.finally(themesPath.removeCallback);
|
|
});
|
|
});
|
|
|
|
describe('gravatar-lookup', function () {
|
|
beforeEach(function () {
|
|
configUtils.set('env', 'production');
|
|
});
|
|
|
|
afterEach(function () {
|
|
configUtils.restore();
|
|
});
|
|
|
|
it('can successfully lookup a gravatar url', function (done) {
|
|
nock('https://www.gravatar.com')
|
|
.get('/avatar/ef6dcde5c99bb8f685dd451ccc3e050a?s=250&d=404&r=x')
|
|
.reply(200);
|
|
|
|
gravatar.lookup({email: 'exists@example.com'}).then(function (result) {
|
|
should.exist(result);
|
|
should.exist(result.image);
|
|
result.image.should.eql('//www.gravatar.com/avatar/ef6dcde5c99bb8f685dd451ccc3e050a?s=250&d=mm&r=x');
|
|
|
|
done();
|
|
}).catch(done);
|
|
});
|
|
|
|
it('can handle a non existant gravatar', function (done) {
|
|
nock('https://www.gravatar.com')
|
|
.get('/avatar/3a2963a39ebba98fb0724a1db2f13d63?s=250&d=404&r=x')
|
|
.reply(404);
|
|
|
|
gravatar.lookup({email: 'invalid@example.com'}).then(function (result) {
|
|
should.exist(result);
|
|
should.not.exist(result.image);
|
|
|
|
done();
|
|
}).catch(done);
|
|
});
|
|
|
|
it('will timeout', function (done) {
|
|
nock('https://www.gravatar.com')
|
|
.get('/avatar/ef6dcde5c99bb8f685dd451ccc3e050a?s=250&d=404&r=x')
|
|
.delay(11)
|
|
.reply(200);
|
|
|
|
gravatar.lookup({email: 'exists@example.com'}, 10).then(function (result) {
|
|
should.not.exist(result);
|
|
done();
|
|
}).catch(done);
|
|
});
|
|
});
|
|
|
|
describe('redirect301', function () {
|
|
it('performs a 301 correctly', function (done) {
|
|
var res = {};
|
|
|
|
res.set = sinon.spy();
|
|
|
|
res.redirect = function (code, path) {
|
|
code.should.equal(301);
|
|
path.should.eql('my/awesome/path');
|
|
res.set.calledWith({'Cache-Control': 'public, max-age=' + utils.ONE_YEAR_S}).should.be.true();
|
|
|
|
done();
|
|
};
|
|
|
|
utils.redirect301(res, 'my/awesome/path');
|
|
});
|
|
});
|
|
});
|