Ghost/core/test/utils/api.js

189 lines
6.5 KiB
JavaScript
Raw Normal View History

var _ = require('lodash'),
url = require('url'),
moment = require('moment'),
DataGenerator = require('./fixtures/data-generator'),
config = require('../../server/config'),
common = require('../../server/lib/common'),
sequence = require('../../server/lib/promise/sequence'),
schema = require('../../server/data/schema').tables,
host = config.get('server').host,
port = config.get('server').port,
protocol = 'http://',
expectedProperties = {
// API top level
posts: ['posts', 'meta'],
tags: ['tags', 'meta'],
users: ['users', 'meta'],
settings: ['settings', 'meta'],
subscribers: ['subscribers', 'meta'],
roles: ['roles'],
pagination: ['page', 'limit', 'pages', 'total', 'next', 'prev'],
slugs: ['slugs'],
slug: ['slug'],
post: _(schema.posts)
.keys()
// by default we only return html
.without('mobiledoc', 'plaintext')
✨ Multiple authors (#9426) no issue This PR adds the server side logic for multiple authors. This adds the ability to add multiple authors per post. We keep and support single authors (maybe till the next major - this is still in discussion) ### key notes - `authors` are not fetched by default, only if we need them - the migration script iterates over all posts and figures out if an author_id is valid and exists (in master we can add invalid author_id's) and then adds the relation (falls back to owner if invalid) - ~~i had to push a fork of bookshelf to npm because we currently can't bump bookshelf + the two bugs i discovered are anyway not yet merged (https://github.com/kirrg001/bookshelf/commits/master)~~ replaced by new bookshelf release - the implementation of single & multiple authors lives in a single place (introduction of a new concept: model relation) - if you destroy an author, we keep the behaviour for now -> remove all posts where the primary author id matches. furthermore, remove all relations in posts_authors (e.g. secondary author) - we make re-use of the `excludeAttrs` concept which was invented in the contributors PR (to protect editing authors as author/contributor role) -> i've added a clear todo that we need a logic to make a diff of the target relation -> both for tags and authors - `authors` helper available (same as `tags` helper) - `primary_author` computed field available - `primary_author` functionality available (same as `primary_tag` e.g. permalinks, prev/next helper etc)
2018-03-27 17:16:15 +03:00
// swaps author_id to author, and always returns computed properties: url, comment_id, primary_tag, primary_author
.without('author_id').concat('author', 'url', 'primary_tag', 'primary_author')
.value(),
user: {
default: _(schema.users).keys().without('password').without('ghost_auth_access_token').value(),
public: _(schema.users)
.keys()
.without(
'password',
'email',
'ghost_auth_access_token',
'ghost_auth_id',
'created_at',
'created_by',
'updated_at',
'updated_by',
'last_seen',
'status'
)
.value()
},
// Tag API swaps parent_id to parent
tag: _(schema.tags).keys().without('parent_id').concat('parent').value(),
setting: _.keys(schema.settings),
subscriber: _.keys(schema.subscribers),
accesstoken: _.keys(schema.accesstokens),
role: _.keys(schema.roles),
permission: _.keys(schema.permissions),
notification: ['type', 'message', 'status', 'id', 'dismissible', 'location', 'custom'],
theme: ['name', 'package', 'active'],
themes: ['themes'],
invites: ['invites', 'meta'],
invite: _(schema.invites).keys().without('token').value(),
webhook: {
default: _(schema.webhooks)
.keys()
.without(
'name',
'last_triggered_at',
'last_triggered_error',
'last_triggered_status',
'secret',
'integration_id'
)
.value()
}
};
function getURL() {
return protocol + host;
}
function getSigninURL() {
return url.resolve(protocol + host + ':' + port, 'ghost/signin/');
}
function getAdminURL() {
return url.resolve(protocol + host + ':' + port, 'ghost/');
}
function isISO8601(date) {
return moment(date).parsingFlags().iso;
}
// make sure the API only returns expected properties only
function checkResponseValue(jsonResponse, expectedProperties) {
var providedProperties = _.keys(jsonResponse),
missing = _.difference(expectedProperties, providedProperties),
unexpected = _.difference(providedProperties, expectedProperties);
_.each(missing, function (prop) {
jsonResponse.should.have.property(prop);
});
_.each(unexpected, function (prop) {
jsonResponse.should.not.have.property(prop);
});
providedProperties.length.should.eql(expectedProperties.length);
}
// @TODO: support options pattern only, it's annoying to call checkResponse(null, null, null, something)
function checkResponse(jsonResponse, objectType, additionalProperties, missingProperties, onlyProperties, options) {
options = options || {};
var checkProperties = options.public ? (expectedProperties[objectType].public || expectedProperties[objectType]) : (expectedProperties[objectType].default || expectedProperties[objectType]);
checkProperties = onlyProperties ? onlyProperties : checkProperties;
checkProperties = additionalProperties ? checkProperties.concat(additionalProperties) : checkProperties;
checkProperties = missingProperties ? _.xor(checkProperties, missingProperties) : checkProperties;
checkResponseValue(jsonResponse, checkProperties);
}
/**
* This function manages the work of ensuring we have an overridden owner user, and grabbing an access token
*
* @TODO make this do the DB init as well
*/
const doAuth = (apiOptions) => {
return function doAuthInner() {
let API_URL = arguments[0];
let request = arguments[1];
// Remove API_URL & request from this list
let options = Array.prototype.slice.call(arguments, 2);
// No DB setup, but override the owner
options = _.merge({'owner:post': true}, _.transform(options, function (result, val) {
if (val) {
result[val] = true;
}
}));
const fixtureOps = apiOptions.getFixtureOps(options);
return sequence(fixtureOps).then(function () {
return login(request, API_URL);
});
};
};
const login = (request, API_URL) => {
// CASE: by default we use the owner to login
if (!request.user) {
request.user = DataGenerator.Content.users[0];
}
return new Promise(function (resolve, reject) {
request.post(API_URL)
.set('Origin', config.get('url'))
.send({
grant_type: 'password',
username: request.user.email,
password: 'Sl1m3rson99',
client_id: 'ghost-admin',
client_secret: 'not_available'
})
.then(function then(res) {
if (res.statusCode !== 200 && res.statusCode !== 201) {
return reject(new common.errors.GhostError({
message: res.body.errors[0].message
}));
}
resolve(res.headers['set-cookie'] || res.body.access_token);
}, reject);
});
};
module.exports = (options = {}) => {
return {
getSigninURL: getSigninURL,
getAdminURL: getAdminURL,
doAuth: doAuth(options),
login: login,
getURL: getURL,
checkResponse: checkResponse,
checkResponseValue: checkResponseValue,
isISO8601: isISO8601
};
};