Ghost/ghost/core/test/e2e-api/admin/integrations.test.js
Simon Backx 320eaac4c4
Removed usage of unquoted ids in filter strings (#19031)
fixes GRO-34
fixes GRO-33

This also adds a new way to run all tests with enforced numeric ObjectIDs.
These numeric ids cause issues if they are used withing NQL filters. So they
surface tiny bugs in our codebase.

You can run tests using this option via:
NUMERIC_IDS=1 yarn test:e2e

Also removed some defensive logic that could be explained by unquoted ids.
2023-11-20 14:00:31 +01:00

394 lines
15 KiB
JavaScript

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 {agentProvider, fixtureManager, matchers} = require('../../utils/e2e-framework');
const {anyEtag, anyErrorId, anyContentVersion} = matchers;
describe('Integrations API', function () {
let request;
before(async function () {
await localUtils.startGhost();
request = supertest.agent(config.get('url'));
await localUtils.doAuth(request, 'integrations');
});
const findBy = (prop, val) => object => object[prop] === val;
it('Can browse all integrations', async function () {
const res = await request.get(localUtils.API.getApiQuery(`integrations/`))
.set('Origin', config.get('url'))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200);
should.equal(res.body.integrations.length, 6);
// there is no enforced order for integrations which makes order different on SQLite and MySQL
const zapierIntegration = _.find(res.body.integrations, {name: 'Zapier'}); // from migrations
should.exist(zapierIntegration);
const testIntegration = _.find(res.body.integrations, {name: 'Test Integration'}); // from fixtures
should.exist(testIntegration);
const exploreIntegration = _.find(res.body.integrations, {name: 'Test Core Integration'}); // from fixtures
should.exist(exploreIntegration);
const selfServeMigrationIntegration = _.find(res.body.integrations, {name: 'Self-Serve Migration Integration'}); // from fixtures
should.exist(selfServeMigrationIntegration);
});
it('Can not read internal integration', async function () {
await request.get(localUtils.API.getApiQuery(`integrations/${testUtils.DataGenerator.Content.integrations[1].id}/`))
.set('Origin', config.get('url'))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(404);
});
it('Can successfully create a single integration with auto generated content and admin api key', async function () {
const res = await request.post(localUtils.API.getApiQuery('integrations/'))
.set('Origin', config.get('url'))
.send({
integrations: [{
name: 'Dis-Integrate!!'
}]
})
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(201);
should.equal(res.body.integrations.length, 1);
const [integration] = res.body.integrations;
should.equal(integration.name, 'Dis-Integrate!!');
should.equal(integration.api_keys.length, 2);
const contentApiKey = integration.api_keys.find(findBy('type', 'content'));
should.equal(contentApiKey.integration_id, integration.id);
const adminApiKey = integration.api_keys.find(findBy('type', 'admin'));
should.equal(adminApiKey.integration_id, integration.id);
should.exist(adminApiKey.secret);
// check Admin API key secret format
const [id, secret] = adminApiKey.secret.split(':');
should.exist(id);
should.equal(id, adminApiKey.id);
should.exist(secret);
secret.length.should.equal(64);
should.exist(res.headers.location);
res.headers.location.should.equal(`http://127.0.0.1:2369${localUtils.API.getApiQuery('integrations/')}${res.body.integrations[0].id}/`);
});
it('Can successfully create a single integration with a webhook', async function () {
const res = await request.post(localUtils.API.getApiQuery('integrations/'))
.set('Origin', config.get('url'))
.send({
integrations: [{
name: 'Integratatron4000',
webhooks: [{
event: 'something',
target_url: 'http://example.com'
}]
}]
})
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(201);
should.equal(res.body.integrations.length, 1);
const [integration] = res.body.integrations;
should.equal(integration.name, 'Integratatron4000');
should.equal(integration.webhooks.length, 1);
const webhook = integration.webhooks[0];
should.equal(webhook.integration_id, integration.id);
should.exist(res.headers.location);
res.headers.location.should.equal(`http://127.0.0.1:2369${localUtils.API.getApiQuery('integrations/')}${res.body.integrations[0].id}/`);
});
it('Can successfully get a created integration', async function () {
const res = await request.post(localUtils.API.getApiQuery('integrations/'))
.set('Origin', config.get('url'))
.send({
integrations: [{
name: 'Interrogation Integration'
}]
})
.expect(201);
const [createdIntegration] = res.body.integrations;
const res2 = await request.get(localUtils.API.getApiQuery(`integrations/${createdIntegration.id}/`))
.set('Origin', config.get('url'))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200);
should.equal(res2.body.integrations.length, 1);
const [integration] = res2.body.integrations;
should.equal(integration.id, createdIntegration.id);
should.equal(integration.name, createdIntegration.name);
should.equal(integration.slug, createdIntegration.slug);
should.equal(integration.description, createdIntegration.description);
should.equal(integration.icon_image, createdIntegration.icon_image);
});
it('Can successfully get *all* created integrations with api_keys', async function () {
await request.post(localUtils.API.getApiQuery('integrations/'))
.set('Origin', config.get('url'))
.send({
integrations: [{
name: 'Integrate with this!'
}]
})
.expect(201);
await request.post(localUtils.API.getApiQuery('integrations/'))
.set('Origin', config.get('url'))
.send({
integrations: [{
name: 'Winter-(is)-great'
}]
})
.expect(201);
const res = await request.get(localUtils.API.getApiQuery(`integrations/?include=api_keys&limit=all`))
.set('Origin', config.get('url'))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200);
const body = res.body;
// This is the only page
should.equal(body.meta.pagination.page, 1);
should.equal(body.meta.pagination.pages, 1);
should.equal(body.meta.pagination.next, null);
should.equal(body.meta.pagination.prev, null);
body.integrations.forEach((integration) => {
should.exist(integration.api_keys);
if (integration.api_keys.length) {
integration.api_keys.forEach((apiKey) => {
should.exist(apiKey.secret);
if (apiKey.type === 'content') {
should.equal(apiKey.secret.split(':').length, 1, `${integration.name} api key secret should have correct key format without ":"`);
} else if (apiKey.type === 'admin') {
should.equal(apiKey.secret.split(':').length, 2, `${integration.name} api key secret should have correct key format with ":"`);
}
});
}
});
});
it('Can successfully edit a created integration', async function () {
const res = await request.post(localUtils.API.getApiQuery('integrations/'))
.set('Origin', config.get('url'))
.send({
integrations: [{
name: 'Rubbish Integration Name'
}]
})
.expect(201);
const [createdIntegration] = res.body.integrations;
await request.put(localUtils.API.getApiQuery(`integrations/${createdIntegration.id}/`))
.set('Origin', config.get('url'))
.send({
integrations: [{
name: 'Awesome Integration Name',
description: 'Finally got round to writing this...'
}]
})
.expect(200);
const res2 = await request.get(localUtils.API.getApiQuery(`integrations/${createdIntegration.id}/`))
.set('Origin', config.get('url'))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200);
const [updatedIntegration] = res2.body.integrations;
should.equal(updatedIntegration.id, createdIntegration.id);
should.equal(updatedIntegration.name, 'Awesome Integration Name');
should.equal(updatedIntegration.description, 'Finally got round to writing this...');
});
it('Can successfully refresh an integration api key', async function () {
const res = await request.post(localUtils.API.getApiQuery('integrations/?include=api_keys'))
.set('Origin', config.get('url'))
.send({
integrations: [{
name: 'Rubbish Integration Name'
}]
})
.expect(201);
const [createdIntegration] = res.body.integrations;
const apiKeys = createdIntegration.api_keys;
const adminApiKey = apiKeys.find(key => key.type === 'admin');
await request.post(localUtils.API.getApiQuery(`integrations/${createdIntegration.id}/api_key/${adminApiKey.id}/refresh`))
.set('Origin', config.get('url'))
.send({
integrations: [{
id: createdIntegration.id
}]
})
.expect(200);
const res2 = await request.get(localUtils.API.getApiQuery(`integrations/${createdIntegration.id}/?include=api_keys`))
.set('Origin', config.get('url'))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200);
const [updatedIntegration] = res2.body.integrations;
const updatedAdminApiKey = updatedIntegration.api_keys.find(key => key.type === 'admin');
should.equal(updatedIntegration.id, createdIntegration.id);
updatedAdminApiKey.secret.should.not.eql(adminApiKey.secret);
const res3 = await request.get(localUtils.API.getApiQuery(`actions/?filter=resource_id:'${adminApiKey.id}'&include=actor`))
.set('Origin', config.get('url'))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200);
const actions = res3.body.actions;
const refreshedAction = actions.find((action) => {
return action.event === 'refreshed';
});
should.exist(refreshedAction);
});
it('Can successfully add and delete a created integrations webhooks', async function () {
const res = await request.post(localUtils.API.getApiQuery('integrations/'))
.set('Origin', config.get('url'))
.send({
integrations: [{
name: 'Webhook-less Integration'
}]
})
.expect(201);
const [createdIntegration] = res.body.integrations;
await request.put(localUtils.API.getApiQuery(`integrations/${createdIntegration.id}/`))
.set('Origin', config.get('url'))
.send({
integrations: [{
webhooks: [{
event: 'somestuff',
target_url: 'http://example.com'
}]
}]
})
.expect(200);
const res2 = await request.get(localUtils.API.getApiQuery(`integrations/${createdIntegration.id}/?include=webhooks`))
.set('Origin', config.get('url'))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200);
const [updatedIntegration] = res2.body.integrations;
should.equal(updatedIntegration.webhooks.length, 1);
const webhook = updatedIntegration.webhooks[0];
should.equal(webhook.integration_id, updatedIntegration.id);
await request.put(localUtils.API.getApiQuery(`integrations/${createdIntegration.id}/`))
.set('Origin', config.get('url'))
.send({
integrations: [{
webhooks: []
}]
})
.expect(200);
const res3 = await request.get(localUtils.API.getApiQuery(`integrations/${createdIntegration.id}/?include=webhooks`))
.set('Origin', config.get('url'))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200);
const [updatedIntegration2] = res3.body.integrations;
should.equal(updatedIntegration2.webhooks.length, 0);
});
it('Can successfully delete a created integration', async function () {
const res = await request.post(localUtils.API.getApiQuery('integrations/'))
.set('Origin', config.get('url'))
.send({
integrations: [{
name: 'Short Lived Integration'
}]
})
.expect(201);
const [createdIntegration] = res.body.integrations;
await request.del(localUtils.API.getApiQuery(`integrations/${createdIntegration.id}/`))
.set('Origin', config.get('url'))
.expect(204)
.expect((_res) => {
_res.body.should.be.empty();
});
await request.get(localUtils.API.getApiQuery(`integrations/${createdIntegration.id}/`))
.set('Origin', config.get('url'))
.expect(404);
const editRes = await request.put(localUtils.API.getApiQuery(`integrations/${createdIntegration.id}/`))
.send({
integrations: [createdIntegration]
})
.set('Origin', config.get('url'))
.expect(404);
editRes.body.errors[0].context.should.eql('Integration not found.');
});
describe('As Administrator', function () {
let agent;
before(async function () {
agent = await agentProvider.getAdminAPIAgent();
await fixtureManager.init('users', 'integrations');
await agent.loginAsContributor();
});
it('Can\'t see Self-Serve or any other integration', async function () {
await agent
.get('integrations')
.matchHeaderSnapshot({
'content-version': anyContentVersion,
etag: anyEtag
})
.matchBodySnapshot({
errors: [
{
id: anyErrorId
}
]
})
.expectStatus(403);
});
});
});