0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-03-11 02:12:21 -05:00

Deleted v3 regression tests

- at the time of writing, the v3 API === canary API
- we have both v3 + canary regression tests, which are nearly the same
  but there are slight deviations that we keep missing when adding new
  tests
- the canary tests are actually describing functionality of the v3 API
- therefore, we should be ok to delete the v3 regression tests for now
- when v3 is stable, we can copy the canary tests back to v3
This commit is contained in:
Daniel Lockyer 2020-12-02 17:52:33 +00:00
parent 2d621c9680
commit 4ef019d88d
17 changed files with 0 additions and 3678 deletions

View file

@ -1,317 +0,0 @@
const should = require('should');
const sinon = require('sinon');
const supertest = require('supertest');
const localUtils = require('./utils');
const testUtils = require('../../../../utils/index');
const models = require('../../../../../core/server/models/index');
const security = require('@tryghost/security');
const settingsCache = require('../../../../../core/server/services/settings/cache');
const config = require('../../../../../core/shared/config/index');
const mailService = require('../../../../../core/server/services/mail/index');
let ghost = testUtils.startGhost;
let request;
describe('Authentication API v3', function () {
let ghostServer;
describe('Blog setup', function () {
before(function () {
return ghost({forceStart: true})
.then(function (_ghostServer) {
ghostServer = _ghostServer;
request = supertest.agent(config.get('url'));
});
});
beforeEach(function () {
sinon.stub(mailService.GhostMailer.prototype, 'send').resolves('Mail is disabled');
});
afterEach(function () {
sinon.restore();
});
it('is setup? no', function () {
return request
.get(localUtils.API.getApiQuery('authentication/setup'))
.set('Origin', config.get('url'))
.expect('Content-Type', /json/)
.expect(200)
.then((res) => {
res.body.setup[0].status.should.be.false();
});
});
it('complete setup', function () {
return request
.post(localUtils.API.getApiQuery('authentication/setup'))
.set('Origin', config.get('url'))
.send({
setup: [{
name: 'test user',
email: 'test@example.com',
password: 'thisissupersafe',
blogTitle: 'a test blog'
}]
})
.expect('Content-Type', /json/)
.expect(201)
.then((res) => {
const jsonResponse = res.body;
should.exist(jsonResponse.users);
should.not.exist(jsonResponse.meta);
jsonResponse.users.should.have.length(1);
localUtils.API.checkResponse(jsonResponse.users[0], 'user');
const newUser = jsonResponse.users[0];
newUser.id.should.equal(testUtils.DataGenerator.Content.users[0].id);
newUser.name.should.equal('test user');
newUser.email.should.equal('test@example.com');
mailService.GhostMailer.prototype.send.called.should.be.true();
mailService.GhostMailer.prototype.send.args[0][0].to.should.equal('test@example.com');
});
});
it('is setup? yes', function () {
return request
.get(localUtils.API.getApiQuery('authentication/setup'))
.set('Origin', config.get('url'))
.expect('Content-Type', /json/)
.expect(200)
.then((res) => {
res.body.setup[0].status.should.be.true();
});
});
it('complete setup again', function () {
return request
.post(localUtils.API.getApiQuery('authentication/setup'))
.set('Origin', config.get('url'))
.send({
setup: [{
name: 'test user',
email: 'test-leo@example.com',
password: 'thisissupersafe',
blogTitle: 'a test blog'
}]
})
.expect('Content-Type', /json/)
.expect(403);
});
it('update setup', function () {
return localUtils.doAuth(request)
.then(() => {
return request
.put(localUtils.API.getApiQuery('authentication/setup'))
.set('Origin', config.get('url'))
.send({
setup: [{
name: 'test user edit',
email: 'test-edit@example.com',
password: 'thisissupersafe',
blogTitle: 'a test blog'
}]
})
.expect('Content-Type', /json/)
.expect(200);
})
.then((res) => {
const jsonResponse = res.body;
should.exist(jsonResponse.users);
should.not.exist(jsonResponse.meta);
jsonResponse.users.should.have.length(1);
localUtils.API.checkResponse(jsonResponse.users[0], 'user');
const newUser = jsonResponse.users[0];
newUser.id.should.equal(testUtils.DataGenerator.Content.users[0].id);
newUser.name.should.equal('test user edit');
newUser.email.should.equal('test-edit@example.com');
});
});
});
describe('Invitation', function () {
before(function () {
return ghost()
.then(function (_ghostServer) {
ghostServer = _ghostServer;
request = supertest.agent(config.get('url'));
// simulates blog setup (initialises the owner)
return localUtils.doAuth(request, 'invites');
});
});
it('check invite with invalid email', function () {
return request
.get(localUtils.API.getApiQuery('authentication/invitation?email=invalidemail'))
.set('Origin', config.get('url'))
.expect('Content-Type', /json/)
.expect(400);
});
it('check valid invite', function () {
return request
.get(localUtils.API.getApiQuery(`authentication/invitation?email=${testUtils.DataGenerator.forKnex.invites[0].email}`))
.set('Origin', config.get('url'))
.expect('Content-Type', /json/)
.expect(200)
.then((res) => {
res.body.invitation[0].valid.should.equal(true);
});
});
it('check invalid invite', function () {
return request
.get(localUtils.API.getApiQuery(`authentication/invitation?email=notinvited@example.org`))
.set('Origin', config.get('url'))
.expect('Content-Type', /json/)
.expect(200)
.then((res) => {
res.body.invitation[0].valid.should.equal(false);
});
});
it('try to accept without invite', function () {
return request
.post(localUtils.API.getApiQuery('authentication/invitation'))
.set('Origin', config.get('url'))
.send({
invitation: [{
token: 'lul11111',
password: 'lel123456',
email: 'not-invited@example.org',
name: 'not invited'
}]
})
.expect('Content-Type', /json/)
.expect(404);
});
it('try to accept with invite', function () {
return request
.post(localUtils.API.getApiQuery('authentication/invitation'))
.set('Origin', config.get('url'))
.send({
invitation: [{
token: testUtils.DataGenerator.forKnex.invites[0].token,
password: '12345678910',
email: testUtils.DataGenerator.forKnex.invites[0].email,
name: 'invited'
}]
})
.expect('Content-Type', /json/)
.expect(200)
.then((res) => {
res.body.invitation[0].message.should.equal('Invitation accepted.');
});
});
});
describe('Password reset', function () {
const user = testUtils.DataGenerator.forModel.users[0];
before(function () {
return ghost({forceStart: true})
.then(() => {
request = supertest.agent(config.get('url'));
})
.then(() => {
return localUtils.doAuth(request);
});
});
beforeEach(function () {
sinon.stub(mailService.GhostMailer.prototype, 'send').resolves('Mail is disabled');
});
afterEach(function () {
sinon.restore();
});
it('reset password', function (done) {
models.User.getOwnerUser(testUtils.context.internal)
.then(function (ownerUser) {
const token = security.tokens.resetToken.generateHash({
expires: Date.now() + (1000 * 60),
email: user.email,
dbHash: settingsCache.get('db_hash'),
password: ownerUser.get('password')
});
request.put(localUtils.API.getApiQuery('authentication/passwordreset'))
.set('Origin', config.get('url'))
.set('Accept', 'application/json')
.send({
passwordreset: [{
token: token,
newPassword: 'thisissupersafe',
ne2Password: 'thisissupersafe'
}]
})
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
const jsonResponse = res.body;
should.exist(jsonResponse.passwordreset[0].message);
jsonResponse.passwordreset[0].message.should.equal('Password changed successfully.');
done();
});
})
.catch(done);
});
it('reset password: invalid token', function () {
return request
.put(localUtils.API.getApiQuery('authentication/passwordreset'))
.set('Origin', config.get('url'))
.set('Accept', 'application/json')
.send({
passwordreset: [{
token: 'invalid',
newPassword: 'thisissupersafe',
ne2Password: 'thisissupersafe'
}]
})
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(401)
.then((res) => {
should.exist(res.body.errors);
res.body.errors[0].type.should.eql('UnauthorizedError');
res.body.errors[0].message.should.eql('Cannot reset password.');
});
});
it('reset password: generate reset token', function () {
return request
.post(localUtils.API.getApiQuery('authentication/passwordreset'))
.set('Origin', config.get('url'))
.set('Accept', 'application/json')
.send({
passwordreset: [{
email: user.email
}]
})
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.then((res) => {
const jsonResponse = res.body;
should.exist(jsonResponse.passwordreset[0].message);
jsonResponse.passwordreset[0].message.should.equal('Check your email for further instructions.');
mailService.GhostMailer.prototype.send.args[0][0].to.should.equal(user.email);
});
});
});
});

View file

@ -1,172 +0,0 @@
const path = require('path');
const _ = require('lodash');
const os = require('os');
const fs = require('fs-extra');
const uuid = require('uuid');
const should = require('should');
const supertest = require('supertest');
const sinon = require('sinon');
const config = require('../../../../../core/shared/config');
const {events} = require('../../../../../core/server/lib/common');
const testUtils = require('../../../../utils');
const localUtils = require('./utils');
let ghost = testUtils.startGhost;
let request;
let eventsTriggered;
describe('DB API', function () {
let backupKey;
let schedulerKey;
before(function () {
return ghost()
.then(() => {
request = supertest.agent(config.get('url'));
})
.then(() => {
return localUtils.doAuth(request);
})
.then(() => {
backupKey = _.find(testUtils.existingData.apiKeys, {integration: {slug: 'ghost-backup'}});
schedulerKey = _.find(testUtils.existingData.apiKeys, {integration: {slug: 'ghost-scheduler'}});
});
});
beforeEach(function () {
eventsTriggered = {};
sinon.stub(events, 'emit').callsFake((eventName, eventObj) => {
if (!eventsTriggered[eventName]) {
eventsTriggered[eventName] = [];
}
eventsTriggered[eventName].push(eventObj);
});
});
afterEach(function () {
sinon.restore();
});
// SKIPPED: we no longer have the "extra" clients and client_trusted_domains tables
it.skip('can export the database with more tables', function () {
return request.get(localUtils.API.getApiQuery('db/?include=clients,client_trusted_domains'))
.set('Origin', config.get('url'))
.expect('Content-Type', /json/)
.expect(200)
.then((res) => {
const jsonResponse = res.body;
should.exist(jsonResponse.db);
jsonResponse.db.should.have.length(1);
Object.keys(jsonResponse.db[0].data).length.should.eql(28);
});
});
it('can export & import', function () {
const exportFolder = path.join(os.tmpdir(), uuid.v4());
const exportPath = path.join(exportFolder, 'export.json');
return request.put(localUtils.API.getApiQuery('settings/'))
.set('Origin', config.get('url'))
.send({
settings: [
{
key: 'is_private',
value: true
}
]
})
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.then(() => {
return request.get(localUtils.API.getApiQuery('db/'))
.set('Origin', config.get('url'))
.expect('Content-Type', /json/)
.expect(200);
})
.then((res) => {
const jsonResponse = res.body;
should.exist(jsonResponse.db);
fs.ensureDirSync(exportFolder);
fs.writeJSONSync(exportPath, jsonResponse);
return request.post(localUtils.API.getApiQuery('db/'))
.set('Origin', config.get('url'))
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.attach('importfile', exportPath)
.expect(200);
})
.then((res) => {
res.body.problems.length.should.eql(3);
fs.removeSync(exportFolder);
});
});
it('fails when triggering an export from unknown filename ', function () {
return request.get(localUtils.API.getApiQuery('db/?filename=this_file_is_not_here.json'))
.set('Origin', config.get('url'))
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect(404);
});
it('import should fail without file', function () {
return request.post(localUtils.API.getApiQuery('db/'))
.set('Origin', config.get('url'))
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect(422);
});
it('import should fail with unsupported file', function () {
return request.post(localUtils.API.getApiQuery('db/'))
.set('Origin', config.get('url'))
.expect('Content-Type', /json/)
.attach('importfile', path.join(__dirname, '/../../../../utils/fixtures/csv/single-column-with-header.csv'))
.expect(415);
});
it('backup can be triggered by backup integration', function () {
const backupQuery = `?filename=test`;
const fsStub = sinon.stub(fs, 'writeFile').resolves();
return request.post(localUtils.API.getApiQuery(`db/backup${backupQuery}`))
.set('Authorization', `Ghost ${localUtils.getValidAdminToken('/v3/admin/', backupKey)}`)
.set('Origin', config.get('url'))
.expect('Content-Type', /json/)
.expect(200)
.then((res) => {
res.body.should.be.Object();
res.body.db[0].filename.should.match(/test\.json/);
fsStub.calledOnce.should.eql(true);
});
});
it('backup can not be triggered by integration other than backup', function () {
const fsStub = sinon.stub(fs, 'writeFile').resolves();
return request.post(localUtils.API.getApiQuery(`db/backup`))
.set('Authorization', `Ghost ${localUtils.getValidAdminToken('/v3/admin/', schedulerKey)}`)
.set('Origin', config.get('url'))
.expect('Content-Type', /json/)
.expect(403)
.then((res) => {
should.exist(res.body.errors);
res.body.errors[0].type.should.eql('NoPermissionError');
fsStub.called.should.eql(false);
});
});
it('backup can be triggered by Admin authentication', function () {
const fsStub = sinon.stub(fs, 'writeFile').resolves();
return request.post(localUtils.API.getApiQuery(`db/backup`))
.set('Origin', config.get('url'))
.expect('Content-Type', /json/)
.expect(200);
});
});

View file

@ -1,86 +0,0 @@
const path = require('path');
const fs = require('fs-extra');
const should = require('should');
const supertest = require('supertest');
const localUtils = require('./utils');
const testUtils = require('../../../../utils');
const config = require('../../../../../core/shared/config');
const ghost = testUtils.startGhost;
describe('Images API', function () {
const images = [];
let request;
before(function () {
return ghost()
.then(function () {
request = supertest.agent(config.get('url'));
})
.then(function () {
return localUtils.doAuth(request);
});
});
after(function () {
images.forEach(function (image) {
fs.removeSync(config.get('paths').appRoot + image);
});
});
it('Can\'t import fail without file', function () {
return request
.post(localUtils.API.getApiQuery('images/upload'))
.set('Origin', config.get('url'))
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect(422);
});
it('Can\'t import with unsupported file', function (done) {
request.post(localUtils.API.getApiQuery('images/upload'))
.set('Origin', config.get('url'))
.expect('Content-Type', /json/)
.attach('file', path.join(__dirname, '/../../../../utils/fixtures/csv/single-column-with-header.csv'))
.expect(415)
.end(function (err) {
if (err) {
return done(err);
}
done();
});
});
it('Can\'t upload incorrect extension', function (done) {
request.post(localUtils.API.getApiQuery('images/upload'))
.set('Origin', config.get('url'))
.set('content-type', 'image/png')
.expect('Content-Type', /json/)
.attach('file', path.join(__dirname, '/../../../../utils/fixtures/images/ghost-logo.pngx'))
.expect(415)
.end(function (err) {
if (err) {
return done(err);
}
done();
});
});
it('Can\'t import if profile image is not square', function (done) {
request.post(localUtils.API.getApiQuery('images/upload'))
.set('Origin', config.get('url'))
.expect('Content-Type', /json/)
.field('purpose', 'profile_image')
.attach('file', path.join(__dirname, '/../../../../utils/fixtures/images/favicon_not_square.png'))
.expect(422)
.end(function (err) {
if (err) {
return done(err);
}
done();
});
});
});

View file

@ -1,198 +0,0 @@
const should = require('should');
const supertest = require('supertest');
const testUtils = require('../../../../utils');
const config = require('../../../../../core/shared/config');
const localUtils = require('./utils');
const ghost = testUtils.startGhost;
describe('Notifications API', function () {
describe('As Editor', function () {
let request;
before(function () {
return ghost()
.then(function (_ghostServer) {
request = supertest.agent(config.get('url'));
})
.then(function () {
return testUtils.createUser({
user: testUtils.DataGenerator.forKnex.createUser({
email: 'test+editor@ghost.org'
}),
role: testUtils.DataGenerator.Content.roles[1].name
});
})
.then((user) => {
request.user = user;
return localUtils.doAuth(request);
});
});
it('Add notification', function () {
const newNotification = {
type: 'info',
message: 'test notification',
custom: true
};
return request.post(localUtils.API.getApiQuery('notifications/'))
.set('Origin', config.get('url'))
.send({notifications: [newNotification]})
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(201)
.then((res) => {
const jsonResponse = res.body;
should.exist(jsonResponse);
should.exist(jsonResponse.notifications);
should.equal(jsonResponse.notifications.length, 1);
});
});
it('Read notifications', function () {
return request.get(localUtils.API.getApiQuery('notifications/'))
.set('Origin', config.get('url'))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.then((res) => {
const jsonResponse = res.body;
should.exist(jsonResponse);
should.exist(jsonResponse.notifications);
should.equal(jsonResponse.notifications.length, 1);
});
});
});
describe('As Author', function () {
let request;
before(function () {
return ghost()
.then(function (_ghostServer) {
request = supertest.agent(config.get('url'));
})
.then(function () {
return testUtils.createUser({
user: testUtils.DataGenerator.forKnex.createUser({
email: 'test+author@ghost.org'
}),
role: testUtils.DataGenerator.Content.roles[2].name
});
})
.then((user) => {
request.user = user;
return localUtils.doAuth(request);
});
});
it('Add notification', function () {
const newNotification = {
type: 'info',
message: 'test notification',
custom: true
};
return request.post(localUtils.API.getApiQuery('notifications/'))
.set('Origin', config.get('url'))
.send({notifications: [newNotification]})
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(403);
});
it('Read notifications', function () {
return request.get(localUtils.API.getApiQuery('notifications/'))
.set('Origin', config.get('url'))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(403);
});
});
describe('Can view by multiple users', function () {
let requestEditor1;
let requestEditor2;
let notification;
before(function () {
return ghost()
.then(function (_ghostServer) {
requestEditor1 = supertest.agent(config.get('url'));
requestEditor2 = supertest.agent(config.get('url'));
})
.then(function () {
return testUtils.createUser({
user: testUtils.DataGenerator.forKnex.createUser({
email: 'test+editor1@ghost.org'
}),
role: testUtils.DataGenerator.Content.roles[1].name
});
})
.then((user) => {
requestEditor1.user = user;
return localUtils.doAuth(requestEditor1);
})
.then(function () {
return testUtils.createUser({
user: testUtils.DataGenerator.forKnex.createUser({
email: 'test+editor2@ghost.org'
}),
role: testUtils.DataGenerator.Content.roles[1].name
});
})
.then((user) => {
requestEditor2.user = user;
return localUtils.doAuth(requestEditor2);
})
.then(() => {
const newNotification = {
type: 'info',
message: 'multiple views',
custom: true
};
return requestEditor1.post(localUtils.API.getApiQuery('notifications/'))
.set('Origin', config.get('url'))
.send({notifications: [newNotification]})
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(201)
.then((res) => {
notification = res.body.notifications[0];
});
});
});
it('notification is visible and dismissible by other user', function () {
return requestEditor1.del(localUtils.API.getApiQuery(`notifications/${notification.id}`))
.set('Origin', config.get('url'))
.expect(204)
.then(() => {
return requestEditor2.get(localUtils.API.getApiQuery(`notifications/`))
.set('Origin', config.get('url'))
.expect(200)
.then(function (res) {
const deleted = res.body.notifications.filter(n => n.id === notification.id);
deleted.should.not.be.empty();
});
})
.then(() => {
return requestEditor2.del(localUtils.API.getApiQuery(`notifications/${notification.id}`))
.set('Origin', config.get('url'))
.expect(204);
})
.then(() => {
return requestEditor2.get(localUtils.API.getApiQuery(`notifications/`))
.set('Origin', config.get('url'))
.expect(200)
.then(function (res) {
const deleted = res.body.notifications.filter(n => n.id === notification.id);
deleted.should.be.empty();
});
});
});
});
});

View file

@ -1,444 +0,0 @@
const should = require('should');
const supertest = require('supertest');
const ObjectId = require('bson-objectid');
const moment = require('moment-timezone');
const testUtils = require('../../../../utils');
const config = require('../../../../../core/shared/config');
const models = require('../../../../../core/server/models');
const localUtils = require('./utils');
const ghost = testUtils.startGhost;
let request;
describe('Posts API (v3)', function () {
let ghostServer;
let ownerCookie;
before(function () {
return ghost()
.then(function (_ghostServer) {
ghostServer = _ghostServer;
request = supertest.agent(config.get('url'));
})
.then(function () {
return localUtils.doAuth(request, 'users:extra', 'posts');
})
.then(function (cookie) {
ownerCookie = cookie;
});
});
describe('Browse', function () {
it('fields & formats combined', function (done) {
request.get(localUtils.API.getApiQuery('posts/?formats=mobiledoc,html&fields=id,title'))
.set('Origin', config.get('url'))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
should.not.exist(res.headers['x-cache-invalidate']);
const jsonResponse = res.body;
should.exist(jsonResponse.posts);
localUtils.API.checkResponse(jsonResponse, 'posts');
jsonResponse.posts.should.have.length(13);
localUtils.API.checkResponse(
jsonResponse.posts[0],
'post',
null,
null,
['mobiledoc', 'id', 'title', 'html']
);
localUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination');
done();
});
});
it('combined fields, formats, include and non existing', function (done) {
request.get(localUtils.API.getApiQuery('posts/?formats=mobiledoc,html,plaintext&fields=id,title,primary_tag,doesnotexist&include=authors,tags'))
.set('Origin', config.get('url'))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
should.not.exist(res.headers['x-cache-invalidate']);
const jsonResponse = res.body;
should.exist(jsonResponse.posts);
localUtils.API.checkResponse(jsonResponse, 'posts');
jsonResponse.posts.should.have.length(13);
localUtils.API.checkResponse(
jsonResponse.posts[0],
'post',
null,
null,
['mobiledoc', 'plaintext', 'id', 'title', 'html', 'authors', 'tags', 'primary_tag']
);
localUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination');
done();
});
});
});
describe('Read', function () {
it('can\'t retrieve non existent post', function (done) {
request.get(localUtils.API.getApiQuery(`posts/${ObjectId.generate()}/`))
.set('Origin', config.get('url'))
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(404)
.end(function (err, res) {
if (err) {
return done(err);
}
should.not.exist(res.headers['x-cache-invalidate']);
const jsonResponse = res.body;
should.exist(jsonResponse);
should.exist(jsonResponse.errors);
testUtils.API.checkResponseValue(jsonResponse.errors[0], [
'message',
'context',
'type',
'details',
'property',
'help',
'code',
'id'
]);
done();
});
});
});
describe('Add', function () {
it('adds default title when it is missing', function () {
return request
.post(localUtils.API.getApiQuery('posts/'))
.set('Origin', config.get('url'))
.send({
posts: [{
title: ''
}]
})
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(201)
.then((res) => {
should.exist(res.body.posts);
should.exist(res.body.posts[0].title);
res.body.posts[0].title.should.equal('(Untitled)');
});
});
});
describe('Edit', function () {
it('published_at = null', function () {
return request
.get(localUtils.API.getApiQuery(`posts/${testUtils.DataGenerator.Content.posts[0].id}/`))
.set('Origin', config.get('url'))
.expect(200)
.then((res) => {
return request
.put(localUtils.API.getApiQuery('posts/' + testUtils.DataGenerator.Content.posts[0].id + '/'))
.set('Origin', config.get('url'))
.send({
posts: [{
published_at: null,
updated_at: res.body.posts[0].updated_at
}]
})
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200);
})
.then((res) => {
// @NOTE: if you set published_at to null and the post is published, we set it to NOW in model layer
should.exist(res.headers['x-cache-invalidate']);
should.exist(res.body.posts);
should.exist(res.body.posts[0].published_at);
});
});
it('html to plaintext', function () {
return request
.get(localUtils.API.getApiQuery(`posts/${testUtils.DataGenerator.Content.posts[0].id}/`))
.set('Origin', config.get('url'))
.expect(200)
.then((res) => {
return request
.put(localUtils.API.getApiQuery('posts/' + testUtils.DataGenerator.Content.posts[0].id + '/?source=html&formats=html,plaintext'))
.set('Origin', config.get('url'))
.send({
posts: [{
html: '<p>HTML Ipsum presents</p>',
updated_at: res.body.posts[0].updated_at
}]
})
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200);
})
.then((res) => {
return models.Post.findOne({
id: res.body.posts[0].id
}, testUtils.context.internal);
})
.then((model) => {
model.get('plaintext').should.equal('HTML Ipsum presents');
});
});
it('canonical_url', function () {
return request
.get(localUtils.API.getApiQuery(`posts/${testUtils.DataGenerator.Content.posts[0].id}/`))
.set('Origin', config.get('url'))
.expect(200)
.then((res) => {
return request
.put(localUtils.API.getApiQuery('posts/' + testUtils.DataGenerator.Content.posts[0].id + '/'))
.set('Origin', config.get('url'))
.send({
posts: [{
canonical_url: `/canonical/url`,
updated_at: res.body.posts[0].updated_at
}]
})
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200);
})
.then((res) => {
should.exist(res.body.posts);
should.exist(res.body.posts[0].canonical_url);
res.body.posts[0].canonical_url.should.equal(`${config.get('url')}/canonical/url`);
});
});
it('update dates & x_by', function () {
const post = {
created_by: ObjectId.generate(),
updated_by: ObjectId.generate(),
created_at: moment().add(2, 'days').format(),
updated_at: moment().add(2, 'days').format()
};
return request
.put(localUtils.API.getApiQuery('posts/' + testUtils.DataGenerator.Content.posts[0].id + '/'))
.set('Origin', config.get('url'))
.send({posts: [post]})
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.then((res) => {
// @NOTE: you cannot modify these fields above manually, that's why the resource won't change.
should.not.exist(res.headers['x-cache-invalidate']);
return models.Post.findOne({
id: res.body.posts[0].id
}, testUtils.context.internal);
})
.then((model) => {
// We expect that the changed properties aren't changed, they are still the same than before.
model.get('created_at').toISOString().should.not.eql(post.created_at);
model.get('updated_by').should.not.eql(post.updated_by);
model.get('created_by').should.not.eql(post.created_by);
// `updated_at` is automatically set, but it's not the date we send to override.
model.get('updated_at').toISOString().should.not.eql(post.updated_at);
});
});
it('Can change scheduled post', function () {
return request
.get(localUtils.API.getApiQuery(`posts/${testUtils.DataGenerator.Content.posts[7].id}/`))
.set('Origin', config.get('url'))
.expect(200)
.then((res) => {
res.body.posts[0].status.should.eql('scheduled');
return request
.put(localUtils.API.getApiQuery('posts/' + testUtils.DataGenerator.Content.posts[7].id + '/'))
.set('Origin', config.get('url'))
.send({
posts: [{
title: 'change scheduled post',
updated_at: res.body.posts[0].updated_at
}]
})
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200);
})
.then((res) => {
should.exist(res.headers['x-cache-invalidate']);
});
});
it('trims title', function () {
const untrimmedTitle = ' test trimmed update title ';
return request
.get(localUtils.API.getApiQuery(`posts/${testUtils.DataGenerator.Content.posts[0].id}/`))
.set('Origin', config.get('url'))
.expect(200)
.then((res) => {
return request
.put(localUtils.API.getApiQuery('posts/' + testUtils.DataGenerator.Content.posts[0].id + '/'))
.set('Origin', config.get('url'))
.send({
posts: [{
title: untrimmedTitle,
updated_at: res.body.posts[0].updated_at
}]
})
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200);
})
.then((res) => {
should.exist(res.body.posts);
should.exist(res.body.posts[0].title);
res.body.posts[0].title.should.equal(untrimmedTitle.trim());
});
});
it('strips invisible unicode from slug', function () {
const slug = 'this-is\u0008-invisible';
return request
.get(localUtils.API.getApiQuery(`posts/${testUtils.DataGenerator.Content.posts[0].id}/`))
.set('Origin', config.get('url'))
.expect(200)
.then((res) => {
return request
.put(localUtils.API.getApiQuery('posts/' + testUtils.DataGenerator.Content.posts[0].id + '/'))
.set('Origin', config.get('url'))
.send({
posts: [{
slug: slug,
updated_at: res.body.posts[0].updated_at
}]
})
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200);
})
.then((res) => {
should.exist(res.body.posts);
should.exist(res.body.posts[0].slug);
res.body.posts[0].slug.should.equal('this-is-invisible');
});
});
it('changes to post_meta fields triggers a cache invalidation', function () {
return request
.get(localUtils.API.getApiQuery(`posts/${testUtils.DataGenerator.Content.posts[0].id}/`))
.set('Origin', config.get('url'))
.expect(200)
.then((res) => {
return request
.put(localUtils.API.getApiQuery('posts/' + testUtils.DataGenerator.Content.posts[0].id + '/'))
.set('Origin', config.get('url'))
.send({
posts: [{
meta_title: 'changed meta title',
updated_at: res.body.posts[0].updated_at
}]
})
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200);
})
.then((res) => {
should.exist(res.headers['x-cache-invalidate']);
should.exist(res.body.posts);
should.equal(res.body.posts[0].meta_title, 'changed meta title');
});
});
it('saving post with no modbiledoc content doesn\t trigger cache invalidation', function () {
return request
.post(localUtils.API.getApiQuery('posts/'))
.set('Origin', config.get('url'))
.send({
posts: [{
title: 'Has a title by no other content',
status: 'published'
}]
})
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(201)
.then((res) => {
should.exist(res.body.posts);
should.exist(res.body.posts[0].title);
res.body.posts[0].title.should.equal('Has a title by no other content');
should.equal(res.body.posts[0].html, undefined);
should.equal(res.body.posts[0].plaintext, undefined);
return request
.put(localUtils.API.getApiQuery(`posts/${res.body.posts[0].id}/`))
.set('Origin', config.get('url'))
.send({
posts: [{
title: res.body.posts[0].title,
mobilecdoc: res.body.posts[0].mobilecdoc,
updated_at: res.body.posts[0].updated_at
}]
})
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200);
})
.then((res) => {
should.not.exist(res.headers['x-cache-invalidate']);
should.exist(res.body.posts);
res.body.posts[0].title.should.equal('Has a title by no other content');
should.equal(res.body.posts[0].html, undefined);
should.equal(res.body.posts[0].plaintext, undefined);
});
});
});
describe('Destroy', function () {
it('non existent post', function () {
return request
.del(localUtils.API.getApiQuery('posts/' + ObjectId.generate() + '/'))
.set('Origin', config.get('url'))
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(404)
.then((res) => {
should.not.exist(res.headers['x-cache-invalidate']);
should.exist(res.body);
should.exist(res.body.errors);
testUtils.API.checkResponseValue(res.body.errors[0], [
'message',
'context',
'type',
'details',
'property',
'help',
'code',
'id'
]);
});
});
});
});

View file

@ -1,383 +0,0 @@
const should = require('should');
const supertest = require('supertest');
const fs = require('fs-extra');
const Promise = require('bluebird');
const path = require('path');
const testUtils = require('../../../../utils');
const localUtils = require('./utils');
const configUtils = require('../../../../utils/configUtils');
const config = require('../../../../../core/shared/config');
const ghost = testUtils.startGhost;
let request;
describe('Redirects API', function () {
let originalContentPath;
before(function () {
return ghost({redirectsFile: true})
.then(() => {
request = supertest.agent(config.get('url'));
})
.then(() => {
return localUtils.doAuth(request);
})
.then(() => {
originalContentPath = configUtils.config.get('paths:contentPath');
});
});
describe('Download', function () {
afterEach(function () {
configUtils.config.set('paths:contentPath', originalContentPath);
});
it('file does not exist', function () {
// Just set any content folder, which does not contain a redirects file.
configUtils.set('paths:contentPath', path.join(__dirname, '../../../utils/fixtures/data'));
return request
.get(localUtils.API.getApiQuery('redirects/json/'))
.set('Origin', config.get('url'))
.expect(200)
.then((res) => {
res.headers['content-disposition'].should.eql('Attachment; filename="redirects.json"');
res.headers['content-type'].should.eql('application/json; charset=utf-8');
should.not.exist(res.headers['x-cache-invalidate']);
should.deepEqual(res.body, []);
});
});
it('file exists', function () {
return request
.get(localUtils.API.getApiQuery('redirects/json/'))
.set('Origin', config.get('url'))
.expect('Content-Type', /application\/json/)
.expect('Content-Disposition', 'Attachment; filename="redirects.json"')
.expect(200)
.then((res) => {
res.headers['content-disposition'].should.eql('Attachment; filename="redirects.json"');
res.headers['content-type'].should.eql('application/json; charset=utf-8');
should.deepEqual(res.body, require('../../../../utils/fixtures/data/redirects.json'));
});
});
});
describe('Download yaml', function () {
beforeEach(function () {
testUtils.setupRedirectsFile(config.get('paths:contentPath'), '.yaml');
});
afterEach(function () {
testUtils.setupRedirectsFile(config.get('paths:contentPath'), '.json');
});
// 'file does not exist' doesn't have to be tested because it always returns .json file.
it('file exists', function () {
return request
.get(localUtils.API.getApiQuery('redirects/json/'))
.set('Origin', config.get('url'))
.expect('Content-Type', /text\/html/)
.expect('Content-Disposition', 'Attachment; filename="redirects.yaml"')
.expect(200)
.then((res) => {
res.headers['content-disposition'].should.eql('Attachment; filename="redirects.yaml"');
res.headers['content-type'].should.eql('text/html; charset=utf-8');
should.deepEqual(res.text, fs.readFileSync(path.join(__dirname, '../../../../utils/fixtures/data/redirects.yaml')).toString());
});
});
});
describe('Upload', function () {
describe('Error cases', function () {
it('syntax error', function () {
fs.writeFileSync(path.join(config.get('paths:contentPath'), 'redirects.json'), 'something');
return request
.post(localUtils.API.getApiQuery('redirects/json/'))
.set('Origin', config.get('url'))
.attach('redirects', path.join(config.get('paths:contentPath'), 'redirects.json'))
.expect('Content-Type', /application\/json/)
.expect(400);
});
it('wrong format: no array', function () {
fs.writeFileSync(path.join(config.get('paths:contentPath'), 'redirects.json'), JSON.stringify({
from: 'c',
to: 'd'
}));
return request
.post(localUtils.API.getApiQuery('redirects/json/'))
.set('Origin', config.get('url'))
.attach('redirects', path.join(config.get('paths:contentPath'), 'redirects.json'))
.expect('Content-Type', /application\/json/)
.expect(422);
});
it('wrong format: no from/to', function () {
fs.writeFileSync(path.join(config.get('paths:contentPath'), 'redirects.json'), JSON.stringify([{to: 'd'}]));
return request
.post(localUtils.API.getApiQuery('redirects/json/'))
.set('Origin', config.get('url'))
.attach('redirects', path.join(config.get('paths:contentPath'), 'redirects.json'))
.expect('Content-Type', /application\/json/)
.expect(422);
});
});
describe('Ensure re-registering redirects works', function () {
const startGhost = (options) => {
return ghost(options)
.then(() => {
request = supertest.agent(config.get('url'));
})
.then(() => {
return localUtils.doAuth(request);
});
};
it('no redirects file exists', function () {
return startGhost({redirectsFile: false, forceStart: true})
.then(() => {
return request
.get('/my-old-blog-post/')
.expect(404);
})
.then(() => {
// Provide a redirects file in the root directory of the content test folder
fs.writeFileSync(path.join(config.get('paths:contentPath'), 'redirects-init.json'), JSON.stringify([{
from: 'k',
to: 'l'
}]));
})
.then(() => {
return request
.post(localUtils.API.getApiQuery('redirects/json/'))
.set('Origin', config.get('url'))
.attach('redirects', path.join(config.get('paths:contentPath'), 'redirects-init.json'))
.expect('Content-Type', /application\/json/)
.expect(200);
})
.then((res) => {
res.headers['x-cache-invalidate'].should.eql('/*');
return request
.get('/k/')
.expect(302);
})
.then((response) => {
response.headers.location.should.eql('/l');
const dataFiles = fs.readdirSync(config.getContentPath('data'));
dataFiles.join(',').match(/(redirects)/g).length.should.eql(1);
});
});
it('override', function () {
return startGhost({forceStart: true})
.then(() => {
return request
.get('/my-old-blog-post/')
.expect(301);
})
.then((response) => {
response.headers.location.should.eql('/revamped-url/');
})
.then(() => {
// Provide a second redirects file in the root directory of the content test folder
fs.writeFileSync(path.join(config.get('paths:contentPath'), 'redirects.json'), JSON.stringify([{
from: 'c',
to: 'd'
}]));
})
.then(() => {
// Override redirects file
return request
.post(localUtils.API.getApiQuery('redirects/json/'))
.set('Origin', config.get('url'))
.attach('redirects', path.join(config.get('paths:contentPath'), 'redirects.json'))
.expect('Content-Type', /application\/json/)
.expect(200);
})
.then((res) => {
res.headers['x-cache-invalidate'].should.eql('/*');
return request
.get('/my-old-blog-post/')
.expect(404);
})
.then(() => {
return request
.get('/c/')
.expect(302);
})
.then((response) => {
response.headers.location.should.eql('/d');
// check backup of redirects files
const dataFiles = fs.readdirSync(config.getContentPath('data'));
dataFiles.join(',').match(/(redirects)/g).length.should.eql(2);
// Provide another redirects file in the root directory of the content test folder
fs.writeFileSync(path.join(config.get('paths:contentPath'), 'redirects-something.json'), JSON.stringify([{
from: 'e',
to: 'b'
}]));
})
.then(() => {
// the backup is in the format HH:mm:ss, we have to wait minimum a second
return new Promise((resolve) => {
setTimeout(resolve, 1100);
});
})
.then(() => {
// Override redirects file again and ensure the backup file works twice
return request
.post(localUtils.API.getApiQuery('redirects/json/'))
.set('Origin', config.get('url'))
.attach('redirects', path.join(config.get('paths:contentPath'), 'redirects-something.json'))
.expect('Content-Type', /application\/json/)
.expect(200);
})
.then(() => {
const dataFiles = fs.readdirSync(config.getContentPath('data'));
dataFiles.join(',').match(/(redirects)/g).length.should.eql(3);
});
});
});
});
describe('Upload yaml', function () {
// No error cases here because there are no easy syntax pitfalls in the yaml format.
describe('Ensure re-registering redirects works', function () {
const startGhost = (options) => {
return ghost(options)
.then(() => {
request = supertest.agent(config.get('url'));
})
.then(() => {
return localUtils.doAuth(request);
});
};
it('no redirects file exists', function () {
return startGhost({redirectsFile: false, forceStart: true})
.then(() => {
return request
.get('/my-old-blog-post/')
.expect(404);
})
.then(() => {
// Provide a redirects file in the root directory of the content test folder
fs.writeFileSync(path.join(config.get('paths:contentPath'), 'redirects-init.yaml'), '302:\n k: l');
})
.then(() => {
return request
.post(localUtils.API.getApiQuery('redirects/json/'))
.set('Origin', config.get('url'))
.attach('redirects', path.join(config.get('paths:contentPath'), 'redirects-init.yaml'))
.expect('Content-Type', /application\/json/)
.expect(200);
})
.then((res) => {
res.headers['x-cache-invalidate'].should.eql('/*');
return request
.get('/k/')
.expect(302);
})
.then((response) => {
response.headers.location.should.eql('/l');
const dataFiles = fs.readdirSync(config.getContentPath('data'));
dataFiles.join(',').match(/(redirects)/g).length.should.eql(1);
});
});
it('override', function () {
// We want to test if we can override old redirects.json with new redirects.yaml
// That's why we start with .json.
return startGhost({forceStart: true, redirectsFileExt: '.json'})
.then(() => {
return request
.get('/my-old-blog-post/')
.expect(301);
})
.then((response) => {
response.headers.location.should.eql('/revamped-url/');
})
.then(() => {
// Provide a second redirects file in the root directory of the content test folder
fs.writeFileSync(path.join(config.get('paths:contentPath'), 'redirects.yaml'), '302:\n c: d');
})
.then(() => {
// Override redirects file
return request
.post(localUtils.API.getApiQuery('redirects/json/'))
.set('Origin', config.get('url'))
.attach('redirects', path.join(config.get('paths:contentPath'), 'redirects.yaml'))
.expect('Content-Type', /application\/json/)
.expect(200);
})
.then((res) => {
res.headers['x-cache-invalidate'].should.eql('/*');
return request
.get('/my-old-blog-post/')
.expect(404);
})
.then(() => {
return request
.get('/c/')
.expect(302);
})
.then((response) => {
response.headers.location.should.eql('/d');
// check backup of redirects files
const dataFiles = fs.readdirSync(config.getContentPath('data'));
dataFiles.join(',').match(/(redirects)/g).length.should.eql(2);
// Provide another redirects file in the root directory of the content test folder
fs.writeFileSync(path.join(config.get('paths:contentPath'), 'redirects-something.json'), JSON.stringify([{
from: 'e',
to: 'b'
}]));
})
.then(() => {
// the backup is in the format HH:mm:ss, we have to wait minimum a second
return new Promise((resolve) => {
setTimeout(resolve, 1100);
});
})
.then(() => {
// Override redirects file again and ensure the backup file works twice
return request
.post(localUtils.API.getApiQuery('redirects/json/'))
.set('Origin', config.get('url'))
.attach('redirects', path.join(config.get('paths:contentPath'), 'redirects-something.json'))
.expect('Content-Type', /application\/json/)
.expect(200);
})
.then(() => {
return request
.get('/e/')
.expect(302);
})
.then((response) => {
response.headers.location.should.eql('/b');
const dataFiles = fs.readdirSync(config.getContentPath('data'));
dataFiles.join(',').match(/(redirects)/g).length.should.eql(3);
});
});
});
});
});

View file

@ -1,184 +0,0 @@
const _ = require('lodash');
const should = require('should');
const supertest = require('supertest');
const Promise = require('bluebird');
const sinon = require('sinon');
const moment = require('moment-timezone');
const SchedulingDefault = require('../../../../../core/server/adapters/scheduling/SchedulingDefault');
const models = require('../../../../../core/server/models/index');
const config = require('../../../../../core/shared/config/index');
const testUtils = require('../../../../utils/index');
const localUtils = require('./utils');
const ghost = testUtils.startGhost;
// TODO: Fix with token in URL
describe.skip('v3 Schedules API', function () {
const resources = [];
let request;
before(function () {
models.init();
// @NOTE: mock the post scheduler, otherwise it will auto publish the post
sinon.stub(SchedulingDefault.prototype, '_pingUrl').resolves();
});
after(function () {
sinon.restore();
});
before(function () {
return ghost()
.then(() => {
request = supertest.agent(config.get('url'));
});
});
before(function () {
return ghost()
.then(function () {
resources.push(testUtils.DataGenerator.forKnex.createPost({
created_by: testUtils.existingData.users[0].id,
author_id: testUtils.existingData.users[0].id,
published_by: testUtils.existingData.users[0].id,
published_at: moment().add(30, 'seconds').toDate(),
status: 'scheduled',
slug: 'first'
}));
resources.push(testUtils.DataGenerator.forKnex.createPost({
created_by: testUtils.existingData.users[0].id,
author_id: testUtils.existingData.users[0].id,
published_by: testUtils.existingData.users[0].id,
published_at: moment().subtract(30, 'seconds').toDate(),
status: 'scheduled',
slug: 'second'
}));
resources.push(testUtils.DataGenerator.forKnex.createPost({
created_by: testUtils.existingData.users[0].id,
author_id: testUtils.existingData.users[0].id,
published_by: testUtils.existingData.users[0].id,
published_at: moment().add(10, 'minute').toDate(),
status: 'scheduled',
slug: 'third'
}));
resources.push(testUtils.DataGenerator.forKnex.createPost({
created_by: testUtils.existingData.users[0].id,
author_id: testUtils.existingData.users[0].id,
published_by: testUtils.existingData.users[0].id,
published_at: moment().subtract(10, 'minute').toDate(),
status: 'scheduled',
slug: 'fourth'
}));
resources.push(testUtils.DataGenerator.forKnex.createPost({
created_by: testUtils.existingData.users[0].id,
author_id: testUtils.existingData.users[0].id,
published_by: testUtils.existingData.users[0].id,
published_at: moment().add(30, 'seconds').toDate(),
status: 'scheduled',
slug: 'fifth',
type: 'page'
}));
return Promise.mapSeries(resources, function (post) {
return models.Post.add(post, {context: {internal: true}});
}).then(function (result) {
result.length.should.eql(5);
});
});
});
describe('publish', function () {
let schedulerKey;
before(function () {
schedulerKey = _.find(testUtils.existingData.apiKeys, {integration: {slug: 'ghost-scheduler'}});
});
it('publishes posts', function () {
return request
.put(localUtils.API.getApiQuery(`schedules/posts/${resources[0].id}/`))
.set('Authorization', `Ghost ${localUtils.getValidAdminToken('/v3/admin/', schedulerKey)}`)
.set('Origin', config.get('url'))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.then((res) => {
should.exist(res.headers['x-cache-invalidate']);
const jsonResponse = res.body;
should.exist(jsonResponse);
jsonResponse.posts[0].id.should.eql(resources[0].id);
jsonResponse.posts[0].status.should.eql('published');
});
});
it('publishes page', function () {
return request
.put(localUtils.API.getApiQuery(`schedules/pages/${resources[4].id}/`))
.set('Authorization', `Ghost ${localUtils.getValidAdminToken('/v3/admin/', schedulerKey)}`)
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.then((res) => {
should.exist(res.headers['x-cache-invalidate']);
const jsonResponse = res.body;
should.exist(jsonResponse);
jsonResponse.pages[0].id.should.eql(resources[4].id);
jsonResponse.pages[0].status.should.eql('published');
});
});
it('no access', function () {
const zapierKey = _.find(testUtils.existingData.apiKeys, {integration: {slug: 'ghost-backup'}});
return request
.put(localUtils.API.getApiQuery(`schedules/posts/${resources[0].id}/`))
.set('Authorization', `Ghost ${localUtils.getValidAdminToken('/v3/admin/', zapierKey)}`)
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(403);
});
it('should fail with invalid resource type', function () {
return request
.put(localUtils.API.getApiQuery(`schedules/this_is_invalid/${resources[0].id}/`))
.set('Authorization', `Ghost ${localUtils.getValidAdminToken('/v3/admin/', schedulerKey)}`)
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(422);
});
it('published_at is x seconds in past, but still in tolerance', function () {
return request
.put(localUtils.API.getApiQuery(`schedules/posts/${resources[1].id}/`))
.set('Authorization', `Ghost ${localUtils.getValidAdminToken('/v3/admin/', schedulerKey)}`)
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200);
});
it('not found', function () {
return request
.put(localUtils.API.getApiQuery(`schedules/posts/${resources[2].id}/`))
.set('Authorization', `Ghost ${localUtils.getValidAdminToken('/v3/admin/', schedulerKey)}`)
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(404);
});
it('force publish', function () {
return request
.put(localUtils.API.getApiQuery(`schedules/posts/${resources[3].id}/`))
.send({
force: true
})
.set('Authorization', `Ghost ${localUtils.getValidAdminToken('/v3/admin/', schedulerKey)}`)
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200);
});
});
});

View file

@ -1,636 +0,0 @@
const _ = require('lodash');
const should = require('should');
const supertest = require('supertest');
const config = require('../../../../../core/shared/config');
const testUtils = require('../../../../utils');
const localUtils = require('./utils');
const ghost = testUtils.startGhost;
// NOTE: in future iterations these fields should be fetched from a central module.
// Have put a list as is here for the lack of better place for it.
const defaultSettingsKeys = [
'title',
'description',
'logo',
'cover_image',
'icon',
'lang',
'timezone',
'codeinjection_head',
'codeinjection_foot',
'facebook',
'twitter',
'navigation',
'secondary_navigation',
'meta_title',
'meta_description',
'og_image',
'og_title',
'og_description',
'twitter_image',
'twitter_title',
'twitter_description',
'active_theme',
'is_private',
'password',
'public_hash',
'default_content_visibility',
'members_allow_free_signup',
'members_from_address',
'members_support_address',
'members_reply_address',
'members_free_signup_redirect',
'members_paid_signup_redirect',
'stripe_product_name',
'stripe_plans',
'stripe_secret_key',
'stripe_publishable_key',
'stripe_connect_secret_key',
'stripe_connect_publishable_key',
'stripe_connect_account_id',
'stripe_connect_display_name',
'stripe_connect_livemode',
'portal_name',
'portal_button',
'portal_plans',
'portal_button_style',
'portal_button_icon',
'portal_button_signup_text',
'mailgun_api_key',
'mailgun_domain',
'mailgun_base_url',
'email_track_opens',
'amp',
'amp_gtag_id',
'labs',
'slack',
'unsplash',
'shared_views',
'active_timezone',
'default_locale',
'accent_color',
'newsletter_show_badge',
'newsletter_show_header',
'newsletter_body_font_category',
'newsletter_footer_content'
];
describe('Settings API (v3)', function () {
let ghostServer;
let request;
describe('As Owner', function () {
before(function () {
return ghost()
.then(function (_ghostServer) {
ghostServer = _ghostServer;
request = supertest.agent(config.get('url'));
})
.then(function () {
return localUtils.doAuth(request);
});
});
it('Can request all settings', function () {
return request.get(localUtils.API.getApiQuery(`settings/`))
.set('Origin', config.get('url'))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.then((res) => {
should.not.exist(res.headers['x-cache-invalidate']);
const jsonResponse = res.body;
should.exist(jsonResponse.settings);
should.exist(jsonResponse.meta);
jsonResponse.settings.should.be.an.Object();
const settings = jsonResponse.settings;
settings.map(s => s.key).sort().should.deepEqual(defaultSettingsKeys.sort());
localUtils.API.checkResponse(jsonResponse, 'settings');
});
});
it('Can request settings by type', function () {
return request.get(localUtils.API.getApiQuery(`settings/?type=theme`))
.set('Origin', config.get('url'))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.then((res) => {
should.not.exist(res.headers['x-cache-invalidate']);
const jsonResponse = res.body;
should.exist(jsonResponse.settings);
should.exist(jsonResponse.meta);
jsonResponse.settings.should.be.an.Object();
const settings = jsonResponse.settings;
Object.keys(settings).length.should.equal(1);
settings[0].key.should.equal('active_theme');
settings[0].value.should.equal('casper');
settings[0].type.should.equal('theme');
localUtils.API.checkResponse(jsonResponse, 'settings');
});
});
it('Can\'t read core setting', function () {
return request
.get(localUtils.API.getApiQuery('settings/db_hash/'))
.set('Origin', config.get('url'))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(403);
});
it('Can\'t read permalinks', function (done) {
request.get(localUtils.API.getApiQuery('settings/permalinks/'))
.set('Origin', config.get('url'))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(404)
.end(function (err, res) {
if (err) {
return done(err);
}
done();
});
});
it('Can read deprecated default_locale', function (done) {
request.get(localUtils.API.getApiQuery('settings/default_locale/'))
.set('Origin', config.get('url'))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
should.not.exist(res.headers['x-cache-invalidate']);
const jsonResponse = res.body;
should.exist(jsonResponse);
should.exist(jsonResponse.settings);
jsonResponse.settings.length.should.eql(1);
testUtils.API.checkResponseValue(jsonResponse.settings[0], ['id', 'group', 'key', 'value', 'type', 'flags', 'created_at', 'updated_at']);
jsonResponse.settings[0].key.should.eql('default_locale');
done();
});
});
it('can edit deprecated default_locale setting', function () {
return request.get(localUtils.API.getApiQuery('settings/default_locale/'))
.set('Origin', config.get('url'))
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.then(function (res) {
let jsonResponse = res.body;
const newValue = 'new value';
should.exist(jsonResponse);
should.exist(jsonResponse.settings);
jsonResponse.settings = [{key: 'default_locale', value: 'ua'}];
return jsonResponse;
})
.then((editedSetting) => {
return request.put(localUtils.API.getApiQuery('settings/'))
.set('Origin', config.get('url'))
.send(editedSetting)
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.then(function (res) {
should.exist(res.headers['x-cache-invalidate']);
const jsonResponse = res.body;
should.exist(jsonResponse);
should.exist(jsonResponse.settings);
jsonResponse.settings.length.should.eql(1);
testUtils.API.checkResponseValue(jsonResponse.settings[0], ['id', 'group', 'key', 'value', 'type', 'flags', 'created_at', 'updated_at']);
jsonResponse.settings[0].key.should.eql('default_locale');
jsonResponse.settings[0].value.should.eql('ua');
});
});
});
it('Can read timezone', function (done) {
request.get(localUtils.API.getApiQuery('settings/timezone/'))
.set('Origin', config.get('url'))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
should.not.exist(res.headers['x-cache-invalidate']);
const jsonResponse = res.body;
should.exist(jsonResponse);
should.exist(jsonResponse.settings);
jsonResponse.settings.length.should.eql(1);
testUtils.API.checkResponseValue(jsonResponse.settings[0], ['id', 'group', 'key', 'value', 'type', 'flags', 'created_at', 'updated_at']);
jsonResponse.settings[0].key.should.eql('timezone');
done();
});
});
it('Can read active_timezone', function (done) {
request.get(localUtils.API.getApiQuery('settings/active_timezone/'))
.set('Origin', config.get('url'))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
should.not.exist(res.headers['x-cache-invalidate']);
const jsonResponse = res.body;
should.exist(jsonResponse);
should.exist(jsonResponse.settings);
jsonResponse.settings.length.should.eql(1);
testUtils.API.checkResponseValue(jsonResponse.settings[0], ['id', 'group', 'key', 'value', 'type', 'flags', 'created_at', 'updated_at']);
jsonResponse.settings[0].key.should.eql('active_timezone');
done();
});
});
it('Can read deprecated active_timezone', function (done) {
request.get(localUtils.API.getApiQuery('settings/active_timezone/'))
.set('Origin', config.get('url'))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
should.not.exist(res.headers['x-cache-invalidate']);
const jsonResponse = res.body;
should.exist(jsonResponse);
should.exist(jsonResponse.settings);
jsonResponse.settings.length.should.eql(1);
testUtils.API.checkResponseValue(jsonResponse.settings[0], ['id', 'group', 'key', 'value', 'type', 'flags', 'created_at', 'updated_at']);
jsonResponse.settings[0].key.should.eql('active_timezone');
done();
});
});
it('can\'t read non existent setting', function (done) {
request.get(localUtils.API.getApiQuery('settings/testsetting/'))
.set('Origin', config.get('url'))
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(404)
.end(function (err, res) {
if (err) {
return done(err);
}
should.not.exist(res.headers['x-cache-invalidate']);
const jsonResponse = res.body;
should.exist(jsonResponse);
should.exist(jsonResponse.errors);
testUtils.API.checkResponseValue(jsonResponse.errors[0], [
'message',
'context',
'type',
'details',
'property',
'help',
'code',
'id'
]);
done();
});
});
it('can toggle member setting', function () {
return request.get(localUtils.API.getApiQuery('settings/'))
.set('Origin', config.get('url'))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.then(function (res) {
const jsonResponse = res.body;
const settingToChange = {
settings: [
{
key: 'labs',
value: '{"subscribers":false,"members":false}'
}
]
};
should.exist(jsonResponse);
should.exist(jsonResponse.settings);
return request.put(localUtils.API.getApiQuery('settings/'))
.set('Origin', config.get('url'))
.send(settingToChange)
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.then(function ({body, headers}) {
const putBody = body;
headers['x-cache-invalidate'].should.eql('/*');
should.exist(putBody);
putBody.settings[0].key.should.eql('labs');
putBody.settings[0].value.should.eql(JSON.stringify({subscribers: false, members: false}));
});
});
});
it('can\'t edit permalinks', function (done) {
const settingToChange = {
settings: [{key: 'permalinks', value: '/:primary_author/:slug/'}]
};
request.put(localUtils.API.getApiQuery('settings/'))
.set('Origin', config.get('url'))
.send(settingToChange)
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(404)
.end(function (err, res) {
if (err) {
return done(err);
}
done();
});
});
it('can\'t edit non existent setting', function () {
return request.get(localUtils.API.getApiQuery('settings/'))
.set('Origin', config.get('url'))
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.then(function (res) {
let jsonResponse = res.body;
const newValue = 'new value';
should.exist(jsonResponse);
should.exist(jsonResponse.settings);
jsonResponse.settings = [{key: 'testvalue', value: newValue}];
return request.put(localUtils.API.getApiQuery('settings/'))
.set('Origin', config.get('url'))
.send(jsonResponse)
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(404)
.then(function ({body, headers}) {
jsonResponse = body;
should.not.exist(headers['x-cache-invalidate']);
should.exist(jsonResponse.errors);
testUtils.API.checkResponseValue(jsonResponse.errors[0], [
'message',
'context',
'type',
'details',
'property',
'help',
'code',
'id'
]);
});
});
});
it('Will transform "1"', function () {
return request.get(localUtils.API.getApiQuery('settings/'))
.set('Origin', config.get('url'))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.then(function (res) {
const jsonResponse = res.body;
const settingToChange = {
settings: [
{
key: 'is_private',
value: '1'
}
]
};
should.exist(jsonResponse);
should.exist(jsonResponse.settings);
return request.put(localUtils.API.getApiQuery('settings/'))
.set('Origin', config.get('url'))
.send(settingToChange)
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.then(function ({body, headers}) {
const putBody = body;
headers['x-cache-invalidate'].should.eql('/*');
should.exist(putBody);
putBody.settings[0].key.should.eql('is_private');
putBody.settings[0].value.should.eql(true);
localUtils.API.checkResponse(putBody, 'settings');
});
});
});
});
describe('As Admin', function () {
before(function () {
return ghost()
.then(function (_ghostServer) {
ghostServer = _ghostServer;
request = supertest.agent(config.get('url'));
})
.then(function () {
// create admin
return testUtils.createUser({
user: testUtils.DataGenerator.forKnex.createUser({email: 'admin+1@ghost.org'}),
role: testUtils.DataGenerator.Content.roles[0].name
});
})
.then(function (admin) {
request.user = admin;
// by default we login with the owner
return localUtils.doAuth(request);
});
});
it('cannot toggle member setting', function (done) {
const settingToChange = {
settings: [
{
key: 'labs',
value: '{"subscribers":false,"members":true}'
}
]
};
request.put(localUtils.API.getApiQuery('settings/'))
.set('Origin', config.get('url'))
.send(settingToChange)
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(403)
.end(function (err, res) {
if (err) {
return done(err);
}
done();
});
});
});
describe('As Editor', function () {
let editor;
before(function () {
return ghost()
.then(function (_ghostServer) {
ghostServer = _ghostServer;
request = supertest.agent(config.get('url'));
})
.then(function () {
// create editor
return testUtils.createUser({
user: testUtils.DataGenerator.forKnex.createUser({email: 'test+1@ghost.org'}),
role: testUtils.DataGenerator.Content.roles[1].name
});
})
.then(function (_user1) {
editor = _user1;
request.user = editor;
// by default we login with the owner
return localUtils.doAuth(request);
});
});
it('should not be able to edit settings', function () {
return request.get(localUtils.API.getApiQuery('settings/'))
.set('Origin', config.get('url'))
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.then(function (res) {
let jsonResponse = res.body;
should.exist(jsonResponse);
should.exist(jsonResponse.settings);
jsonResponse.settings = [{key: 'visibility', value: 'public'}];
return request.put(localUtils.API.getApiQuery('settings/'))
.set('Origin', config.get('url'))
.send(jsonResponse)
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(403)
.then(function ({body, headers}) {
jsonResponse = body;
should.not.exist(headers['x-cache-invalidate']);
should.exist(jsonResponse.errors);
testUtils.API.checkResponseValue(jsonResponse.errors[0], [
'message',
'context',
'type',
'details',
'property',
'help',
'code',
'id'
]);
});
});
});
});
describe('As Author', function () {
before(function () {
return ghost()
.then(function (_ghostServer) {
ghostServer = _ghostServer;
request = supertest.agent(config.get('url'));
})
.then(function () {
// create author
return testUtils.createUser({
user: testUtils.DataGenerator.forKnex.createUser({email: 'test+2@ghost.org'}),
role: testUtils.DataGenerator.Content.roles[2].name
});
})
.then(function (author) {
request.user = author;
// by default we login with the owner
return localUtils.doAuth(request);
});
});
it('should not be able to edit settings', function () {
return request.get(localUtils.API.getApiQuery('settings/'))
.set('Origin', config.get('url'))
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.then(function (res) {
let jsonResponse = res.body;
const newValue = 'new value';
should.exist(jsonResponse);
should.exist(jsonResponse.settings);
jsonResponse.settings = [{key: 'visibility', value: 'public'}];
return request.put(localUtils.API.getApiQuery('settings/'))
.set('Origin', config.get('url'))
.send(jsonResponse)
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(403)
.then(function ({body, headers}) {
jsonResponse = body;
should.not.exist(headers['x-cache-invalidate']);
should.exist(jsonResponse.errors);
testUtils.API.checkResponseValue(jsonResponse.errors[0], [
'message',
'context',
'type',
'details',
'property',
'help',
'code',
'id'
]);
});
});
});
});
});

View file

@ -1,48 +0,0 @@
const should = require('should');
const supertest = require('supertest');
const sinon = require('sinon');
const testUtils = require('../../../../utils');
const localUtils = require('./utils');
const config = require('../../../../../core/shared/config');
const {events} = require('../../../../../core/server/lib/common');
const ghost = testUtils.startGhost;
let request;
describe('Slack API', function () {
let ghostServer;
before(function () {
return ghost()
.then(function (_ghostServer) {
ghostServer = _ghostServer;
request = supertest.agent(config.get('url'));
})
.then(function () {
return localUtils.doAuth(request);
});
});
after(function () {
sinon.restore();
});
it('Can post slack test', function (done) {
const eventSpy = sinon.spy(events, 'emit');
request.post(localUtils.API.getApiQuery('slack/test/'))
.set('Origin', config.get('url'))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
should.not.exist(res.headers['x-cache-invalidate']);
const jsonResponse = res.body;
should.exist(jsonResponse);
eventSpy.calledWith('slack.test').should.be.true();
done();
});
});
});

View file

@ -1,295 +0,0 @@
const should = require('should');
const supertest = require('supertest');
const ObjectId = require('bson-objectid');
const testUtils = require('../../../../utils');
const config = require('../../../../../core/shared/config');
const localUtils = require('./utils');
const ghost = testUtils.startGhost;
let request;
describe('User API', function () {
let editor;
let author;
let ghostServer;
let otherAuthor;
let admin;
describe('As Owner', function () {
before(function () {
return ghost()
.then(function (_ghostServer) {
ghostServer = _ghostServer;
request = supertest.agent(config.get('url'));
})
.then(function () {
// create inactive user
return testUtils.createUser({
user: testUtils.DataGenerator.forKnex.createUser({email: 'test+3@ghost.org'}),
role: testUtils.DataGenerator.Content.roles[2].name
});
})
.then(function (_user) {
otherAuthor = _user;
// create admin user
return testUtils.createUser({
user: testUtils.DataGenerator.forKnex.createUser({email: 'test+admin@ghost.org', slug: 'owner'}),
role: testUtils.DataGenerator.Content.roles[3].name
});
})
.then(function (_user) {
admin = _user;
// by default we login with the owner
return localUtils.doAuth(request);
});
});
describe('Read', function () {
it('can\'t retrieve non existent user by id', function (done) {
request.get(localUtils.API.getApiQuery('users/' + ObjectId.generate() + '/'))
.set('Origin', config.get('url'))
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(404)
.end(function (err, res) {
if (err) {
return done(err);
}
should.not.exist(res.headers['x-cache-invalidate']);
const jsonResponse = res.body;
should.exist(jsonResponse);
should.exist(jsonResponse.errors);
testUtils.API.checkResponseValue(jsonResponse.errors[0], [
'message',
'context',
'type',
'details',
'property',
'help',
'code',
'id'
]);
done();
});
});
it('can\'t retrieve non existent user by slug', function (done) {
request.get(localUtils.API.getApiQuery('users/slug/blargh/'))
.set('Origin', config.get('url'))
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(404)
.end(function (err, res) {
if (err) {
return done(err);
}
should.not.exist(res.headers['x-cache-invalidate']);
const jsonResponse = res.body;
should.exist(jsonResponse);
should.exist(jsonResponse.errors);
testUtils.API.checkResponseValue(jsonResponse.errors[0], [
'message',
'context',
'type',
'details',
'property',
'help',
'code',
'id'
]);
done();
});
});
});
describe('Edit', function () {
it('can change the other users password', function (done) {
request.put(localUtils.API.getApiQuery('users/password/'))
.set('Origin', config.get('url'))
.send({
password: [{
newPassword: 'superSecure',
ne2Password: 'superSecure',
user_id: otherAuthor.id
}]
})
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.end(function (err) {
if (err) {
return done(err);
}
done();
});
});
});
describe('Destroy', function () {
it('[failure] Destroy unknown user id', function (done) {
request.delete(localUtils.API.getApiQuery('users/' + ObjectId.generate()))
.set('Origin', config.get('url'))
.expect(404)
.end(function (err) {
if (err) {
return done(err);
}
done();
});
});
});
});
describe('As Editor', function () {
before(function () {
return ghost()
.then(function (_ghostServer) {
ghostServer = _ghostServer;
request = supertest.agent(config.get('url'));
})
.then(function () {
// create editor
return testUtils.createUser({
user: testUtils.DataGenerator.forKnex.createUser({email: 'test+1@ghost.org'}),
role: testUtils.DataGenerator.Content.roles[1].name
});
})
.then(function (_user1) {
editor = _user1;
request.user = editor;
// by default we login with the owner
return localUtils.doAuth(request);
});
});
describe('success cases', function () {
it('can edit himself', function (done) {
request.put(localUtils.API.getApiQuery('users/' + editor.id + '/'))
.set('Origin', config.get('url'))
.send({
users: [{id: editor.id, name: 'test'}]
})
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.end(function (err) {
if (err) {
return done(err);
}
done();
});
});
});
describe('error cases', function () {
it('can\'t edit the owner', function (done) {
request.put(localUtils.API.getApiQuery('users/' + testUtils.DataGenerator.Content.users[0].id + '/'))
.set('Origin', config.get('url'))
.send({
users: [{
id: testUtils.DataGenerator.Content.users[0].id
}]
})
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(403)
.end(function (err) {
if (err) {
return done(err);
}
done();
});
});
it('Cannot transfer ownership to any other user', function () {
return request
.put(localUtils.API.getApiQuery('users/owner'))
.set('Origin', config.get('url'))
.send({
owner: [{
id: testUtils.existingData.users[1].id
}]
})
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(403);
});
});
});
describe('As Author', function () {
before(function () {
return ghost()
.then(function (_ghostServer) {
ghostServer = _ghostServer;
request = supertest.agent(config.get('url'));
})
.then(function () {
// create author
return testUtils.createUser({
user: testUtils.DataGenerator.forKnex.createUser({email: 'test+2@ghost.org'}),
role: testUtils.DataGenerator.Content.roles[2].name
});
})
.then(function (_user2) {
author = _user2;
request.user = author;
// by default we login with the owner
return localUtils.doAuth(request);
});
});
describe('success cases', function () {
it('can edit himself', function (done) {
request.put(localUtils.API.getApiQuery('users/' + author.id + '/'))
.set('Origin', config.get('url'))
.send({
users: [{id: author.id, name: 'test'}]
})
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.end(function (err) {
if (err) {
return done(err);
}
done();
});
});
});
describe('error cases', function () {
it('can\'t edit the owner', function (done) {
request.put(localUtils.API.getApiQuery('users/' + testUtils.DataGenerator.Content.users[0].id + '/'))
.set('Origin', config.get('url'))
.send({
users: [{
id: testUtils.DataGenerator.Content.users[0].id
}]
})
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(403)
.end(function (err) {
if (err) {
return done(err);
}
done();
});
});
});
});
});

View file

@ -1,124 +0,0 @@
const url = require('url');
const _ = require('lodash');
const testUtils = require('../../../../utils');
const schema = require('../../../../../core/server/data/schema').tables;
const API_URL = '/ghost/api/v3/admin/';
const 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'],
invites: ['invites', 'meta'],
themes: ['themes'],
post: _(schema.posts)
.keys()
// by default we only return mobiledoc
.without('html', 'plaintext')
.without('visibility')
.without('locale')
.without('page')
.without('author_id', 'author')
// always returns computed properties
// primary_tag and primary_author properties are included
// only because authors and tags are always included
.concat('url', 'primary_tag', 'primary_author', 'excerpt')
.concat('authors', 'tags')
// returns meta fields from `posts_meta` schema
.concat(
..._(schema.posts_meta).keys().without('post_id', 'id')
)
.concat('send_email_when_published')
,
user: _(schema.users)
.keys()
.without('visibility')
.without('password')
.without('locale')
.concat('url')
,
tag: _(schema.tags)
.keys()
// unused field
.without('parent_id')
,
setting: _(schema.settings)
.keys()
,
subscriber: _(schema.subscribers)
.keys()
,
role: _(schema.roles)
.keys()
,
permission: _(schema.permissions)
.keys()
,
notification: ['type', 'message', 'status', 'id', 'dismissible', 'location', 'custom'],
theme: ['name', 'package', 'active'],
invite: _(schema.invites)
.keys()
.without('token')
,
webhook: _(schema.webhooks)
.keys()
};
_.each(expectedProperties, (value, key) => {
if (!value.__wrapped__) {
return;
}
/**
* @deprecated: x_by
*/
expectedProperties[key] = value
.without(
'created_by',
'updated_by',
'published_by'
)
.value();
});
module.exports = {
API: {
getApiQuery(route) {
return url.resolve(API_URL, route);
},
checkResponse(...args) {
this.expectedProperties = expectedProperties;
return testUtils.API.checkResponse.call(this, ...args);
}
},
doAuth(...args) {
return testUtils.API.doAuth(`${API_URL}session/`, ...args);
},
getValidAdminToken(endpoint, key) {
const jwt = require('jsonwebtoken');
key = key || testUtils.DataGenerator.Content.api_keys[0];
const JWT_OPTIONS = {
keyid: key.id,
algorithm: 'HS256',
expiresIn: '5m',
audience: endpoint
};
return jwt.sign(
{},
Buffer.from(key.secret, 'hex'),
JWT_OPTIONS
);
}
};

View file

@ -1,136 +0,0 @@
const should = require('should');
const supertest = require('supertest');
const testUtils = require('../../../../utils');
const config = require('../../../../../core/shared/config');
const localUtils = require('./utils');
const ghost = testUtils.startGhost;
describe('Webhooks API (v3)', function () {
let request;
before(function () {
return ghost()
.then(function () {
request = supertest.agent(config.get('url'));
})
.then(function () {
return localUtils.doAuth(request, 'api_keys', 'webhooks');
});
});
it('Can create a webhook using integration', function () {
let webhookData = {
event: 'test.create',
target_url: 'http://example.com/webhooks/test/extra/v3',
integration_id: 'ignore_me',
name: 'test',
secret: 'thisissecret',
api_version: 'v3'
};
return request.post(localUtils.API.getApiQuery('webhooks/'))
.set('Authorization', `Ghost ${localUtils.getValidAdminToken('/v3/admin/', testUtils.DataGenerator.Content.api_keys[0])}`)
.send({webhooks: [webhookData]})
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(201)
.then((res) => {
should.not.exist(res.headers['x-cache-invalidate']);
const jsonResponse = res.body;
should.exist(jsonResponse);
should.exist(jsonResponse.webhooks);
should.exist(jsonResponse.webhooks[0].event);
should.exist(jsonResponse.webhooks[0].target_url);
jsonResponse.webhooks[0].event.should.eql('test.create');
jsonResponse.webhooks[0].target_url.should.eql('http://example.com/webhooks/test/extra/v3');
jsonResponse.webhooks[0].integration_id.should.eql(testUtils.DataGenerator.Content.api_keys[0].integration_id);
localUtils.API.checkResponse(jsonResponse.webhooks[0], 'webhook');
});
});
it('Integration cannot edit or delete other integration\'s webhook', function () {
let createdIntegration;
let createdWebhook;
return Promise.resolve()
.then(() => {
return request.post(localUtils.API.getApiQuery('integrations/'))
.set('Origin', config.get('url'))
.send({
integrations: [{
name: 'Rubbish Integration Name'
}]
})
.expect(201)
.then(({body}) => {
[createdIntegration] = body.integrations;
return request.post(localUtils.API.getApiQuery('webhooks/'))
.set('Origin', config.get('url'))
.send({
webhooks: [{
name: 'Testing',
event: 'site.changed',
target_url: 'https://example.com/rebuild',
integration_id: createdIntegration.id
}]
})
.expect(201);
});
})
.then(({body}) => {
[createdWebhook] = body.webhooks;
return request.put(localUtils.API.getApiQuery(`webhooks/${createdWebhook.id}/`))
.set('Authorization', `Ghost ${localUtils.getValidAdminToken('/v3/admin/', testUtils.DataGenerator.Content.api_keys[0])}`)
.send({
webhooks: [{
name: 'Edit Test',
event: 'subscriber.added',
target_url: 'https://example.com/new-subscriber'
}]
})
.expect(403);
})
.then(() => {
return request.del(localUtils.API.getApiQuery(`webhooks/${createdWebhook.id}/`))
.set('Authorization', `Ghost ${localUtils.getValidAdminToken('/v3/admin/', testUtils.DataGenerator.Content.api_keys[0])}`)
.expect(403);
});
});
it('Integration editing non-existing webhook returns 404', function () {
return request.put(localUtils.API.getApiQuery(`webhooks/5f27d0287c75da744d8615da/`))
.set('Authorization', `Ghost ${localUtils.getValidAdminToken('/v3/admin/', testUtils.DataGenerator.Content.api_keys[0])}`)
.send({
webhooks: [{
name: 'Edit Test'
}]
})
.expect(404);
});
it('Integration deleting non-existing webhook returns 404', function () {
return request.delete(localUtils.API.getApiQuery(`webhooks/5f27d0287c75da744d8615db/`))
.set('Authorization', `Ghost ${localUtils.getValidAdminToken('/v3/admin/', testUtils.DataGenerator.Content.api_keys[0])}`)
.expect(404);
});
it('Cannot edit webhooks using content api keys', function () {
let webhookData = {
event: 'post.create',
target_url: 'http://example.com/webhooks/test/extra/2'
};
return request.post(localUtils.API.getApiQuery('webhooks/'))
.set('Authorization', `Ghost ${localUtils.getValidAdminToken('/v3/admin/', testUtils.DataGenerator.Content.api_keys[1])}`)
.send({webhooks: [webhookData]})
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(401);
});
});

View file

@ -1,41 +0,0 @@
const should = require('should');
const supertest = require('supertest');
const localUtils = require('./utils');
const testUtils = require('../../../../utils');
const configUtils = require('../../../../utils/configUtils');
const config = require('../../../../../core/shared/config');
const ghost = testUtils.startGhost;
describe('Authors Content API', function () {
const validKey = localUtils.getValidKey();
let request;
before(function () {
return ghost()
.then(function (_ghostServer) {
request = supertest.agent(config.get('url'));
})
.then(function () {
return testUtils.initFixtures('owner:post', 'users:no-owner', 'user:inactive', 'posts', 'api_keys');
});
});
afterEach(function () {
configUtils.restore();
});
it('can read authors with fields', function () {
return request.get(localUtils.API.getApiQuery(`authors/1/?key=${validKey}&fields=name`))
.set('Origin', testUtils.API.getURL())
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.then((res) => {
should.not.exist(res.headers['x-cache-invalidate']);
// We don't expose any other attrs.
localUtils.API.checkResponse(res.body.authors[0], 'author', null, null, ['id', 'name']);
});
});
});

View file

@ -1,56 +0,0 @@
const should = require('should');
const supertest = require('supertest');
const testUtils = require('../../../../utils');
const localUtils = require('./utils');
const configUtils = require('../../../../utils/configUtils');
const config = require('../../../../../core/shared/config');
const ghost = testUtils.startGhost;
let request;
describe('api/v3/content/pages', function () {
before(function () {
return ghost()
.then(function () {
request = supertest.agent(config.get('url'));
})
.then(function () {
return testUtils.initFixtures('users:no-owner', 'user:inactive', 'posts', 'tags:extra', 'api_keys');
});
});
afterEach(function () {
configUtils.restore();
});
it('Can browse pages with page:false', function () {
const key = localUtils.getValidKey();
return request.get(localUtils.API.getApiQuery(`pages/?key=${key}&filter=page:false`))
.set('Origin', testUtils.API.getURL())
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.then((res) => {
res.headers.vary.should.eql('Accept-Encoding');
should.exist(res.headers['access-control-allow-origin']);
should.not.exist(res.headers['x-cache-invalidate']);
const jsonResponse = res.body;
should.exist(jsonResponse.pages);
should.exist(jsonResponse.meta);
jsonResponse.pages.should.have.length(0);
});
});
it('can\'t read post', function () {
const key = localUtils.getValidKey();
return request
.get(localUtils.API.getApiQuery(`pages/${testUtils.DataGenerator.Content.posts[0].id}/?key=${key}`))
.set('Origin', testUtils.API.getURL())
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(404);
});
});

View file

@ -1,377 +0,0 @@
const should = require('should');
const sinon = require('sinon');
const moment = require('moment');
const supertest = require('supertest');
const _ = require('lodash');
const labs = require('../../../../../core/server/services/labs');
const testUtils = require('../../../../utils');
const localUtils = require('./utils');
const configUtils = require('../../../../utils/configUtils');
const urlUtils = require('../../../../utils/urlUtils');
const config = require('../../../../../core/shared/config');
const ghost = testUtils.startGhost;
let request;
describe('api/v3/content/posts', function () {
before(function () {
return ghost()
.then(function () {
request = supertest.agent(config.get('url'));
})
.then(function () {
return testUtils.initFixtures('users:no-owner', 'user:inactive', 'posts', 'tags:extra', 'api_keys');
});
});
afterEach(function () {
configUtils.restore();
urlUtils.restore();
});
const validKey = localUtils.getValidKey();
it('browse posts', function (done) {
request.get(localUtils.API.getApiQuery(`posts/?key=${validKey}`))
.set('Origin', testUtils.API.getURL())
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
res.headers.vary.should.eql('Accept-Encoding');
should.exist(res.headers['access-control-allow-origin']);
should.not.exist(res.headers['x-cache-invalidate']);
const jsonResponse = res.body;
should.exist(jsonResponse.posts);
localUtils.API.checkResponse(jsonResponse, 'posts');
jsonResponse.posts.should.have.length(11);
localUtils.API.checkResponse(jsonResponse.posts[0], 'post');
localUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination');
_.isBoolean(jsonResponse.posts[0].featured).should.eql(true);
// Default order 'published_at desc' check
jsonResponse.posts[0].slug.should.eql('welcome');
jsonResponse.posts[6].slug.should.eql('themes');
// check meta response for this test
jsonResponse.meta.pagination.page.should.eql(1);
jsonResponse.meta.pagination.limit.should.eql(15);
jsonResponse.meta.pagination.pages.should.eql(1);
jsonResponse.meta.pagination.total.should.eql(11);
jsonResponse.meta.pagination.hasOwnProperty('next').should.be.true();
jsonResponse.meta.pagination.hasOwnProperty('prev').should.be.true();
should.not.exist(jsonResponse.meta.pagination.next);
should.not.exist(jsonResponse.meta.pagination.prev);
done();
});
});
it('browse posts with related authors/tags also returns primary_author/primary_tag', function (done) {
request.get(localUtils.API.getApiQuery(`posts/?key=${validKey}&include=authors,tags`))
.set('Origin', testUtils.API.getURL())
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
res.headers.vary.should.eql('Accept-Encoding');
should.exist(res.headers['access-control-allow-origin']);
should.not.exist(res.headers['x-cache-invalidate']);
const jsonResponse = res.body;
should.exist(jsonResponse.posts);
localUtils.API.checkResponse(jsonResponse, 'posts');
jsonResponse.posts.should.have.length(11);
localUtils.API.checkResponse(
jsonResponse.posts[0],
'post',
['authors', 'tags', 'primary_tag', 'primary_author'],
null
);
localUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination');
_.isBoolean(jsonResponse.posts[0].featured).should.eql(true);
// Default order 'published_at desc' check
jsonResponse.posts[0].slug.should.eql('welcome');
jsonResponse.posts[6].slug.should.eql('themes');
// check meta response for this test
jsonResponse.meta.pagination.page.should.eql(1);
jsonResponse.meta.pagination.limit.should.eql(15);
jsonResponse.meta.pagination.pages.should.eql(1);
jsonResponse.meta.pagination.total.should.eql(11);
jsonResponse.meta.pagination.hasOwnProperty('next').should.be.true();
jsonResponse.meta.pagination.hasOwnProperty('prev').should.be.true();
should.not.exist(jsonResponse.meta.pagination.next);
should.not.exist(jsonResponse.meta.pagination.prev);
done();
});
});
it('browse posts with basic page filter should not return pages', function (done) {
request.get(localUtils.API.getApiQuery(`posts/?key=${validKey}&filter=page:true`))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
const jsonResponse = res.body;
should.not.exist(res.headers['x-cache-invalidate']);
should.exist(jsonResponse.posts);
localUtils.API.checkResponse(jsonResponse, 'posts');
localUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination');
jsonResponse.posts.should.have.length(0);
done();
});
});
it('browse posts with basic page filter should not return pages', function (done) {
request.get(localUtils.API.getApiQuery(`posts/?key=${validKey}&filter=page:true,featured:true`))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
const jsonResponse = res.body;
should.not.exist(res.headers['x-cache-invalidate']);
should.exist(jsonResponse.posts);
localUtils.API.checkResponse(jsonResponse, 'posts');
localUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination');
jsonResponse.posts.should.have.length(2);
jsonResponse.posts.filter(p => (p.page === true)).should.have.length(0);
done();
});
});
it('browse posts with published and draft status, should not return drafts', function (done) {
request.get(localUtils.API.getApiQuery(`posts/?key=${validKey}&filter=status:published,status:draft`))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
const jsonResponse = res.body;
jsonResponse.posts.should.be.an.Array().with.lengthOf(11);
done();
});
});
it('ensure origin header on redirect is not getting lost', function (done) {
// NOTE: force a redirect to the admin url
configUtils.set('admin:url', 'http://localhost:9999');
urlUtils.stubUrlUtilsFromConfig();
request.get(localUtils.API.getApiQuery(`posts?key=${validKey}`))
.set('Origin', 'https://example.com')
// 301 Redirects _should_ be cached
.expect('Cache-Control', testUtils.cacheRules.year)
.expect(301)
.end(function (err, res) {
if (err) {
return done(err);
}
res.headers.vary.should.eql('Accept, Accept-Encoding');
res.headers.location.should.eql(`http://localhost:9999/ghost/api/v3/content/posts/?key=${validKey}`);
should.exist(res.headers['access-control-allow-origin']);
should.not.exist(res.headers['x-cache-invalidate']);
done();
});
});
it('can\'t read page', function () {
return request
.get(localUtils.API.getApiQuery(`posts/${testUtils.DataGenerator.Content.posts[5].id}/?key=${validKey}`))
.set('Origin', testUtils.API.getURL())
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(404);
});
it('can read post with fields', function () {
return request
.get(localUtils.API.getApiQuery(`posts/${testUtils.DataGenerator.Content.posts[0].id}/?key=${validKey}&fields=title,slug`))
.set('Origin', testUtils.API.getURL())
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.then((res) => {
localUtils.API.checkResponse(res.body.posts[0], 'post', null, null, ['id', 'title', 'slug']);
});
});
describe('content gating', function () {
let publicPost;
let membersPost;
let paidPost;
before(function () {
// NOTE: ideally this would be set through Admin API request not a stub
sinon.stub(labs, 'isSet').withArgs('members').returns(true);
});
before (function () {
publicPost = testUtils.DataGenerator.forKnex.createPost({
slug: 'free-to-see',
visibility: 'public',
published_at: moment().add(15, 'seconds').toDate() // here to ensure sorting is not modified
});
membersPost = testUtils.DataGenerator.forKnex.createPost({
slug: 'thou-shalt-not-be-seen',
visibility: 'members',
published_at: moment().add(45, 'seconds').toDate() // here to ensure sorting is not modified
});
paidPost = testUtils.DataGenerator.forKnex.createPost({
slug: 'thou-shalt-be-paid-for',
visibility: 'paid',
published_at: moment().add(30, 'seconds').toDate() // here to ensure sorting is not modified
});
return testUtils.fixtures.insertPosts([
publicPost,
membersPost,
paidPost
]);
});
it('public post fields are always visible', function () {
return request
.get(localUtils.API.getApiQuery(`posts/${publicPost.id}/?key=${validKey}&fields=slug,html,plaintext&formats=html,plaintext`))
.set('Origin', testUtils.API.getURL())
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.then((res) => {
const jsonResponse = res.body;
should.exist(jsonResponse.posts);
const post = jsonResponse.posts[0];
localUtils.API.checkResponse(post, 'post', null, null, ['id', 'slug', 'html', 'plaintext']);
post.slug.should.eql('free-to-see');
post.html.should.not.eql('');
post.plaintext.should.not.eql('');
});
});
it('cannot read members only post content', function () {
return request
.get(localUtils.API.getApiQuery(`posts/${membersPost.id}/?key=${validKey}`))
.set('Origin', testUtils.API.getURL())
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.then((res) => {
const jsonResponse = res.body;
should.exist(jsonResponse.posts);
const post = jsonResponse.posts[0];
localUtils.API.checkResponse(post, 'post', null, null);
post.slug.should.eql('thou-shalt-not-be-seen');
post.html.should.eql('');
});
});
it('cannot read paid only post content', function () {
return request
.get(localUtils.API.getApiQuery(`posts/${paidPost.id}/?key=${validKey}`))
.set('Origin', testUtils.API.getURL())
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.then((res) => {
const jsonResponse = res.body;
should.exist(jsonResponse.posts);
const post = jsonResponse.posts[0];
localUtils.API.checkResponse(post, 'post', null, null);
post.slug.should.eql('thou-shalt-be-paid-for');
post.html.should.eql('');
});
});
it('cannot read members only post plaintext', function () {
return request
.get(localUtils.API.getApiQuery(`posts/${membersPost.id}/?key=${validKey}&formats=html,plaintext&fields=html,plaintext`))
.set('Origin', testUtils.API.getURL())
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.then((res) => {
const jsonResponse = res.body;
should.exist(jsonResponse.posts);
const post = jsonResponse.posts[0];
localUtils.API.checkResponse(post, 'post', null, null, ['id', 'html', 'plaintext']);
post.html.should.eql('');
post.plaintext.should.eql('');
});
});
it('cannot browse members only posts content', function () {
return request.get(localUtils.API.getApiQuery(`posts/?key=${validKey}`))
.set('Origin', testUtils.API.getURL())
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.then((res) => {
res.headers.vary.should.eql('Accept-Encoding');
should.exist(res.headers['access-control-allow-origin']);
should.not.exist(res.headers['x-cache-invalidate']);
const jsonResponse = res.body;
should.exist(jsonResponse.posts);
localUtils.API.checkResponse(jsonResponse, 'posts');
jsonResponse.posts.should.have.length(14);
localUtils.API.checkResponse(jsonResponse.posts[0], 'post', null, null);
localUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination');
_.isBoolean(jsonResponse.posts[0].featured).should.eql(true);
// Default order 'published_at desc' check
jsonResponse.posts[0].slug.should.eql('thou-shalt-not-be-seen');
jsonResponse.posts[1].slug.should.eql('thou-shalt-be-paid-for');
jsonResponse.posts[2].slug.should.eql('free-to-see');
jsonResponse.posts[7].slug.should.eql('organising-content');
jsonResponse.posts[0].html.should.eql('');
jsonResponse.posts[1].html.should.eql('');
jsonResponse.posts[2].html.should.not.eql('');
jsonResponse.posts[7].html.should.not.eql('');
// check meta response for this test
jsonResponse.meta.pagination.page.should.eql(1);
jsonResponse.meta.pagination.limit.should.eql(15);
jsonResponse.meta.pagination.pages.should.eql(1);
jsonResponse.meta.pagination.total.should.eql(14);
jsonResponse.meta.pagination.hasOwnProperty('next').should.be.true();
jsonResponse.meta.pagination.hasOwnProperty('prev').should.be.true();
should.not.exist(jsonResponse.meta.pagination.next);
should.not.exist(jsonResponse.meta.pagination.prev);
});
});
});
});

View file

@ -1,87 +0,0 @@
const should = require('should');
const supertest = require('supertest');
const _ = require('lodash');
const localUtils = require('./utils');
const testUtils = require('../../../../utils');
const configUtils = require('../../../../utils/configUtils');
const config = require('../../../../../core/shared/config');
const ghost = testUtils.startGhost;
let request;
describe('api/v3/content/tags', function () {
before(function () {
return ghost()
.then(function () {
request = supertest.agent(config.get('url'));
})
.then(function () {
return testUtils.initFixtures('users:no-owner', 'user:inactive', 'posts', 'tags:extra', 'api_keys');
});
});
afterEach(function () {
configUtils.restore();
});
const validKey = localUtils.getValidKey();
it('Can read tags with fields', function () {
return request
.get(localUtils.API.getApiQuery(`tags/${testUtils.DataGenerator.Content.tags[0].id}/?key=${validKey}&fields=name,slug`))
.set('Origin', testUtils.API.getURL())
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.then((res) => {
localUtils.API.checkResponse(res.body.tags[0], 'tag', null, null, ['id', 'name', 'slug']);
});
});
it('Can request all tags with count.posts field', function () {
return request
.get(localUtils.API.getApiQuery(`tags/?key=${validKey}&include=count.posts`))
.set('Origin', testUtils.API.getURL())
.set('Origin', config.get('url'))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.then((res) => {
should.not.exist(res.headers['x-cache-invalidate']);
const jsonResponse = res.body;
should.exist(jsonResponse);
should.exist(jsonResponse.tags);
jsonResponse.tags.should.have.length(4);
localUtils.API.checkResponse(jsonResponse.tags[0], 'tag', ['count', 'url']);
jsonResponse.meta.pagination.should.have.property('page', 1);
jsonResponse.meta.pagination.should.have.property('limit', 15);
jsonResponse.meta.pagination.should.have.property('pages', 4);
jsonResponse.meta.pagination.should.have.property('total', 56);
jsonResponse.meta.pagination.should.have.property('next', 2);
jsonResponse.meta.pagination.should.have.property('prev', null);
should.exist(jsonResponse.tags[0].count.posts);
// Each tag should have the correct count
_.find(jsonResponse.tags, {name: 'Getting Started'}).count.posts.should.eql(7);
_.find(jsonResponse.tags, {name: 'kitchen sink'}).count.posts.should.eql(2);
_.find(jsonResponse.tags, {name: 'bacon'}).count.posts.should.eql(2);
_.find(jsonResponse.tags, {name: 'chorizo'}).count.posts.should.eql(1);
});
});
it('Browse tags with slug filter, should order in slug order', function () {
return request.get(localUtils.API.getApiQuery(`tags/?key=${validKey}&filter=slug:[kitchen-sink,bacon,chorizo]`))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.then((res) => {
const jsonResponse = res.body;
jsonResponse.tags.should.be.an.Array().with.lengthOf(3);
jsonResponse.tags[0].slug.should.equal('kitchen-sink');
jsonResponse.tags[1].slug.should.equal('bacon');
jsonResponse.tags[2].slug.should.equal('chorizo');
});
});
});

View file

@ -1,94 +0,0 @@
const url = require('url');
const _ = require('lodash');
const testUtils = require('../../../../utils');
const schema = require('../../../../../core/server/data/schema').tables;
const API_URL = '/ghost/api/v3/content/';
const expectedProperties = {
// API top level
posts: ['posts', 'meta'],
tags: ['tags', 'meta'],
authors: ['authors', 'meta'],
pagination: ['page', 'limit', 'pages', 'total', 'next', 'prev'],
post: _(schema.posts)
.keys()
// by default we only return html
.without('mobiledoc', 'plaintext')
// v3 doesn't return author_id OR author
.without('author_id', 'author')
// and always returns computed properties: url, primary_tag, primary_author
.concat('url')
// v3 API doesn't return unused fields
.without('locale')
// These fields aren't useful as they always have known values
.without('status')
// @TODO: https://github.com/TryGhost/Ghost/issues/10335
// .without('page')
.without('type')
// v3 returns a calculated excerpt field
.concat('excerpt')
// Access is a calculated property in >= v3
.concat('access')
// returns meta fields from `posts_meta` schema
.concat(
..._(schema.posts_meta).keys().without('post_id', 'id')
)
.concat('reading_time')
.concat('send_email_when_published')
,
author: _(schema.users)
.keys()
.without(
'password',
'email',
'created_at',
'created_by',
'updated_at',
'updated_by',
'last_seen',
'status'
)
// v3 API doesn't return unused fields
.without('accessibility', 'locale', 'tour', 'visibility')
,
tag: _(schema.tags)
.keys()
// v3 Tag API doesn't return parent_id or parent
.without('parent_id', 'parent')
// v3 Tag API doesn't return date fields
.without('created_at', 'updated_at')
};
_.each(expectedProperties, (value, key) => {
if (!value.__wrapped__) {
return;
}
/**
* @deprecated: x_by
*/
expectedProperties[key] = value
.without(
'created_by',
'updated_by',
'published_by'
)
.value();
});
module.exports = {
API: {
getApiQuery(route) {
return url.resolve(API_URL, route);
},
checkResponse(...args) {
this.expectedProperties = expectedProperties;
return testUtils.API.checkResponse.call(this, ...args);
}
},
getValidKey() {
return testUtils.DataGenerator.Content.api_keys[1].secret;
}
};