mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-03 08:25:06 +03:00
Added regression tests for the v3 endpoints
refs https://github.com/TryGhost/Team/issues/221
This commit is contained in:
parent
23e50c6ccc
commit
3127aac47c
398
test/regression/api/v3/admin/authentication_spec.js
Normal file
398
test/regression/api/v3/admin/authentication_spec.js
Normal file
@ -0,0 +1,398 @@
|
||||
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 and existing email address', 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.users[0].email,
|
||||
name: 'invited'
|
||||
}]
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(422);
|
||||
});
|
||||
|
||||
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.');
|
||||
res.body.errors[0].context.should.eql('Invalid password reset link.');
|
||||
});
|
||||
});
|
||||
|
||||
it('reset password: expired token', function () {
|
||||
return models.User.getOwnerUser(testUtils.context.internal)
|
||||
.then(function (ownerUser) {
|
||||
const dateInThePast = Date.now() - (1000 * 60);
|
||||
const token = security.tokens.resetToken.generateHash({
|
||||
expires: dateInThePast,
|
||||
email: user.email,
|
||||
dbHash: settingsCache.get('db_hash'),
|
||||
password: ownerUser.get('password')
|
||||
});
|
||||
|
||||
return 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(400);
|
||||
})
|
||||
.then((res) => {
|
||||
should.exist(res.body.errors);
|
||||
res.body.errors[0].type.should.eql('BadRequestError');
|
||||
res.body.errors[0].message.should.eql('Cannot reset password.');
|
||||
res.body.errors[0].context.should.eql('Password reset link expired.');
|
||||
});
|
||||
});
|
||||
|
||||
it('reset password: unmatched token', function () {
|
||||
const token = security.tokens.resetToken.generateHash({
|
||||
expires: Date.now() + (1000 * 60),
|
||||
email: user.email,
|
||||
dbHash: settingsCache.get('db_hash'),
|
||||
password: 'invalid_password'
|
||||
});
|
||||
|
||||
return 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(400)
|
||||
.then((res) => {
|
||||
should.exist(res.body.errors);
|
||||
res.body.errors[0].type.should.eql('BadRequestError');
|
||||
res.body.errors[0].message.should.eql('Cannot reset password.');
|
||||
res.body.errors[0].context.should.eql('Password reset link has already been used.');
|
||||
});
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
172
test/regression/api/v3/admin/db_spec.js
Normal file
172
test/regression/api/v3/admin/db_spec.js
Normal file
@ -0,0 +1,172 @@
|
||||
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(29);
|
||||
});
|
||||
});
|
||||
|
||||
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('export 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('export 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('export 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);
|
||||
});
|
||||
});
|
102
test/regression/api/v3/admin/identities_spec.js
Normal file
102
test/regression/api/v3/admin/identities_spec.js
Normal file
@ -0,0 +1,102 @@
|
||||
const should = require('should');
|
||||
const supertest = require('supertest');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const jwksClient = require('jwks-rsa');
|
||||
const testUtils = require('../../../../utils');
|
||||
const localUtils = require('./utils');
|
||||
const config = require('../../../../../core/shared/config');
|
||||
|
||||
const ghost = testUtils.startGhost;
|
||||
|
||||
let request;
|
||||
|
||||
const verifyJWKS = (endpoint, token) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const client = jwksClient({
|
||||
jwksUri: endpoint
|
||||
});
|
||||
|
||||
function getKey(header, callback){
|
||||
client.getSigningKey(header.kid, (err, key) => {
|
||||
let signingKey = key.publicKey || key.rsaPublicKey;
|
||||
callback(null, signingKey);
|
||||
});
|
||||
}
|
||||
|
||||
jwt.verify(token, getKey, {}, (err, decoded) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
|
||||
resolve(decoded);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
describe('Identities API', function () {
|
||||
describe('As Owner', function () {
|
||||
before(function () {
|
||||
return ghost()
|
||||
.then(function () {
|
||||
request = supertest.agent(config.get('url'));
|
||||
})
|
||||
.then(function () {
|
||||
return localUtils.doAuth(request);
|
||||
});
|
||||
});
|
||||
|
||||
it('Can create JWT token and verify it afterwards with public jwks', function () {
|
||||
let identity;
|
||||
|
||||
return request
|
||||
.get(localUtils.API.getApiQuery(`identities/`))
|
||||
.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.identities);
|
||||
|
||||
identity = jsonResponse.identities[0];
|
||||
})
|
||||
.then(() => {
|
||||
return verifyJWKS(`${request.app}/ghost/.well-known/jwks.json`, identity.token);
|
||||
})
|
||||
.then((decoded) => {
|
||||
decoded.sub.should.equal('jbloggs@example.com');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('As non-Owner', function () {
|
||||
before(function () {
|
||||
return ghost()
|
||||
.then(function (_ghostServer) {
|
||||
request = supertest.agent(config.get('url'));
|
||||
})
|
||||
.then(function () {
|
||||
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;
|
||||
|
||||
return localUtils.doAuth(request);
|
||||
});
|
||||
});
|
||||
|
||||
it('Cannot read', function () {
|
||||
return request
|
||||
.get(localUtils.API.getApiQuery(`identities/`))
|
||||
.set('Origin', config.get('url'))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(403);
|
||||
});
|
||||
});
|
||||
});
|
86
test/regression/api/v3/admin/images_spec.js
Normal file
86
test/regression/api/v3/admin/images_spec.js
Normal file
@ -0,0 +1,86 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
70
test/regression/api/v3/admin/labels_spec.js
Normal file
70
test/regression/api/v3/admin/labels_spec.js
Normal file
@ -0,0 +1,70 @@
|
||||
const path = require('path');
|
||||
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 ghost = testUtils.startGhost;
|
||||
|
||||
let request;
|
||||
|
||||
describe('Labels API', function () {
|
||||
after(function () {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
before(function () {
|
||||
return ghost()
|
||||
.then(function () {
|
||||
request = supertest.agent(config.get('url'));
|
||||
})
|
||||
.then(function () {
|
||||
return localUtils.doAuth(request);
|
||||
});
|
||||
});
|
||||
|
||||
it('Errors when adding label with the same name', function () {
|
||||
const label = {
|
||||
name: 'test'
|
||||
};
|
||||
|
||||
return request
|
||||
.post(localUtils.API.getApiQuery(`labels/`))
|
||||
.send({labels: [label]})
|
||||
.set('Origin', config.get('url'))
|
||||
.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.labels);
|
||||
jsonResponse.labels.should.have.length(1);
|
||||
|
||||
jsonResponse.labels[0].name.should.equal(label.name);
|
||||
jsonResponse.labels[0].slug.should.equal(label.name);
|
||||
})
|
||||
.then(() => {
|
||||
return request
|
||||
.post(localUtils.API.getApiQuery(`labels/`))
|
||||
.send({labels: [label]})
|
||||
.set('Origin', config.get('url'))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(422);
|
||||
})
|
||||
.then((res) => {
|
||||
should.not.exist(res.headers['x-cache-invalidate']);
|
||||
const jsonResponse = res.body;
|
||||
should.exist(jsonResponse);
|
||||
should.exist(jsonResponse.errors);
|
||||
jsonResponse.errors.should.have.length(1);
|
||||
|
||||
jsonResponse.errors[0].type.should.equal('ValidationError');
|
||||
jsonResponse.errors[0].context.should.equal('Label already exists');
|
||||
});
|
||||
});
|
||||
});
|
119
test/regression/api/v3/admin/members_signin_url_spec.js
Normal file
119
test/regression/api/v3/admin/members_signin_url_spec.js
Normal file
@ -0,0 +1,119 @@
|
||||
const path = require('path');
|
||||
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 labs = require('../../../../../core/server/services/labs');
|
||||
|
||||
const ghost = testUtils.startGhost;
|
||||
|
||||
let request;
|
||||
|
||||
describe('Members Sigin URL API', function () {
|
||||
before(function () {
|
||||
sinon.stub(labs, 'isSet').withArgs('members').returns(true);
|
||||
});
|
||||
|
||||
after(function () {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
describe('As Owner', function () {
|
||||
before(function () {
|
||||
return ghost()
|
||||
.then(function () {
|
||||
request = supertest.agent(config.get('url'));
|
||||
})
|
||||
.then(function () {
|
||||
return localUtils.doAuth(request, 'member');
|
||||
});
|
||||
});
|
||||
|
||||
it('Can read', function () {
|
||||
return request
|
||||
.get(localUtils.API.getApiQuery(`members/${testUtils.DataGenerator.Content.members[0].id}/signin_urls/`))
|
||||
.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.member_signin_urls);
|
||||
jsonResponse.member_signin_urls.should.have.length(1);
|
||||
localUtils.API.checkResponse(jsonResponse.member_signin_urls[0], 'member_signin_url');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('As Admin', function () {
|
||||
before(function () {
|
||||
return ghost()
|
||||
.then(function () {
|
||||
request = supertest.agent(config.get('url'));
|
||||
})
|
||||
.then(function () {
|
||||
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;
|
||||
|
||||
return localUtils.doAuth(request, 'member');
|
||||
});
|
||||
});
|
||||
|
||||
it('Can read', function () {
|
||||
return request
|
||||
.get(localUtils.API.getApiQuery(`members/${testUtils.DataGenerator.Content.members[0].id}/signin_urls/`))
|
||||
.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.member_signin_urls);
|
||||
jsonResponse.member_signin_urls.should.have.length(1);
|
||||
localUtils.API.checkResponse(jsonResponse.member_signin_urls[0], 'member_signin_url');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('As non-Owner and non-Admin', function () {
|
||||
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, 'member');
|
||||
});
|
||||
});
|
||||
|
||||
it('Cannot read', function () {
|
||||
return request
|
||||
.get(localUtils.API.getApiQuery(`members/${testUtils.DataGenerator.Content.members[0].id}/signin_urls/`))
|
||||
.set('Origin', config.get('url'))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(403);
|
||||
});
|
||||
});
|
||||
});
|
645
test/regression/api/v3/admin/members_spec.js
Normal file
645
test/regression/api/v3/admin/members_spec.js
Normal file
@ -0,0 +1,645 @@
|
||||
const path = require('path');
|
||||
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 labs = require('../../../../../core/server/services/labs');
|
||||
|
||||
const ghost = testUtils.startGhost;
|
||||
|
||||
let request;
|
||||
|
||||
describe('Members API', function () {
|
||||
before(function () {
|
||||
sinon.stub(labs, 'isSet').withArgs('members').returns(true);
|
||||
});
|
||||
|
||||
after(function () {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
before(function () {
|
||||
return ghost()
|
||||
.then(function () {
|
||||
request = supertest.agent(config.get('url'));
|
||||
})
|
||||
.then(function () {
|
||||
return localUtils.doAuth(request, 'members');
|
||||
});
|
||||
});
|
||||
|
||||
it('Can order by email_open_rate', async function () {
|
||||
await request
|
||||
.get(localUtils.API.getApiQuery('members/?order=email_open_rate%20desc'))
|
||||
.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.members);
|
||||
localUtils.API.checkResponse(jsonResponse, 'members');
|
||||
jsonResponse.members.should.have.length(4);
|
||||
|
||||
jsonResponse.members[0].email.should.equal('paid@test.com');
|
||||
jsonResponse.members[0].email_open_rate.should.equal(80);
|
||||
jsonResponse.members[1].email.should.equal('member2@test.com');
|
||||
jsonResponse.members[1].email_open_rate.should.equal(50);
|
||||
jsonResponse.members[2].email.should.equal('member1@test.com');
|
||||
should.equal(null, jsonResponse.members[2].email_open_rate);
|
||||
jsonResponse.members[3].email.should.equal('trialing@test.com');
|
||||
should.equal(null, jsonResponse.members[3].email_open_rate);
|
||||
});
|
||||
|
||||
await request
|
||||
.get(localUtils.API.getApiQuery('members/?order=email_open_rate%20asc'))
|
||||
.set('Origin', config.get('url'))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(200)
|
||||
.then((res) => {
|
||||
const jsonResponse = res.body;
|
||||
localUtils.API.checkResponse(jsonResponse, 'members');
|
||||
jsonResponse.members.should.have.length(4);
|
||||
|
||||
jsonResponse.members[0].email.should.equal('member2@test.com');
|
||||
jsonResponse.members[0].email_open_rate.should.equal(50);
|
||||
jsonResponse.members[1].email.should.equal('paid@test.com');
|
||||
jsonResponse.members[1].email_open_rate.should.equal(80);
|
||||
jsonResponse.members[2].email.should.equal('member1@test.com');
|
||||
should.equal(null, jsonResponse.members[2].email_open_rate);
|
||||
jsonResponse.members[3].email.should.equal('trialing@test.com');
|
||||
should.equal(null, jsonResponse.members[3].email_open_rate);
|
||||
});
|
||||
});
|
||||
|
||||
it('Can search by case-insensitive name', function () {
|
||||
return request
|
||||
.get(localUtils.API.getApiQuery('members/?search=egg'))
|
||||
.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.members);
|
||||
jsonResponse.members.should.have.length(1);
|
||||
jsonResponse.members[0].email.should.equal('member1@test.com');
|
||||
localUtils.API.checkResponse(jsonResponse, 'members');
|
||||
localUtils.API.checkResponse(jsonResponse.members[0], 'member', 'stripe');
|
||||
localUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination');
|
||||
});
|
||||
});
|
||||
|
||||
it('Can search by case-insensitive email', function () {
|
||||
return request
|
||||
.get(localUtils.API.getApiQuery('members/?search=MEMBER2'))
|
||||
.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.members);
|
||||
jsonResponse.members.should.have.length(1);
|
||||
jsonResponse.members[0].email.should.equal('member2@test.com');
|
||||
localUtils.API.checkResponse(jsonResponse, 'members');
|
||||
localUtils.API.checkResponse(jsonResponse.members[0], 'member', 'stripe');
|
||||
localUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination');
|
||||
});
|
||||
});
|
||||
|
||||
it('Can search for paid members', function () {
|
||||
return request
|
||||
.get(localUtils.API.getApiQuery('members/?search=egon&paid=true'))
|
||||
.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.members);
|
||||
jsonResponse.members.should.have.length(1);
|
||||
jsonResponse.members[0].email.should.equal('paid@test.com');
|
||||
localUtils.API.checkResponse(jsonResponse, 'members');
|
||||
localUtils.API.checkResponse(jsonResponse.members[0], 'member', 'stripe');
|
||||
localUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination');
|
||||
});
|
||||
});
|
||||
|
||||
it('Search for non existing member returns empty result set', function () {
|
||||
return request
|
||||
.get(localUtils.API.getApiQuery('members/?search=do_not_exist'))
|
||||
.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.members);
|
||||
jsonResponse.members.should.have.length(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('Add should fail when passing incorrect email_type query parameter', function () {
|
||||
const member = {
|
||||
name: 'test',
|
||||
email: 'memberTestAdd@test.com'
|
||||
};
|
||||
|
||||
return request
|
||||
.post(localUtils.API.getApiQuery(`members/?send_email=true&email_type=lel`))
|
||||
.send({members: [member]})
|
||||
.set('Origin', config.get('url'))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(422);
|
||||
});
|
||||
|
||||
it('Add should fail when comped flag is passed in but Stripe is not enabled', function () {
|
||||
const member = {
|
||||
email: 'memberTestAdd@test.com',
|
||||
comped: true
|
||||
};
|
||||
|
||||
return request
|
||||
.post(localUtils.API.getApiQuery(`members/`))
|
||||
.send({members: [member]})
|
||||
.set('Origin', config.get('url'))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(422)
|
||||
.then((res) => {
|
||||
const jsonResponse = res.body;
|
||||
|
||||
should.exist(jsonResponse);
|
||||
should.exist(jsonResponse.errors);
|
||||
|
||||
jsonResponse.errors[0].message.should.eql('Validation error, cannot save member.');
|
||||
jsonResponse.errors[0].context.should.match(/Missing Stripe connection./);
|
||||
});
|
||||
});
|
||||
|
||||
// NOTE: this test should be enabled and expanded once test suite fully supports Stripe mocking
|
||||
it.skip('Can set a "Complimentary" subscription', function () {
|
||||
const memberToChange = {
|
||||
name: 'Comped Member',
|
||||
email: 'member2comp@test.com'
|
||||
};
|
||||
|
||||
const memberChanged = {
|
||||
comped: true
|
||||
};
|
||||
|
||||
return request
|
||||
.post(localUtils.API.getApiQuery(`members/`))
|
||||
.send({members: [memberToChange]})
|
||||
.set('Origin', config.get('url'))
|
||||
.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.members);
|
||||
jsonResponse.members.should.have.length(1);
|
||||
|
||||
return jsonResponse.members[0];
|
||||
})
|
||||
.then((newMember) => {
|
||||
return request
|
||||
.put(localUtils.API.getApiQuery(`members/${newMember.id}/`))
|
||||
.send({members: [memberChanged]})
|
||||
.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.members);
|
||||
jsonResponse.members.should.have.length(1);
|
||||
localUtils.API.checkResponse(jsonResponse.members[0], 'member', 'stripe');
|
||||
jsonResponse.members[0].name.should.equal(memberToChange.name);
|
||||
jsonResponse.members[0].email.should.equal(memberToChange.email);
|
||||
jsonResponse.members[0].comped.should.equal(memberToChange.comped);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Can delete a member without cancelling Stripe Subscription', async function () {
|
||||
const member = {
|
||||
name: 'Member 2 Delete',
|
||||
email: 'Member2Delete@test.com'
|
||||
};
|
||||
|
||||
const createdMember = await request.post(localUtils.API.getApiQuery(`members/`))
|
||||
.send({members: [member]})
|
||||
.set('Origin', config.get('url'))
|
||||
.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.members);
|
||||
jsonResponse.members.should.have.length(1);
|
||||
|
||||
return jsonResponse.members[0];
|
||||
});
|
||||
|
||||
await request.delete(localUtils.API.getApiQuery(`members/${createdMember.id}/`))
|
||||
.set('Origin', config.get('url'))
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(204)
|
||||
.then((res) => {
|
||||
should.not.exist(res.headers['x-cache-invalidate']);
|
||||
|
||||
const jsonResponse = res.body;
|
||||
|
||||
should.exist(jsonResponse);
|
||||
});
|
||||
});
|
||||
|
||||
// NOTE: this test should be enabled and expanded once test suite fully supports Stripe mocking
|
||||
it.skip('Can delete a member and cancel Stripe Subscription', async function () {
|
||||
const member = {
|
||||
name: 'Member 2 Delete',
|
||||
email: 'Member2Delete@test.com',
|
||||
comped: true
|
||||
};
|
||||
|
||||
const createdMember = await request.post(localUtils.API.getApiQuery(`members/`))
|
||||
.send({members: [member]})
|
||||
.set('Origin', config.get('url'))
|
||||
.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.members);
|
||||
jsonResponse.members.should.have.length(1);
|
||||
|
||||
return jsonResponse.members[0];
|
||||
});
|
||||
|
||||
await request.delete(localUtils.API.getApiQuery(`members/${createdMember.id}/?cancel=true`))
|
||||
.set('Origin', config.get('url'))
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(204)
|
||||
.then((res) => {
|
||||
should.not.exist(res.headers['x-cache-invalidate']);
|
||||
|
||||
const jsonResponse = res.body;
|
||||
|
||||
should.exist(jsonResponse);
|
||||
});
|
||||
});
|
||||
|
||||
// NOTE: this test should be enabled and expanded once test suite fully supports Stripe mocking
|
||||
it.skip('Does not cancel Stripe Subscription if cancel_subscriptions is not set to "true"', async function () {
|
||||
const member = {
|
||||
name: 'Member 2 Delete',
|
||||
email: 'Member2Delete@test.com',
|
||||
comped: true
|
||||
};
|
||||
|
||||
const createdMember = await request.post(localUtils.API.getApiQuery(`members/`))
|
||||
.send({members: [member]})
|
||||
.set('Origin', config.get('url'))
|
||||
.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.members);
|
||||
jsonResponse.members.should.have.length(1);
|
||||
|
||||
return jsonResponse.members[0];
|
||||
});
|
||||
|
||||
await request.delete(localUtils.API.getApiQuery(`members/${createdMember.id}/?cancel=false`))
|
||||
.set('Origin', config.get('url'))
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(204)
|
||||
.then((res) => {
|
||||
should.not.exist(res.headers['x-cache-invalidate']);
|
||||
|
||||
const jsonResponse = res.body;
|
||||
|
||||
should.exist(jsonResponse);
|
||||
});
|
||||
});
|
||||
|
||||
it('Can import CSV with minimum one field and labels', function () {
|
||||
let importLabel;
|
||||
|
||||
return request
|
||||
.post(localUtils.API.getApiQuery(`members/upload/`))
|
||||
.field('labels', ['global-label-1', 'global-label-1'])
|
||||
.attach('membersfile', path.join(__dirname, '/../../../../utils/fixtures/csv/valid-members-labels.csv'))
|
||||
.set('Origin', config.get('url'))
|
||||
.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.meta);
|
||||
should.exist(jsonResponse.meta.stats);
|
||||
|
||||
should.exist(jsonResponse.meta.import_label);
|
||||
jsonResponse.meta.import_label.slug.should.match(/^import-/);
|
||||
jsonResponse.meta.stats.imported.should.equal(2);
|
||||
jsonResponse.meta.stats.invalid.length.should.equal(0);
|
||||
|
||||
importLabel = jsonResponse.meta.import_label.slug;
|
||||
return request
|
||||
.get(localUtils.API.getApiQuery(`members/?&filter=label:${importLabel}`))
|
||||
.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.members);
|
||||
should.equal(jsonResponse.members.length, 2);
|
||||
|
||||
const importedMember1 = jsonResponse.members.find(m => m.email === 'member+labels_1@example.com');
|
||||
should.exist(importedMember1);
|
||||
should(importedMember1.name).equal(null);
|
||||
should(importedMember1.note).equal(null);
|
||||
importedMember1.subscribed.should.equal(true);
|
||||
importedMember1.comped.should.equal(false);
|
||||
importedMember1.stripe.should.not.be.undefined();
|
||||
importedMember1.stripe.subscriptions.length.should.equal(0);
|
||||
|
||||
// check label order
|
||||
// 1 unique global + 1 record labels + 1 auto generated label
|
||||
importedMember1.labels.length.should.equal(3);
|
||||
should.exist(importedMember1.labels.find(({slug}) => slug === 'label'));
|
||||
should.exist(importedMember1.labels.find(({slug}) => slug === 'global-label-1'));
|
||||
should.exist(importedMember1.labels.find(({slug}) => slug.match(/^import-/)));
|
||||
|
||||
const importedMember2 = jsonResponse.members.find(m => m.email === 'member+labels_2@example.com');
|
||||
should.exist(importedMember2);
|
||||
// 1 unique global + 2 record labels
|
||||
importedMember2.labels.length.should.equal(4);
|
||||
should.exist(importedMember2.labels.find(({slug}) => slug === 'another-label'));
|
||||
should.exist(importedMember2.labels.find(({slug}) => slug === 'and-one-more'));
|
||||
should.exist(importedMember2.labels.find(({slug}) => slug === 'global-label-1'));
|
||||
should.exist(importedMember2.labels.find(({slug}) => slug.match(/^import-/)));
|
||||
});
|
||||
});
|
||||
|
||||
it('Can import CSV with mapped fields', function () {
|
||||
return request
|
||||
.post(localUtils.API.getApiQuery(`members/upload/`))
|
||||
.field('mapping[correo_electrpnico]', 'email')
|
||||
.field('mapping[nombre]', 'name')
|
||||
.attach('membersfile', path.join(__dirname, '/../../../../utils/fixtures/csv/members-with-mappings.csv'))
|
||||
.set('Origin', config.get('url'))
|
||||
.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.meta);
|
||||
should.exist(jsonResponse.meta.stats);
|
||||
|
||||
jsonResponse.meta.stats.imported.should.equal(1);
|
||||
jsonResponse.meta.stats.invalid.length.should.equal(0);
|
||||
|
||||
should.exist(jsonResponse.meta.import_label);
|
||||
jsonResponse.meta.import_label.slug.should.match(/^import-/);
|
||||
})
|
||||
.then(() => {
|
||||
return request
|
||||
.get(localUtils.API.getApiQuery(`members/?search=${encodeURIComponent('member+mapped_1@example.com')}`))
|
||||
.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.members);
|
||||
should.exist(jsonResponse.members[0]);
|
||||
|
||||
const importedMember1 = jsonResponse.members[0];
|
||||
should(importedMember1.email).equal('member+mapped_1@example.com');
|
||||
should(importedMember1.name).equal('Hannah');
|
||||
should(importedMember1.note).equal('no need to map me');
|
||||
importedMember1.subscribed.should.equal(true);
|
||||
importedMember1.comped.should.equal(false);
|
||||
importedMember1.stripe.should.not.be.undefined();
|
||||
importedMember1.stripe.subscriptions.length.should.equal(0);
|
||||
importedMember1.labels.length.should.equal(1); // auto-generated import label
|
||||
});
|
||||
});
|
||||
|
||||
it('Can import CSV with labels and provide additional labels', function () {
|
||||
return request
|
||||
.post(localUtils.API.getApiQuery(`members/upload/`))
|
||||
.attach('membersfile', path.join(__dirname, '/../../../../utils/fixtures/csv/valid-members-defaults.csv'))
|
||||
.set('Origin', config.get('url'))
|
||||
.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.meta);
|
||||
should.exist(jsonResponse.meta.stats);
|
||||
|
||||
jsonResponse.meta.stats.imported.should.equal(2);
|
||||
jsonResponse.meta.stats.invalid.length.should.equal(0);
|
||||
})
|
||||
.then(() => {
|
||||
return request
|
||||
.get(localUtils.API.getApiQuery(`members/`))
|
||||
.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.members);
|
||||
|
||||
const defaultMember1 = jsonResponse.members.find(member => (member.email === 'member+defaults_1@example.com'));
|
||||
should(defaultMember1.name).equal(null);
|
||||
should(defaultMember1.note).equal(null);
|
||||
defaultMember1.subscribed.should.equal(true);
|
||||
defaultMember1.comped.should.equal(false);
|
||||
defaultMember1.stripe.should.not.be.undefined();
|
||||
defaultMember1.stripe.subscriptions.length.should.equal(0);
|
||||
defaultMember1.labels.length.should.equal(1); // auto-generated import label
|
||||
|
||||
const defaultMember2 = jsonResponse.members.find(member => (member.email === 'member+defaults_2@example.com'));
|
||||
should(defaultMember2).not.be.undefined();
|
||||
});
|
||||
});
|
||||
|
||||
it('Runs imports with stripe_customer_id as background job', function () {
|
||||
return request
|
||||
.post(localUtils.API.getApiQuery(`members/upload/`))
|
||||
.attach('membersfile', path.join(__dirname, '/../../../../utils/fixtures/csv/members-with-stripe-ids.csv'))
|
||||
.set('Origin', config.get('url'))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(202)
|
||||
.then((res) => {
|
||||
should.not.exist(res.headers['x-cache-invalidate']);
|
||||
const jsonResponse = res.body;
|
||||
|
||||
should.exist(jsonResponse);
|
||||
should.not.exist(jsonResponse.meta);
|
||||
});
|
||||
});
|
||||
|
||||
it('Fails to import memmber with invalid values', function () {
|
||||
return request
|
||||
.post(localUtils.API.getApiQuery(`members/upload/`))
|
||||
.field('labels', ['new-global-label'])
|
||||
.attach('membersfile', path.join(__dirname, '/../../../../utils/fixtures/csv/members-invalid-values.csv'))
|
||||
.set('Origin', config.get('url'))
|
||||
.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.meta);
|
||||
should.exist(jsonResponse.meta.stats);
|
||||
|
||||
jsonResponse.meta.stats.imported.should.equal(1);
|
||||
jsonResponse.meta.stats.invalid.length.should.equal(1);
|
||||
|
||||
jsonResponse.meta.stats.invalid[0].error.should.match(/Validation \(isEmail\) failed for email/);
|
||||
|
||||
should.exist(jsonResponse.meta.import_label);
|
||||
jsonResponse.meta.import_label.slug.should.match(/^import-/);
|
||||
});
|
||||
});
|
||||
|
||||
it('Can fetch stats with no ?days param', function () {
|
||||
return request
|
||||
.get(localUtils.API.getApiQuery('members/stats/'))
|
||||
.set('Origin', config.get('url'))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
// .expect(200) - doesn't surface underlying errors in tests
|
||||
.then((res) => {
|
||||
res.status.should.equal(200, JSON.stringify(res.body));
|
||||
|
||||
should.not.exist(res.headers['x-cache-invalidate']);
|
||||
const jsonResponse = res.body;
|
||||
|
||||
should.exist(jsonResponse);
|
||||
should.exist(jsonResponse.total);
|
||||
should.exist(jsonResponse.total_in_range);
|
||||
should.exist(jsonResponse.total_on_date);
|
||||
should.exist(jsonResponse.new_today);
|
||||
|
||||
// 3 from fixtures and 6 imported in previous tests
|
||||
jsonResponse.total.should.equal(10);
|
||||
});
|
||||
});
|
||||
|
||||
it('Can fetch stats with ?days=90', function () {
|
||||
return request
|
||||
.get(localUtils.API.getApiQuery('members/stats/?days=90'))
|
||||
.set('Origin', config.get('url'))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
// .expect(200) - doesn't surface underlying errors in tests
|
||||
.then((res) => {
|
||||
res.status.should.equal(200, JSON.stringify(res.body));
|
||||
|
||||
should.not.exist(res.headers['x-cache-invalidate']);
|
||||
const jsonResponse = res.body;
|
||||
|
||||
should.exist(jsonResponse);
|
||||
should.exist(jsonResponse.total);
|
||||
should.exist(jsonResponse.total_in_range);
|
||||
should.exist(jsonResponse.total_on_date);
|
||||
should.exist(jsonResponse.new_today);
|
||||
|
||||
// 3 from fixtures and 6 imported in previous tests
|
||||
jsonResponse.total.should.equal(10);
|
||||
});
|
||||
});
|
||||
|
||||
it('Can fetch stats with ?days=all-time', function () {
|
||||
return request
|
||||
.get(localUtils.API.getApiQuery('members/stats/?days=all-time'))
|
||||
.set('Origin', config.get('url'))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
// .expect(200) - doesn't surface underlying errors in tests
|
||||
.then((res) => {
|
||||
res.status.should.equal(200, JSON.stringify(res.body));
|
||||
|
||||
should.not.exist(res.headers['x-cache-invalidate']);
|
||||
const jsonResponse = res.body;
|
||||
|
||||
should.exist(jsonResponse);
|
||||
should.exist(jsonResponse.total);
|
||||
should.exist(jsonResponse.total_in_range);
|
||||
should.exist(jsonResponse.total_on_date);
|
||||
should.exist(jsonResponse.new_today);
|
||||
|
||||
// 3 from fixtures and 6 imported in previous tests
|
||||
jsonResponse.total.should.equal(10);
|
||||
});
|
||||
});
|
||||
|
||||
it('Errors when fetching stats with unknown days param value', function () {
|
||||
return request
|
||||
.get(localUtils.API.getApiQuery('members/stats/?days=nope'))
|
||||
.set('Origin', config.get('url'))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(422);
|
||||
});
|
||||
});
|
198
test/regression/api/v3/admin/notifications_spec.js
Normal file
198
test/regression/api/v3/admin/notifications_spec.js
Normal file
@ -0,0 +1,198 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
47
test/regression/api/v3/admin/pages_spec.js
Normal file
47
test/regression/api/v3/admin/pages_spec.js
Normal file
@ -0,0 +1,47 @@
|
||||
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;
|
||||
let request;
|
||||
|
||||
describe('Pages API', function () {
|
||||
before(function () {
|
||||
return ghost()
|
||||
.then(function (_ghostServer) {
|
||||
request = supertest.agent(config.get('url'));
|
||||
})
|
||||
.then(function () {
|
||||
return localUtils.doAuth(request, 'posts');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edit', function () {
|
||||
it('accepts html source', function () {
|
||||
return request
|
||||
.get(localUtils.API.getApiQuery(`pages/${testUtils.DataGenerator.Content.posts[5].id}/`))
|
||||
.set('Origin', config.get('url'))
|
||||
.expect(200)
|
||||
.then((res) => {
|
||||
res.body.pages[0].slug.should.equal('static-page-test');
|
||||
|
||||
return request
|
||||
.put(localUtils.API.getApiQuery('pages/' + testUtils.DataGenerator.Content.posts[5].id + '/?source=html'))
|
||||
.set('Origin', config.get('url'))
|
||||
.send({
|
||||
pages: [{
|
||||
html: '<p>HTML Ipsum presents</p>',
|
||||
updated_at: res.body.pages[0].updated_at
|
||||
}]
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(200);
|
||||
})
|
||||
.then((res) => {
|
||||
res.body.pages[0].mobiledoc.should.equal('{"version":"0.3.1","atoms":[],"cards":[],"markups":[],"sections":[[1,"p",[[0,[],0,"HTML Ipsum presents"]]]]}');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
635
test/regression/api/v3/admin/posts_spec.js
Normal file
635
test/regression/api/v3/admin/posts_spec.js
Normal file
@ -0,0 +1,635 @@
|
||||
const _ = require('lodash');
|
||||
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', 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', 'emails');
|
||||
})
|
||||
.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,email'))
|
||||
.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', 'email']
|
||||
);
|
||||
|
||||
localUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can filter by fields coming from posts_meta table non null meta_description', function (done) {
|
||||
request.get(localUtils.API.getApiQuery(`posts/?filter=meta_description:-null`))
|
||||
.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(2);
|
||||
jsonResponse.posts.forEach((post) => {
|
||||
should.notEqual(post.meta_description, null);
|
||||
});
|
||||
|
||||
localUtils.API.checkResponse(
|
||||
jsonResponse.posts[0],
|
||||
'post'
|
||||
);
|
||||
|
||||
localUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can filter by fields coming from posts_meta table by value', function (done) {
|
||||
request.get(localUtils.API.getApiQuery(`posts/?filter=meta_description:'meta description for short and sweet'`))
|
||||
.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(1);
|
||||
jsonResponse.posts[0].id.should.equal(testUtils.DataGenerator.Content.posts[2].id);
|
||||
jsonResponse.posts[0].meta_description.should.equal('meta description for short and sweet');
|
||||
|
||||
localUtils.API.checkResponse(
|
||||
jsonResponse.posts[0],
|
||||
'post'
|
||||
);
|
||||
|
||||
localUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can order by fields coming from posts_meta table', function (done) {
|
||||
request.get(localUtils.API.getApiQuery('posts/?order=meta_description%20ASC'))
|
||||
.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);
|
||||
|
||||
should.equal(jsonResponse.posts[0].meta_description, null);
|
||||
jsonResponse.posts[12].slug.should.equal('short-and-sweet');
|
||||
jsonResponse.posts[12].meta_description.should.equal('meta description for short and sweet');
|
||||
|
||||
localUtils.API.checkResponse(
|
||||
jsonResponse.posts[0],
|
||||
'post'
|
||||
);
|
||||
|
||||
localUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can order by email open rate', async function () {
|
||||
try {
|
||||
await testUtils.createEmailedPost({
|
||||
postOptions: {
|
||||
post: {
|
||||
slug: '80-open-rate'
|
||||
}
|
||||
},
|
||||
emailOptions: {
|
||||
email: {
|
||||
email_count: 100,
|
||||
opened_count: 80,
|
||||
track_opens: true
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await testUtils.createEmailedPost({
|
||||
postOptions: {
|
||||
post: {
|
||||
slug: '60-open-rate'
|
||||
}
|
||||
},
|
||||
emailOptions: {
|
||||
email: {
|
||||
email_count: 100,
|
||||
opened_count: 60,
|
||||
track_opens: true
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
if (_.isArray(err)) {
|
||||
throw err[0];
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
|
||||
await request.get(localUtils.API.getApiQuery('posts/?order=email.open_rate%20DESC'))
|
||||
.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.posts);
|
||||
localUtils.API.checkResponse(jsonResponse, 'posts');
|
||||
jsonResponse.posts.should.have.length(15);
|
||||
|
||||
jsonResponse.posts[0].slug.should.equal('80-open-rate', 'DESC 1st');
|
||||
jsonResponse.posts[1].slug.should.equal('60-open-rate', 'DESC 2nd');
|
||||
|
||||
localUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination');
|
||||
});
|
||||
|
||||
await request.get(localUtils.API.getApiQuery('posts/?order=email.open_rate%20ASC'))
|
||||
.set('Origin', config.get('url'))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(200)
|
||||
.then((res) => {
|
||||
const jsonResponse = res.body;
|
||||
jsonResponse.posts[0].slug.should.equal('60-open-rate', 'ASC 1st');
|
||||
jsonResponse.posts[1].slug.should.equal('80-open-rate', 'ASC 2nd');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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)');
|
||||
|
||||
should.exist(res.headers.location);
|
||||
res.headers.location.should.equal(`http://127.0.0.1:2369${localUtils.API.getApiQuery('posts/')}${res.body.posts[0].id}/`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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('accepts visibility parameter', 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: [{
|
||||
visibility: 'members',
|
||||
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].visibility);
|
||||
res.body.posts[0].visibility.should.equal('members');
|
||||
});
|
||||
});
|
||||
|
||||
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'
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
429
test/regression/api/v3/admin/redirects_spec.js
Normal file
429
test/regression/api/v3/admin/redirects_spec.js
Normal file
@ -0,0 +1,429 @@
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
const startGhost = (options) => {
|
||||
return ghost(options)
|
||||
.then(() => {
|
||||
request = supertest.agent(config.get('url'));
|
||||
})
|
||||
.then(() => {
|
||||
return localUtils.doAuth(request);
|
||||
});
|
||||
};
|
||||
|
||||
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.
|
||||
// TODO: But it should be written when the default redirects file type is changed to yaml.
|
||||
|
||||
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 () {
|
||||
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 () {
|
||||
describe('Error cases', function () {
|
||||
it('syntax error', function () {
|
||||
fs.writeFileSync(path.join(config.get('paths:contentPath'), 'redirects.yaml'), 'x');
|
||||
|
||||
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(400);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Ensure re-registering redirects works', function () {
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// https://github.com/TryGhost/Ghost/issues/10898
|
||||
describe('Merge querystring', function () {
|
||||
it('toURL param takes precedence, other params pass through', function () {
|
||||
return startGhost({forceStart: true, redirectsFileExt: '.json'})
|
||||
.then(function () {
|
||||
return request
|
||||
.get('/test-params/?q=123&lang=js')
|
||||
.expect(301)
|
||||
.then(function (res) {
|
||||
res.headers.location.should.eql('/result?q=abc&lang=js');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: For backward compatibility, we only check if download, upload endpoints work here.
|
||||
// when updating to v4, the old endpoints should be updated to the new ones.
|
||||
// And the tests below should be removed.
|
||||
describe('New endpoints work', function () {
|
||||
it('download', function () {
|
||||
return request
|
||||
.get(localUtils.API.getApiQuery('redirects/download/'))
|
||||
.set('Origin', config.get('url'))
|
||||
.expect('Content-Type', /application\/json/)
|
||||
.expect('Content-Disposition', 'Attachment; filename="redirects.json"')
|
||||
.expect(200);
|
||||
});
|
||||
|
||||
it('upload', function () {
|
||||
// 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'
|
||||
}]));
|
||||
|
||||
return request
|
||||
.post(localUtils.API.getApiQuery('redirects/upload/'))
|
||||
.set('Origin', config.get('url'))
|
||||
.attach('redirects', path.join(config.get('paths:contentPath'), 'redirects-init.json'))
|
||||
.expect('Content-Type', /application\/json/)
|
||||
.expect(200);
|
||||
});
|
||||
});
|
||||
});
|
184
test/regression/api/v3/admin/schedules_spec.js
Normal file
184
test/regression/api/v3/admin/schedules_spec.js
Normal file
@ -0,0 +1,184 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
717
test/regression/api/v3/admin/settings_spec.js
Normal file
717
test/regression/api/v3/admin/settings_spec.js
Normal file
@ -0,0 +1,717 @@
|
||||
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 defaultSettingsKeyTypes = [
|
||||
{key: 'title', type: 'blog'},
|
||||
{key: 'description', type: 'blog'},
|
||||
{key: 'logo', type: 'blog'},
|
||||
{key: 'cover_image', type: 'blog'},
|
||||
{key: 'icon', type: 'blog'},
|
||||
{key: 'lang', type: 'blog'},
|
||||
{key: 'timezone', type: 'blog'},
|
||||
{key: 'codeinjection_head', type: 'blog'},
|
||||
{key: 'codeinjection_foot', type: 'blog'},
|
||||
{key: 'facebook', type: 'blog'},
|
||||
{key: 'twitter', type: 'blog'},
|
||||
{key: 'navigation', type: 'blog'},
|
||||
{key: 'secondary_navigation', type: 'blog'},
|
||||
{key: 'meta_title', type: 'blog'},
|
||||
{key: 'meta_description', type: 'blog'},
|
||||
{key: 'og_image', type: 'blog'},
|
||||
{key: 'og_title', type: 'blog'},
|
||||
{key: 'og_description', type: 'blog'},
|
||||
{key: 'twitter_image', type: 'blog'},
|
||||
{key: 'twitter_title', type: 'blog'},
|
||||
{key: 'twitter_description', type: 'blog'},
|
||||
{key: 'active_theme', type: 'theme'},
|
||||
{key: 'is_private', type: 'private'},
|
||||
{key: 'password', type: 'private'},
|
||||
{key: 'public_hash', type: 'private'},
|
||||
{key: 'default_content_visibility', type: 'members'},
|
||||
{key: 'members_allow_free_signup', type: 'members'},
|
||||
{key: 'members_from_address', type: 'members'},
|
||||
{key: 'members_support_address', type: 'members'},
|
||||
{key: 'members_reply_address', type: 'members'},
|
||||
{key: 'members_paid_signup_redirect', type: 'members'},
|
||||
{key: 'members_free_signup_redirect', type: 'members'},
|
||||
{key: 'stripe_product_name', type: 'members'},
|
||||
{key: 'stripe_plans', type: 'members'},
|
||||
{key: 'stripe_secret_key', type: 'members'},
|
||||
{key: 'stripe_publishable_key', type: 'members'},
|
||||
{key: 'stripe_connect_secret_key', type: 'members'},
|
||||
{key: 'stripe_connect_publishable_key', type: 'members'},
|
||||
{key: 'stripe_connect_account_id', type: 'members'},
|
||||
{key: 'stripe_connect_display_name', type: 'members'},
|
||||
{key: 'stripe_connect_livemode', type: 'members'},
|
||||
{key: 'portal_name', type: 'portal'},
|
||||
{key: 'portal_button', type: 'portal'},
|
||||
{key: 'portal_plans', type: 'portal'},
|
||||
{key: 'portal_button_style', type: 'portal'},
|
||||
{key: 'portal_button_icon', type: 'portal'},
|
||||
{key: 'portal_button_signup_text', type: 'portal'},
|
||||
{key: 'mailgun_api_key', type: 'bulk_email'},
|
||||
{key: 'mailgun_domain', type: 'bulk_email'},
|
||||
{key: 'mailgun_base_url', type: 'bulk_email'},
|
||||
{key: 'email_track_opens', type: 'bulk_email'},
|
||||
{key: 'amp', type: 'blog'},
|
||||
{key: 'amp_gtag_id', type: 'blog'},
|
||||
{key: 'labs', type: 'blog'},
|
||||
{key: 'slack', type: 'blog'},
|
||||
{key: 'unsplash', type: 'blog'},
|
||||
{key: 'shared_views', type: 'blog'},
|
||||
{key: 'active_timezone', type: 'blog'},
|
||||
{key: 'default_locale', type: 'blog'},
|
||||
{key: 'accent_color', type: 'blog'},
|
||||
{key: 'newsletter_show_badge', type: 'newsletter'},
|
||||
{key: 'newsletter_show_header', type: 'newsletter'},
|
||||
{key: 'newsletter_body_font_category', type: 'newsletter'},
|
||||
{key: 'newsletter_footer_content', type: 'newsletter'},
|
||||
{key: 'firstpromoter', type: 'firstpromoter'},
|
||||
{key: 'firstpromoter_id', type: 'firstpromoter'}
|
||||
];
|
||||
|
||||
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;
|
||||
should.equal(settings.length, defaultSettingsKeyTypes.length);
|
||||
for (const defaultSetting of defaultSettingsKeyTypes) {
|
||||
should.exist(settings.find((setting) => {
|
||||
return setting.key === defaultSetting.key && setting.type === defaultSetting.type;
|
||||
}), `Expected to find a setting with key ${defaultSetting.key} and type ${defaultSetting.type}`);
|
||||
}
|
||||
|
||||
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 request settings by group', function () {
|
||||
return request.get(localUtils.API.getApiQuery(`settings/?group=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 request settings by group and by deprecated type', function () {
|
||||
return request.get(localUtils.API.getApiQuery(`settings/?group=theme&type=private`))
|
||||
.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(4);
|
||||
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('Requesting core settings type returns no results', function () {
|
||||
return request.get(localUtils.API.getApiQuery(`settings/?type=core`))
|
||||
.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(0);
|
||||
|
||||
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 jsonResponse;
|
||||
})
|
||||
.then((jsonResponse) => {
|
||||
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 (res) {
|
||||
jsonResponse = res.body;
|
||||
should.not.exist(res.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;
|
||||
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'
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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;
|
||||
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'
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
48
test/regression/api/v3/admin/slack_spec.js
Normal file
48
test/regression/api/v3/admin/slack_spec.js
Normal file
@ -0,0 +1,48 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
295
test/regression/api/v3/admin/users_spec.js
Normal file
295
test/regression/api/v3/admin/users_spec.js
Normal file
@ -0,0 +1,295 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
134
test/regression/api/v3/admin/utils.js
Normal file
134
test/regression/api/v3/admin/utils.js
Normal file
@ -0,0 +1,134 @@
|
||||
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'],
|
||||
members: ['members', 'meta'],
|
||||
|
||||
post: _(schema.posts)
|
||||
.keys()
|
||||
// by default we only return mobiledoc
|
||||
.without('html', 'plaintext')
|
||||
.without('locale')
|
||||
.without('page')
|
||||
.without('author_id', 'author')
|
||||
.without('type')
|
||||
// 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', 'email')
|
||||
// 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()
|
||||
,
|
||||
member: _(schema.members)
|
||||
.keys()
|
||||
.concat('avatar_image')
|
||||
.concat('comped')
|
||||
.concat('labels')
|
||||
,
|
||||
member_signin_url: ['member_id', 'url'],
|
||||
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()
|
||||
,
|
||||
email_preview: ['html', 'subject', 'plaintext']
|
||||
};
|
||||
|
||||
_.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
|
||||
);
|
||||
}
|
||||
};
|
187
test/regression/api/v3/admin/webhooks_spec.js
Normal file
187
test/regression/api/v3/admin/webhooks_spec.js
Normal file
@ -0,0 +1,187 @@
|
||||
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);
|
||||
jsonResponse.webhooks[0].name.should.eql('test');
|
||||
jsonResponse.webhooks[0].secret.should.eql('thisissecret');
|
||||
jsonResponse.webhooks[0].api_version.should.eql('v3');
|
||||
|
||||
localUtils.API.checkResponse(jsonResponse.webhooks[0], 'webhook');
|
||||
});
|
||||
});
|
||||
|
||||
it('Fails validation for when integration_id is missing', function () {
|
||||
let webhookData = {
|
||||
event: 'test.create',
|
||||
target_url: 'http://example.com/webhooks/test/extra/1',
|
||||
name: 'test',
|
||||
secret: 'thisissecret',
|
||||
api_version: 'v2'
|
||||
};
|
||||
|
||||
return request.post(localUtils.API.getApiQuery('webhooks/'))
|
||||
.set('Origin', config.get('url'))
|
||||
.send({webhooks: [webhookData]})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(422);
|
||||
});
|
||||
|
||||
it('Fails validation for non-lowercase event name', function () {
|
||||
let webhookData = {
|
||||
event: 'tEst.evenT',
|
||||
target_url: 'http://example.com/webhooks/test/extra/1',
|
||||
name: 'test',
|
||||
secret: 'thisissecret',
|
||||
api_version: 'v2'
|
||||
};
|
||||
|
||||
return request.post(localUtils.API.getApiQuery('webhooks/'))
|
||||
.set('Origin', config.get('url'))
|
||||
.send({webhooks: [webhookData]})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(422);
|
||||
});
|
||||
|
||||
it('Fails validation when required fields are not present', function () {
|
||||
let webhookData = {
|
||||
api_version: 'v2',
|
||||
integration_id: 'dummy'
|
||||
};
|
||||
|
||||
return request.post(localUtils.API.getApiQuery('webhooks/'))
|
||||
.set('Origin', config.get('url'))
|
||||
.send({webhooks: [webhookData]})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(422);
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
56
test/regression/api/v3/content/authors_spec.js
Normal file
56
test/regression/api/v3/content/authors_spec.js
Normal file
@ -0,0 +1,56 @@
|
||||
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']);
|
||||
});
|
||||
});
|
||||
|
||||
it('browse authors with slug filter, should order in slug order', function () {
|
||||
return request.get(localUtils.API.getApiQuery(`authors/?key=${validKey}&filter=slug:[joe-bloggs,ghost,slimer-mcectoplasm]`))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(200)
|
||||
.then((res) => {
|
||||
const jsonResponse = res.body;
|
||||
|
||||
jsonResponse.authors.should.be.an.Array().with.lengthOf(3);
|
||||
jsonResponse.authors[0].slug.should.equal('joe-bloggs');
|
||||
jsonResponse.authors[1].slug.should.equal('ghost');
|
||||
jsonResponse.authors[2].slug.should.equal('slimer-mcectoplasm');
|
||||
});
|
||||
});
|
||||
});
|
68
test/regression/api/v3/content/pages_spec.js
Normal file
68
test/regression/api/v3/content/pages_spec.js
Normal file
@ -0,0 +1,68 @@
|
||||
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 () {
|
||||
const key = localUtils.getValidKey();
|
||||
|
||||
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 () {
|
||||
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('browse pages with slug filter, should order in slug order', function () {
|
||||
return request.get(localUtils.API.getApiQuery(`pages/?key=${key}&filter=slug:[static-page-test]`))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(200)
|
||||
.then((res) => {
|
||||
const jsonResponse = res.body;
|
||||
|
||||
jsonResponse.pages.should.be.an.Array().with.lengthOf(1);
|
||||
jsonResponse.pages[0].slug.should.equal('static-page-test');
|
||||
});
|
||||
});
|
||||
|
||||
it('can\'t read post', function () {
|
||||
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);
|
||||
});
|
||||
});
|
407
test/regression/api/v3/content/posts_spec.js
Normal file
407
test/regression/api/v3/content/posts_spec.js
Normal file
@ -0,0 +1,407 @@
|
||||
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('browse posts with slug filter, should order in slug order', function () {
|
||||
return request.get(localUtils.API.getApiQuery(`posts/?key=${validKey}&filter=slug:[themes,ghostly-kitchen-sink,the-editor]`))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(200)
|
||||
.then((res) => {
|
||||
const jsonResponse = res.body;
|
||||
|
||||
jsonResponse.posts.should.be.an.Array().with.lengthOf(3);
|
||||
jsonResponse.posts[0].slug.should.equal('themes');
|
||||
jsonResponse.posts[1].slug.should.equal('ghostly-kitchen-sink');
|
||||
jsonResponse.posts[2].slug.should.equal('the-editor');
|
||||
});
|
||||
});
|
||||
|
||||
it('browse posts with slug filter should order taking order parameter into account', function () {
|
||||
return request.get(localUtils.API.getApiQuery(`posts/?key=${validKey}&order=slug%20DESC&filter=slug:[themes,ghostly-kitchen-sink,the-editor]`))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(200)
|
||||
.then((res) => {
|
||||
const jsonResponse = res.body;
|
||||
|
||||
jsonResponse.posts.should.be.an.Array().with.lengthOf(3);
|
||||
jsonResponse.posts[0].slug.should.equal('themes');
|
||||
jsonResponse.posts[1].slug.should.equal('the-editor');
|
||||
jsonResponse.posts[2].slug.should.equal('ghostly-kitchen-sink');
|
||||
});
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
87
test/regression/api/v3/content/tags_spec.js
Normal file
87
test/regression/api/v3/content/tags_spec.js
Normal file
@ -0,0 +1,87 @@
|
||||
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 () {
|
||||
const validKey = localUtils.getValidKey();
|
||||
|
||||
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 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');
|
||||
});
|
||||
});
|
||||
});
|
94
test/regression/api/v3/content/utils.js
Normal file
94
test/regression/api/v3/content/utils.js
Normal file
@ -0,0 +1,94 @@
|
||||
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;
|
||||
}
|
||||
};
|
@ -3444,7 +3444,7 @@ describe('Integration - Web - Site', function () {
|
||||
});
|
||||
|
||||
beforeEach(function () {
|
||||
const postsAPI = require('../../../core/server/api/canary/posts-public');
|
||||
const postsAPI = require('../../../core/server/api/v3/posts-public');
|
||||
postSpy = sinon.spy(postsAPI.browse, 'query');
|
||||
});
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user