mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-01 13:54:35 +03:00
Load yaml settings files synchronously
refs https://github.com/TryGhost/Team/issues/65 - it's easier for the architecture if we read the setting files synchronously, because the dynamic routing component is part of the express bootstrap and the whole routing bootstrap is synchronously - for now: we only read one file anyway - it's for now easier to read the file synchronously, then i don't have to change any existing express bootstrap architecture
This commit is contained in:
parent
e43bdad818
commit
0ac19dcf84
@ -1,13 +1,14 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Settings Lib
|
||||
* A collection of utilities for handling settings including a cache
|
||||
*/
|
||||
|
||||
var SettingsModel = require('../../models/settings').Settings,
|
||||
const _ = require('lodash'),
|
||||
SettingsModel = require('../../models/settings').Settings,
|
||||
SettingsCache = require('./cache'),
|
||||
SettingsLoader = require('./loader'),
|
||||
// EnsureSettingsFiles = require('./ensure-settings'),
|
||||
_ = require('lodash'),
|
||||
common = require('../../lib/common'),
|
||||
debug = require('ghost-ignition').debug('services:settings:index');
|
||||
|
||||
@ -49,7 +50,7 @@ module.exports = {
|
||||
* will return an Object like this:
|
||||
* {routes: {}, collections: {}, resources: {}}
|
||||
* @param {String} setting type of supported setting.
|
||||
* @returns {Promise<Object>} settingsFile
|
||||
* @returns {Object} settingsFile
|
||||
* @description Returns settings object as defined per YAML files in
|
||||
* `/content/settings` directory.
|
||||
*/
|
||||
@ -59,18 +60,13 @@ module.exports = {
|
||||
// CASE: this should be an edge case and only if internal usage of the
|
||||
// getter is incorrect.
|
||||
if (!setting || _.indexOf(knownSettings, setting) < 0) {
|
||||
return Promise.reject(new common.errors.IncorrectUsageError({
|
||||
throw new common.errors.IncorrectUsageError({
|
||||
message: `Requested setting is not supported: '${setting}'.`,
|
||||
help: `Please use only the supported settings: ${knownSettings}.`
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
return SettingsLoader(setting)
|
||||
.then((settingsFile) => {
|
||||
debug('setting loaded and parsed:', settingsFile);
|
||||
|
||||
return settingsFile;
|
||||
});
|
||||
return SettingsLoader(setting);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -88,23 +84,18 @@ module.exports = {
|
||||
* config: { url: 'testblog.com' }
|
||||
* }
|
||||
* }
|
||||
* @returns {Promise<Object>} settingsObject
|
||||
* @returns {Object} settingsObject
|
||||
* @description Returns all settings object as defined per YAML files in
|
||||
* `/content/settings` directory.
|
||||
*/
|
||||
getAll: function getAll() {
|
||||
const knownSettings = this.knownSettings(),
|
||||
props = {};
|
||||
settingsToReturn = {};
|
||||
|
||||
_.each(knownSettings, function (setting) {
|
||||
props[setting] = SettingsLoader(setting);
|
||||
settingsToReturn[setting] = SettingsLoader(setting);
|
||||
});
|
||||
|
||||
return Promise.props(props)
|
||||
.then((settingsFile) => {
|
||||
debug('all settings loaded and parsed:', settingsFile);
|
||||
|
||||
return settingsFile;
|
||||
});
|
||||
return settingsToReturn;
|
||||
}
|
||||
};
|
||||
|
@ -11,7 +11,7 @@ const fs = require('fs-extra'),
|
||||
* Reads the desired settings YAML file and passes the
|
||||
* file to the YAML parser which then returns a JSON object.
|
||||
* @param {String} setting the requested settings as defined in setting knownSettings
|
||||
* @returns {Promise<Object>} settingsFile
|
||||
* @returns {Object} settingsFile
|
||||
*/
|
||||
module.exports = function loadSettings(setting) {
|
||||
// we only support the `yaml` file extension. `yml` will be ignored.
|
||||
@ -19,23 +19,21 @@ module.exports = function loadSettings(setting) {
|
||||
const contentPath = config.getContentPath('settings');
|
||||
const filePath = path.join(contentPath, fileName);
|
||||
|
||||
return fs.readFile(filePath, 'utf8')
|
||||
.then((file) => {
|
||||
debug('settings file found for', setting);
|
||||
try {
|
||||
const file = fs.readFileSync(filePath, 'utf8');
|
||||
debug('settings file found for', setting);
|
||||
|
||||
// yamlParser returns a JSON object
|
||||
const parsed = yamlParser(file, fileName);
|
||||
// yamlParser returns a JSON object
|
||||
return yamlParser(file, fileName);
|
||||
} catch (err) {
|
||||
if (common.errors.utils.isIgnitionError(err)) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
return parsed;
|
||||
}).catch((error) => {
|
||||
if (common.errors.utils.isIgnitionError(error)) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
throw new common.errors.GhostError({
|
||||
message: common.i18n.t('errors.services.settings.loader', {setting: setting, path: contentPath}),
|
||||
context: filePath,
|
||||
err: error
|
||||
});
|
||||
throw new common.errors.GhostError({
|
||||
message: common.i18n.t('errors.services.settings.loader', {setting: setting, path: contentPath}),
|
||||
context: filePath,
|
||||
err: err
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -4,13 +4,10 @@ const sinon = require('sinon'),
|
||||
should = require('should'),
|
||||
rewire = require('rewire'),
|
||||
fs = require('fs-extra'),
|
||||
yaml = require('js-yaml'),
|
||||
path = require('path'),
|
||||
configUtils = require('../../../utils/configUtils'),
|
||||
common = require('../../../../server/lib/common'),
|
||||
|
||||
loadSettings = rewire('../../../../server/services/settings/loader'),
|
||||
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
describe('UNIT > Settings Service:', function () {
|
||||
@ -41,54 +38,67 @@ describe('UNIT > Settings Service:', function () {
|
||||
});
|
||||
|
||||
it('can find yaml settings file and returns a settings object', function () {
|
||||
const fsReadFileSpy = sandbox.spy(fs, 'readFile');
|
||||
const fsReadFileSpy = sandbox.spy(fs, 'readFileSync');
|
||||
const expectedSettingsFile = path.join(__dirname, '../../../utils/fixtures/settings/goodroutes.yaml');
|
||||
|
||||
yamlParserStub.returns(yamlStubFile);
|
||||
loadSettings.__set__('yamlParser', yamlParserStub);
|
||||
|
||||
return loadSettings('goodroutes').then((setting) => {
|
||||
should.exist(setting);
|
||||
setting.should.be.an.Object().with.properties('routes', 'collections', 'resources');
|
||||
// There are 4 files in the fixtures folder, but only 1 supported and valid yaml files
|
||||
fsReadFileSpy.calledOnce.should.be.true();
|
||||
fsReadFileSpy.calledWith(expectedSettingsFile).should.be.true();
|
||||
yamlParserStub.callCount.should.be.eql(1);
|
||||
});
|
||||
const setting = loadSettings('goodroutes');
|
||||
should.exist(setting);
|
||||
setting.should.be.an.Object().with.properties('routes', 'collections', 'resources');
|
||||
// There are 4 files in the fixtures folder, but only 1 supported and valid yaml files
|
||||
fsReadFileSpy.calledOnce.should.be.true();
|
||||
fsReadFileSpy.calledWith(expectedSettingsFile).should.be.true();
|
||||
yamlParserStub.callCount.should.be.eql(1);
|
||||
});
|
||||
|
||||
it('can handle errors from YAML parser', function () {
|
||||
yamlParserStub.rejects(new common.errors.GhostError({
|
||||
it('can handle errors from YAML parser', function (done) {
|
||||
yamlParserStub.throws(new common.errors.GhostError({
|
||||
message: 'could not parse yaml file',
|
||||
context: 'bad indentation of a mapping entry at line 5, column 10'
|
||||
}));
|
||||
|
||||
loadSettings.__set__('yamlParser', yamlParserStub);
|
||||
|
||||
return loadSettings('goodroutes').then((setting) => {
|
||||
should.not.exist(setting);
|
||||
}).catch((error) => {
|
||||
should.exist(error);
|
||||
error.message.should.be.eql('could not parse yaml file');
|
||||
error.context.should.be.eql('bad indentation of a mapping entry at line 5, column 10');
|
||||
try {
|
||||
loadSettings('goodroutes');
|
||||
done(new Error('Loader should fail'));
|
||||
} catch (err) {
|
||||
should.exist(err);
|
||||
err.message.should.be.eql('could not parse yaml file');
|
||||
err.context.should.be.eql('bad indentation of a mapping entry at line 5, column 10');
|
||||
yamlParserStub.calledOnce.should.be.true();
|
||||
});
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('throws error if file can\'t be accessed', function () {
|
||||
it('throws error if file can\'t be accessed', function (done) {
|
||||
const expectedSettingsFile = path.join(__dirname, '../../../utils/fixtures/settings/routes.yaml');
|
||||
const fsError = new Error('no permission');
|
||||
fsError.code = 'EPERM';
|
||||
const fsReadFileStub = sandbox.stub(fs, 'readFile').rejects(fsError);
|
||||
|
||||
const originalFn = fs.readFileSync;
|
||||
const fsReadFileStub = sandbox.stub(fs, 'readFileSync').callsFake(function (filePath, options) {
|
||||
if (filePath.match(/routes\.yaml/)) {
|
||||
throw fsError;
|
||||
}
|
||||
|
||||
return originalFn(filePath, options);
|
||||
});
|
||||
|
||||
yamlParserStub = sinon.spy();
|
||||
loadSettings.__set__('yamlParser', yamlParserStub);
|
||||
|
||||
return loadSettings('routes').then((settings) => {
|
||||
should.not.exist(settings);
|
||||
}).catch((error) => {
|
||||
should.exist(error);
|
||||
error.message.should.match(/Error trying to load YAML setting for routes from/);
|
||||
try {
|
||||
loadSettings('routes');
|
||||
done(new Error('Loader should fail'));
|
||||
} catch (err) {
|
||||
err.message.should.match(/Error trying to load YAML setting for routes from/);
|
||||
fsReadFileStub.calledWith(expectedSettingsFile).should.be.true();
|
||||
yamlParserStub.calledOnce.should.be.false();
|
||||
});
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -4,9 +4,7 @@ const sinon = require('sinon'),
|
||||
should = require('should'),
|
||||
rewire = require('rewire'),
|
||||
common = require('../../../../server/lib/common'),
|
||||
|
||||
settings = rewire('../../../../server/services/settings/index'),
|
||||
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
describe('UNIT > Settings Service:', function () {
|
||||
@ -42,40 +40,43 @@ describe('UNIT > Settings Service:', function () {
|
||||
});
|
||||
|
||||
it('returns settings object for `routes`', function () {
|
||||
settingsLoaderStub.resolves(settingsStubFile);
|
||||
settingsLoaderStub.returns(settingsStubFile);
|
||||
settings.__set__('SettingsLoader', settingsLoaderStub);
|
||||
|
||||
settings.get('routes').then((result) => {
|
||||
should.exist(result);
|
||||
result.should.be.an.Object().with.properties('routes', 'collections', 'resources');
|
||||
settingsLoaderStub.calledOnce.should.be.true();
|
||||
});
|
||||
const result = settings.get('routes');
|
||||
should.exist(result);
|
||||
result.should.be.an.Object().with.properties('routes', 'collections', 'resources');
|
||||
settingsLoaderStub.calledOnce.should.be.true();
|
||||
});
|
||||
|
||||
it('rejects when requested settings type is not supported', function () {
|
||||
settingsLoaderStub.resolves(settingsStubFile);
|
||||
it('rejects when requested settings type is not supported', function (done) {
|
||||
settingsLoaderStub.returns(settingsStubFile);
|
||||
settings.__set__('SettingsLoader', settingsLoaderStub);
|
||||
|
||||
return settings.get('something').then((result) => {
|
||||
should.not.exist(result);
|
||||
}).catch((error) => {
|
||||
should.exist(error);
|
||||
error.message.should.be.eql('Requested setting is not supported: \'something\'.');
|
||||
try {
|
||||
settings.get('something');
|
||||
done(new Error('SettingsLoader should fail'));
|
||||
} catch (err) {
|
||||
should.exist(err);
|
||||
err.message.should.be.eql('Requested setting is not supported: \'something\'.');
|
||||
settingsLoaderStub.callCount.should.be.eql(0);
|
||||
});
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('passes SettingsLoader error through', function () {
|
||||
settingsLoaderStub.rejects(new common.errors.GhostError({message: 'oops'}));
|
||||
it('passes SettingsLoader error through', function (done) {
|
||||
settingsLoaderStub.throws(new common.errors.GhostError({message: 'oops'}));
|
||||
settings.__set__('SettingsLoader', settingsLoaderStub);
|
||||
|
||||
return settings.get('routes').then((result) => {
|
||||
should.not.exist(result);
|
||||
}).catch((error) => {
|
||||
should.exist(error);
|
||||
error.message.should.be.eql('oops');
|
||||
try {
|
||||
settings.get('routes');
|
||||
done(new Error('SettingsLoader should fail'));
|
||||
} catch (err) {
|
||||
should.exist(err);
|
||||
err.message.should.be.eql('oops');
|
||||
settingsLoaderStub.calledOnce.should.be.true();
|
||||
});
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@ -106,32 +107,33 @@ describe('UNIT > Settings Service:', function () {
|
||||
});
|
||||
|
||||
it('returns settings object for all known settings', function () {
|
||||
settingsLoaderStub.onFirstCall().resolves(settingsStubFile1);
|
||||
settingsLoaderStub.onSecondCall().resolves(settingsStubFile2);
|
||||
settingsLoaderStub.onFirstCall().returns(settingsStubFile1);
|
||||
settingsLoaderStub.onSecondCall().returns(settingsStubFile2);
|
||||
settings.__set__('SettingsLoader', settingsLoaderStub);
|
||||
|
||||
return settings.getAll().then((result) => {
|
||||
should.exist(result);
|
||||
result.should.be.an.Object().with.properties('routes', 'globals');
|
||||
result.routes.should.be.an.Object().with.properties('routes', 'collections', 'resources');
|
||||
result.globals.should.be.an.Object().with.properties('config');
|
||||
const result = settings.getAll();
|
||||
should.exist(result);
|
||||
result.should.be.an.Object().with.properties('routes', 'globals');
|
||||
result.routes.should.be.an.Object().with.properties('routes', 'collections', 'resources');
|
||||
result.globals.should.be.an.Object().with.properties('config');
|
||||
|
||||
settingsLoaderStub.calledTwice.should.be.true();
|
||||
});
|
||||
settingsLoaderStub.calledTwice.should.be.true();
|
||||
});
|
||||
|
||||
it('passes SettinsLoader error through', function () {
|
||||
settingsLoaderStub.onFirstCall().resolves(settingsStubFile1);
|
||||
settingsLoaderStub.onSecondCall().rejects(new common.errors.GhostError({message: 'oops'}));
|
||||
it('passes SettinsLoader error through', function (done) {
|
||||
settingsLoaderStub.onFirstCall().returns(settingsStubFile1);
|
||||
settingsLoaderStub.onSecondCall().throws(new common.errors.GhostError({message: 'oops'}));
|
||||
settings.__set__('SettingsLoader', settingsLoaderStub);
|
||||
|
||||
return settings.getAll().then((result) => {
|
||||
should.not.exist(result);
|
||||
}).catch((error) => {
|
||||
should.exist(error);
|
||||
error.message.should.be.eql('oops');
|
||||
try {
|
||||
settings.getAll();
|
||||
done(new Error('SettingsLoader should fail'));
|
||||
} catch (err) {
|
||||
should.exist(err);
|
||||
err.message.should.be.eql('oops');
|
||||
settingsLoaderStub.calledTwice.should.be.true();
|
||||
});
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user