8687772604
refs #5940, #5923 - make read-directory ignore invalid package.json files - display a warning about invalid package.json files on startup - add tests to ensure read-directory continues, even with invalid package.json files
447 lines
16 KiB
JavaScript
447 lines
16 KiB
JavaScript
/*globals describe, it*/
|
|
/*jshint expr:true*/
|
|
var should = require('should'),
|
|
parsePackageJson = require('../../server/utils/parse-package-json'),
|
|
validateThemes = require('../../server/utils/validate-themes'),
|
|
readDirectory = require('../../server/utils/read-directory'),
|
|
readThemes = require('../../server/utils/read-themes'),
|
|
tempfile = require('../utils/tempfile'),
|
|
utils = require('../../server/utils'),
|
|
join = require('path').join,
|
|
rm = require('rimraf-then'),
|
|
fs = require('fs');
|
|
|
|
// 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', 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');
|
|
});
|
|
|
|
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, tmpPath;
|
|
|
|
tmpPath = tempfile();
|
|
pkgJson = JSON.stringify({
|
|
name: 'test',
|
|
version: '0.0.0'
|
|
});
|
|
|
|
fs.writeFileSync(tmpPath, pkgJson);
|
|
|
|
parsePackageJson(tmpPath)
|
|
.then(function (pkg) {
|
|
pkg.should.eql({
|
|
name: 'test',
|
|
version: '0.0.0'
|
|
});
|
|
|
|
done();
|
|
})
|
|
.catch(done)
|
|
.finally(function () {
|
|
return rm(tmpPath);
|
|
});
|
|
});
|
|
|
|
it('should fail when name is missing', function (done) {
|
|
var pkgJson, tmpPath;
|
|
|
|
tmpPath = tempfile();
|
|
pkgJson = JSON.stringify({
|
|
version: '0.0.0'
|
|
});
|
|
|
|
fs.writeFileSync(tmpPath, pkgJson);
|
|
|
|
parsePackageJson(tmpPath)
|
|
.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(tmpPath);
|
|
err.help.should.equal('This will be required in future. Please see http://docs.ghost.org/themes/');
|
|
|
|
done();
|
|
})
|
|
.finally(function () {
|
|
return rm(tmpPath);
|
|
});
|
|
});
|
|
|
|
it('should fail when version is missing', function (done) {
|
|
var pkgJson, tmpPath;
|
|
|
|
tmpPath = tempfile();
|
|
pkgJson = JSON.stringify({
|
|
name: 'test'
|
|
});
|
|
|
|
fs.writeFileSync(tmpPath, pkgJson);
|
|
|
|
parsePackageJson(tmpPath)
|
|
.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(tmpPath);
|
|
err.help.should.equal('This will be required in future. Please see http://docs.ghost.org/themes/');
|
|
|
|
done();
|
|
})
|
|
.finally(function () {
|
|
return rm(tmpPath);
|
|
});
|
|
});
|
|
|
|
it('should fail when JSON is invalid', function (done) {
|
|
var pkgJson, tmpPath;
|
|
|
|
tmpPath = tempfile();
|
|
pkgJson = '{name:"test"}';
|
|
|
|
fs.writeFileSync(tmpPath, pkgJson);
|
|
|
|
parsePackageJson(tmpPath)
|
|
.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(tmpPath);
|
|
err.help.should.equal('This will be required in future. Please see http://docs.ghost.org/themes/');
|
|
|
|
done();
|
|
})
|
|
.finally(function () {
|
|
return rm(tmpPath);
|
|
});
|
|
});
|
|
|
|
it('should fail when file is missing', function (done) {
|
|
var tmpPath = tempfile();
|
|
|
|
parsePackageJson(tmpPath)
|
|
.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(tmpPath);
|
|
|
|
done();
|
|
})
|
|
.finally(function () {
|
|
return rm(tmpPath);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('read-directory', function () {
|
|
it('should read directory recursively', function (done) {
|
|
var themePath = tempfile();
|
|
|
|
// create example theme
|
|
fs.mkdirSync(themePath);
|
|
fs.mkdirSync(join(themePath, 'partials'));
|
|
fs.writeFileSync(join(themePath, 'index.hbs'));
|
|
fs.writeFileSync(join(themePath, 'partials', 'navigation.hbs'));
|
|
|
|
readDirectory(themePath)
|
|
.then(function (tree) {
|
|
tree.should.eql({
|
|
partials: {
|
|
'navigation.hbs': join(themePath, 'partials', 'navigation.hbs')
|
|
},
|
|
'index.hbs': join(themePath, 'index.hbs')
|
|
});
|
|
|
|
done();
|
|
})
|
|
.catch(done)
|
|
.finally(function () {
|
|
return rm(themePath);
|
|
});
|
|
});
|
|
|
|
it('should read directory and ignore unneeded items', function (done) {
|
|
var themePath = tempfile();
|
|
|
|
// create example theme
|
|
fs.mkdirSync(themePath);
|
|
fs.mkdirSync(join(themePath, 'partials'));
|
|
fs.writeFileSync(join(themePath, 'index.hbs'));
|
|
fs.writeFileSync(join(themePath, 'partials', 'navigation.hbs'));
|
|
|
|
// create some trash
|
|
fs.mkdirSync(join(themePath, 'node_modules'));
|
|
fs.mkdirSync(join(themePath, 'bower_components'));
|
|
fs.mkdirSync(join(themePath, '.git'));
|
|
fs.writeFileSync(join(themePath, '.DS_Store'));
|
|
|
|
readDirectory(themePath, {ignore: ['.git']})
|
|
.then(function (tree) {
|
|
tree.should.eql({
|
|
partials: {
|
|
'navigation.hbs': join(themePath, 'partials', 'navigation.hbs')
|
|
},
|
|
'index.hbs': join(themePath, 'index.hbs')
|
|
});
|
|
|
|
done();
|
|
})
|
|
.catch(done)
|
|
.finally(function () {
|
|
return rm(themePath);
|
|
});
|
|
});
|
|
|
|
it('should read directory and parse package.json files', function (done) {
|
|
var themePath, pkgJson;
|
|
|
|
themePath = tempfile();
|
|
pkgJson = JSON.stringify({
|
|
name: 'test',
|
|
version: '0.0.0'
|
|
});
|
|
|
|
// create example theme
|
|
fs.mkdirSync(themePath);
|
|
fs.mkdirSync(join(themePath, 'partials'));
|
|
fs.writeFileSync(join(themePath, 'package.json'), pkgJson);
|
|
fs.writeFileSync(join(themePath, 'index.hbs'));
|
|
fs.writeFileSync(join(themePath, 'partials', 'navigation.hbs'));
|
|
|
|
readDirectory(themePath)
|
|
.then(function (tree) {
|
|
tree.should.eql({
|
|
partials: {
|
|
'navigation.hbs': join(themePath, 'partials', 'navigation.hbs')
|
|
},
|
|
'index.hbs': join(themePath, 'index.hbs'),
|
|
'package.json': {
|
|
name: 'test',
|
|
version: '0.0.0'
|
|
}
|
|
});
|
|
|
|
done();
|
|
})
|
|
.catch(done)
|
|
.finally(function () {
|
|
return rm(themePath);
|
|
});
|
|
});
|
|
|
|
it('should read directory and ignore invalid package.json files', function (done) {
|
|
var themePath, pkgJson;
|
|
|
|
themePath = tempfile();
|
|
pkgJson = JSON.stringify({
|
|
name: 'test'
|
|
});
|
|
|
|
// create example theme
|
|
fs.mkdirSync(themePath);
|
|
fs.mkdirSync(join(themePath, 'partials'));
|
|
fs.writeFileSync(join(themePath, 'package.json'), pkgJson);
|
|
fs.writeFileSync(join(themePath, 'index.hbs'));
|
|
fs.writeFileSync(join(themePath, 'partials', 'navigation.hbs'));
|
|
|
|
readDirectory(themePath)
|
|
.then(function (tree) {
|
|
tree.should.eql({
|
|
partials: {
|
|
'navigation.hbs': join(themePath, 'partials', 'navigation.hbs')
|
|
},
|
|
'index.hbs': join(themePath, 'index.hbs'),
|
|
'package.json': null
|
|
});
|
|
|
|
done();
|
|
})
|
|
.catch(done)
|
|
.finally(function () {
|
|
return rm(themePath);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('read-themes', function () {
|
|
it('should read directory and include only folders', function (done) {
|
|
var themesPath = tempfile();
|
|
|
|
fs.mkdirSync(themesPath);
|
|
|
|
// create trash
|
|
fs.writeFileSync(join(themesPath, 'casper.zip'));
|
|
fs.writeFileSync(join(themesPath, '.DS_Store'));
|
|
|
|
// create actual theme
|
|
fs.mkdirSync(join(themesPath, 'casper'));
|
|
fs.mkdirSync(join(themesPath, 'casper', 'partials'));
|
|
fs.writeFileSync(join(themesPath, 'casper', 'index.hbs'));
|
|
fs.writeFileSync(join(themesPath, 'casper', 'partials', 'navigation.hbs'));
|
|
|
|
readThemes(themesPath)
|
|
.then(function (tree) {
|
|
tree.should.eql({
|
|
casper: {
|
|
partials: {
|
|
'navigation.hbs': join(themesPath, 'casper', 'partials', 'navigation.hbs')
|
|
},
|
|
'index.hbs': join(themesPath, 'casper', 'index.hbs')
|
|
}
|
|
});
|
|
|
|
done();
|
|
})
|
|
.catch(done)
|
|
.finally(function () {
|
|
return rm(themesPath);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('validate-themes', function () {
|
|
it('should return warnings for themes without package.json', function (done) {
|
|
var themesPath, pkgJson;
|
|
|
|
themesPath = tempfile();
|
|
pkgJson = JSON.stringify({
|
|
name: 'casper',
|
|
version: '1.0.0'
|
|
});
|
|
|
|
fs.mkdirSync(themesPath);
|
|
|
|
fs.mkdirSync(join(themesPath, 'casper'));
|
|
fs.mkdirSync(join(themesPath, 'invalid-casper'));
|
|
|
|
fs.writeFileSync(join(themesPath, 'casper', 'package.json'), pkgJson);
|
|
|
|
validateThemes(themesPath)
|
|
.then(function () {
|
|
done(new Error('validateThemes succeeded, but should\'ve failed'));
|
|
})
|
|
.catch(function (result) {
|
|
result.errors.length.should.equal(0);
|
|
result.warnings.should.eql([{
|
|
message: 'Found a theme with no package.json file',
|
|
context: 'Theme name: invalid-casper',
|
|
help: 'This will be required in future. Please see http://docs.ghost.org/themes/'
|
|
}]);
|
|
|
|
done();
|
|
})
|
|
.finally(function () {
|
|
return rm(themesPath);
|
|
});
|
|
});
|
|
|
|
it('should return warning for theme with invalid package.json', function (done) {
|
|
var themesPath, pkgJson;
|
|
|
|
themesPath = tempfile();
|
|
pkgJson = '{"name":casper}';
|
|
|
|
fs.mkdirSync(themesPath);
|
|
|
|
fs.mkdirSync(join(themesPath, 'casper'));
|
|
fs.writeFileSync(join(themesPath, 'casper', 'package.json'), pkgJson);
|
|
|
|
validateThemes(themesPath)
|
|
.then(function () {
|
|
done(new Error('validateThemes succeeded, but should\'ve failed'));
|
|
})
|
|
.catch(function (result) {
|
|
result.errors.length.should.equal(0);
|
|
result.warnings.should.eql([{
|
|
message: 'Found a malformed package.json',
|
|
context: 'Theme name: casper',
|
|
help: 'Valid package.json will be required in future. Please see http://docs.ghost.org/themes/'
|
|
}]);
|
|
|
|
done();
|
|
})
|
|
.finally(function () {
|
|
return rm(themesPath);
|
|
});
|
|
});
|
|
});
|
|
});
|