diff --git a/core/server/services/themes/validate.js b/core/server/services/themes/validate.js index e4d9d452f6ee..fe46ce90dd62 100644 --- a/core/server/services/themes/validate.js +++ b/core/server/services/themes/validate.js @@ -16,24 +16,27 @@ checkTheme = function checkTheme(theme, isZip) { checkPromise = gscan.check(theme.path); } - return checkPromise.then(function resultHandler(checkedTheme) { - checkedTheme = gscan.format(checkedTheme, { - onlyFatalErrors: config.get('env') === 'production' - }); + return checkPromise + .then(function resultHandler(checkedTheme) { + checkedTheme = gscan.format(checkedTheme, { + onlyFatalErrors: config.get('env') === 'production' + }); - // CASE: production and no fatal errors - // CASE: development returns fatal and none fatal errors, theme is only invalid if fatal errors - if (!checkedTheme.results.error.length || - config.get('env') === 'development' && !checkedTheme.results.hasFatalErrors) { - return checkedTheme; - } + // CASE: production and no fatal errors + // CASE: development returns fatal and none fatal errors, theme is only invalid if fatal errors + if (!checkedTheme.results.error.length || + config.get('env') === 'development' && !checkedTheme.results.hasFatalErrors) { + return checkedTheme; + } - return Promise.reject(new common.errors.ThemeValidationError({ - message: common.i18n.t('errors.api.themes.invalidTheme'), - errorDetails: checkedTheme.results.error, - context: checkedTheme - })); - }); + return Promise.reject(new common.errors.ThemeValidationError({ + message: common.i18n.t('errors.api.themes.invalidTheme'), + errorDetails: checkedTheme.results.error, + context: checkedTheme + })); + }).catch(function (error) { + return Promise.reject(error); + }); }; module.exports.check = checkTheme; diff --git a/core/test/unit/services/themes/validate_spec.js b/core/test/unit/services/themes/validate_spec.js new file mode 100644 index 000000000000..d06e603647b1 --- /dev/null +++ b/core/test/unit/services/themes/validate_spec.js @@ -0,0 +1,141 @@ +'use strict'; + +const should = require('should'); +const sinon = require('sinon'); +const _ = require('lodash'); + +const themes = require('../../../../server/services/themes'); +const validate = themes.validate; + +const gscan = require('gscan'); +const common = require('../../../../server/lib/common'); +const sandbox = sinon.sandbox.create(); + +describe('Themes', function () { + let checkZipStub; + let checkStub; + let formatStub; + + beforeEach(function () { + checkZipStub = sandbox.stub(gscan, 'checkZip'); + checkStub = sandbox.stub(gscan, 'check'); + formatStub = sandbox.stub(gscan, 'format'); + }); + + afterEach(function () { + sandbox.restore(); + }); + + describe('Validate', function () { + const testTheme = { + name: 'supertheme', + version: '1.0.0', + path: '/path/to/theme' + }; + + it('[success] validates a valid zipped theme', function () { + checkZipStub.resolves({}); + formatStub.returns({results: {error: []}}); + + return validate.check(testTheme, true) + .then((checkedTheme) => { + checkZipStub.calledOnce.should.be.true(); + checkZipStub.calledWith(testTheme).should.be.true(); + checkStub.callCount.should.be.equal(0); + formatStub.calledOnce.should.be.true(); + checkedTheme.should.be.an.Object(); + }); + }); + + it('[success] validates a valid unzipped theme', function () { + checkStub.resolves({}); + formatStub.returns({results: {error: []}}); + + return validate.check(testTheme, false) + .then((checkedTheme) => { + checkZipStub.callCount.should.be.equal(0); + checkStub.calledOnce.should.be.true(); + checkStub.calledWith(testTheme.path).should.be.true(); + formatStub.calledOnce.should.be.true(); + checkedTheme.should.be.an.Object(); + }); + }); + + it('[failure] validates an invalid zipped theme', function () { + checkZipStub.resolves({}); + formatStub.returns({ + results: { + error: [ + { + fatal: true, + level: 'error', + rule: 'Replace the {{#if author.cover}} helper with {{#if author.cover_image}}', + details: 'The cover attribute was replaced with cover_image.
Instead of {{#if author.cover}} you need to use {{#if author.cover_image}}.
See the object attributes of author here.', + failures: [ {} ], + code: 'GS001-DEPR-CON-AC' + } + ] + } + }); + + return validate.check(testTheme, true) + .then((checkedTheme) => { + checkedTheme.should.not.exist(); + }).catch((error) => { + error.should.be.an.Object(); + (error instanceof common.errors.ThemeValidationError).should.eql(true); + checkZipStub.calledOnce.should.be.true(); + checkZipStub.calledWith(testTheme).should.be.true(); + checkStub.callCount.should.be.equal(0); + formatStub.calledOnce.should.be.true(); + }); + }); + + it('[failure] validates an invalid unzipped theme', function () { + checkStub.resolves({}); + formatStub.returns({ + results: { + error: [ + { + fatal: true, + level: 'error', + rule: 'Replace the {{#if author.cover}} helper with {{#if author.cover_image}}', + details: 'The cover attribute was replaced with cover_image.
Instead of {{#if author.cover}} you need to use {{#if author.cover_image}}.
See the object attributes of author here.', + failures: [ {} ], + code: 'GS001-DEPR-CON-AC' + } + ] + } + }); + + return validate.check(testTheme, false) + .then((checkedTheme) => { + checkedTheme.should.not.exist(); + }).catch((error) => { + error.should.be.an.Object(); + (error instanceof common.errors.ThemeValidationError).should.eql(true); + checkStub.calledOnce.should.be.true(); + checkStub.calledWith(testTheme.path).should.be.true(); + checkZipStub.callCount.should.be.equal(0); + formatStub.calledOnce.should.be.true(); + }); + }); + + it('[failure] can handle a corrupt zip file', function () { + checkZipStub.rejects(new Error('invalid zip file')); + formatStub.returns({results: {error: []}}); + + return validate.check(testTheme, true) + .then((checkedTheme) => { + checkedTheme.should.not.exist(); + }).catch((error) => { + error.should.be.an.Object(); + error.message.should.be.equal('invalid zip file'); + checkZipStub.calledOnce.should.be.true(); + checkZipStub.calledWith(testTheme).should.be.true(); + checkStub.callCount.should.be.equal(0); + formatStub.calledOnce.should.be.false(); + }); + }); + }); +});