const moment = require('moment-timezone'); const {agentProvider, mockManager, fixtureManager, matchers} = require('../utils/e2e-framework'); const {anyGhostAgent, anyArray, anyObjectId, anyISODateTime, anyUuid, anyContentVersion, anyNumber, anyLocalURL, anyString} = matchers; const tierSnapshot = { id: anyObjectId, created_at: anyISODateTime, updated_at: anyISODateTime }; const tagSnapshot = { created_at: anyISODateTime, description: null, id: anyObjectId, name: anyString, slug: anyString, updated_at: anyISODateTime, url: anyLocalURL, visibility: anyString }; const buildAuthorSnapshot = (roles = true) => { const authorSnapshot = { last_seen: anyISODateTime, created_at: anyISODateTime, updated_at: anyISODateTime }; if (roles) { authorSnapshot.roles = new Array(1).fill({ id: anyObjectId, created_at: anyISODateTime, updated_at: anyISODateTime }); } return authorSnapshot; }; const buildPostSnapshotWithTiers = ({published, tiersCount, roles = true}) => { return { id: anyObjectId, uuid: anyUuid, comment_id: anyObjectId, published_at: published ? anyISODateTime : null, created_at: anyISODateTime, updated_at: anyISODateTime, url: anyLocalURL, tiers: new Array(tiersCount).fill(tierSnapshot), primary_author: buildAuthorSnapshot(roles), authors: new Array(1).fill(buildAuthorSnapshot(roles)), post_revisions: anyArray }; }; const buildPostSnapshotWithTiersAndTags = ({published, tiersCount, tags, roles = true}) => { return { id: anyObjectId, uuid: anyUuid, comment_id: anyObjectId, published_at: published ? anyISODateTime : null, created_at: anyISODateTime, updated_at: anyISODateTime, url: anyLocalURL, tiers: new Array(tiersCount).fill(tierSnapshot), primary_author: buildAuthorSnapshot(roles), authors: new Array(1).fill(buildAuthorSnapshot(roles)), primary_tag: tags ? tagSnapshot : null, tags: tags ? new Array(1).fill(tagSnapshot) : [], post_revisions: anyArray }; }; const buildPreviousPostSnapshotWithTiers = ({tiersCount}) => { return { updated_at: anyISODateTime, tiers: new Array(tiersCount).fill(tierSnapshot) }; }; const buildPreviousPostSnapshotForDeletedPost = () => { return { id: anyObjectId, uuid: anyUuid, comment_id: anyObjectId, created_at: anyISODateTime, updated_at: anyISODateTime, authors: new Array(1).fill(buildAuthorSnapshot(true)) }; }; const buildPreviousPostSnapshotWithTiersAndTags = ({tiersCount, tags}) => { const previousSnapshot = { tags: tags ? new Array(1).fill(tagSnapshot) : [] }; if (tiersCount > 0){ previousSnapshot.tiers = new Array(tiersCount).fill(tierSnapshot); } return previousSnapshot; }; const buildPreviousPostSnapshotWithTiersPublished = ({tiersCount, published}) => { return { updated_at: anyISODateTime, published_at: published ? anyISODateTime : null, tiers: new Array(tiersCount).fill(tierSnapshot) }; }; describe('post.* events', function () { let adminAPIAgent; let webhookMockReceiver; before(async function () { adminAPIAgent = await agentProvider.getAdminAPIAgent(); await fixtureManager.init('integrations'); await adminAPIAgent.loginAsOwner(); }); beforeEach(function () { webhookMockReceiver = mockManager.mockWebhookRequests(); }); afterEach(function () { mockManager.restore(); }); it('post.published event is triggered', async function () { const webhookURL = 'https://test-webhook-receiver.com/post-published/'; await webhookMockReceiver.mock(webhookURL); await fixtureManager.insertWebhook({ event: 'post.published', url: webhookURL }); const res = await adminAPIAgent .post('posts/') .body({ posts: [{ title: 'webhookz', status: 'draft', mobiledoc: fixtureManager.get('posts', 1).mobiledoc }] }) .expectStatus(201); const id = res.body.posts[0].id; const updatedPost = res.body.posts[0]; updatedPost.status = 'published'; await adminAPIAgent .put('posts/' + id) .body({ posts: [updatedPost] }) .expectStatus(200); await webhookMockReceiver.receivedRequest(); webhookMockReceiver .matchHeaderSnapshot({ 'content-version': anyContentVersion, 'content-length': anyNumber, 'user-agent': anyGhostAgent }) .matchBodySnapshot({ post: { current: buildPostSnapshotWithTiers({ published: true, tiersCount: 2 }), previous: buildPreviousPostSnapshotWithTiers({ tiersCount: 2 }) } }); }); it('post.unpublished event is triggered', async function () { const webhookURL = 'https://test-webhook-receiver.com/post-unpublished/'; await webhookMockReceiver.mock(webhookURL); await fixtureManager.insertWebhook({ event: 'post.unpublished', url: webhookURL }); const res = await adminAPIAgent .post('posts/') .body({ posts: [ { title: 'webhookz', status: 'published', mobiledoc: fixtureManager.get('posts', 1).mobiledoc } ] }) .expectStatus(201); const id = res.body.posts[0].id; const updatedPost = res.body.posts[0]; updatedPost.status = 'draft'; await adminAPIAgent .put('posts/' + id) .body({ posts: [updatedPost] }) .expectStatus(200); await webhookMockReceiver.receivedRequest(); webhookMockReceiver .matchHeaderSnapshot({ 'content-version': anyContentVersion, 'content-length': anyNumber, 'user-agent': anyGhostAgent }) .matchBodySnapshot({ post: { current: buildPostSnapshotWithTiers({ published: true, tiersCount: 2 }), previous: buildPreviousPostSnapshotWithTiers({ tiersCount: 2 }) } }); }); it('post.added event is triggered', async function () { const webhookURL = 'https://test-webhook-receiver.com/post-added/'; await webhookMockReceiver.mock(webhookURL); await fixtureManager.insertWebhook({ event: 'post.added', url: webhookURL }); await adminAPIAgent .post('posts/') .body({ posts: [{ title: 'testing post.added webhook', status: 'draft' }] }) .expectStatus(201); await webhookMockReceiver.receivedRequest(); webhookMockReceiver .matchHeaderSnapshot({ 'content-version': anyContentVersion, 'content-length': anyNumber, 'user-agent': anyGhostAgent }) .matchBodySnapshot({ post: { current: buildPostSnapshotWithTiers({ published: false, tiersCount: 2, // @NOTE: post.added event does not include post author's roles // see commit message for more context roles: false }) } }); }); it('post.deleted event is triggered', async function () { const webhookURL = 'https://test-webhook-receiver.com/post-deleted/'; await webhookMockReceiver.mock(webhookURL); await fixtureManager.insertWebhook({ event: 'post.deleted', url: webhookURL }); const res = await adminAPIAgent .post('posts/') .body({ posts: [{ title: 'testing post.deleted webhook', status: 'draft' }] }) .expectStatus(201); const id = res.body.posts[0].id; await adminAPIAgent .delete('posts/' + id) .expectStatus(204); await webhookMockReceiver.receivedRequest(); webhookMockReceiver .matchHeaderSnapshot({ 'content-version': anyContentVersion, 'content-length': anyNumber, 'user-agent': anyGhostAgent }) .matchBodySnapshot({ post: { current: {}, previous: buildPreviousPostSnapshotForDeletedPost() } }); }); it('post.scheduled event is triggered', async function () { const webhookURL = 'https://test-webhook-receiver.com/post-scheduled/'; await webhookMockReceiver.mock(webhookURL); await fixtureManager.insertWebhook({ event: 'post.scheduled', url: webhookURL }); const res = await adminAPIAgent .post('posts/') .body({ posts: [{ title: 'Testing post.scheduled webhook', status: 'draft' }] }) .expectStatus(201); const id = res.body.posts[0].id; const scheduledPost = res.body.posts[0]; scheduledPost.status = 'scheduled'; scheduledPost.published_at = moment().add(1, 'days').toISOString(); await adminAPIAgent .put('posts/' + id) .body({ posts: [scheduledPost] }) .expectStatus(200); await webhookMockReceiver.receivedRequest(); webhookMockReceiver .matchHeaderSnapshot({ 'content-version': anyContentVersion, 'content-length': anyNumber, 'user-agent': anyGhostAgent }) .matchBodySnapshot({ post: { current: buildPostSnapshotWithTiers({ published: true, tiersCount: 2 }), previous: buildPreviousPostSnapshotWithTiers({ tiersCount: 2 }) } }); }); it('post.unscheduled event is triggered', async function () { const webhookURL = 'https://test-webhook-receiver.com/post-unscheduled/'; await webhookMockReceiver.mock(webhookURL); await fixtureManager.insertWebhook({ event: 'post.unscheduled', url: webhookURL }); const published_at = moment().add(1, 'days').toISOString(); const res = await adminAPIAgent .post('posts/') .body({ posts: [{ title: 'Testing post.unscheduled webhook', status: 'scheduled', published_at: published_at }] }) .expectStatus(201); const id = res.body.posts[0].id; const unrescheduledPost = res.body.posts[0]; unrescheduledPost.status = 'draft'; unrescheduledPost.published_at = null; await adminAPIAgent .put('posts/' + id) .body({ posts: [unrescheduledPost] }) .expectStatus(200); await webhookMockReceiver.receivedRequest(); webhookMockReceiver .matchHeaderSnapshot({ 'content-version': anyContentVersion, 'content-length': anyNumber, 'user-agent': anyGhostAgent }) .matchBodySnapshot({ post: { current: buildPostSnapshotWithTiers({ published: false, tiersCount: 2 }), previous: { published_at: anyISODateTime, updated_at: anyISODateTime, tiers: new Array(2).fill(tierSnapshot) } } }); }); it('post.tag.attached event is triggered', async function () { const webhookURL = 'https://test-webhook-receiver.com/post-tag-attached/'; await webhookMockReceiver.mock(webhookURL); await fixtureManager.insertWebhook({ event: 'post.tag.attached', url: webhookURL }); const res = await adminAPIAgent .post('posts/') .body({ posts: [{ title: 'test post tag attached webhook', status: 'draft', mobiledoc: fixtureManager.get('posts', 1).mobiledoc }] }) .expectStatus(201); const id = res.body.posts[0].id; const updatedPost = res.body.posts[0]; updatedPost.tags = ['Getting Started']; await adminAPIAgent .put('posts/' + id) .body({ posts: [updatedPost] }) .expectStatus(200); await webhookMockReceiver.receivedRequest(); webhookMockReceiver .matchHeaderSnapshot({ 'content-version': anyContentVersion, 'content-length': anyNumber, 'user-agent': anyGhostAgent }) .matchBodySnapshot({ post: { current: buildPostSnapshotWithTiersAndTags({ published: false, tiersCount: 2, tags: true }), previous: buildPreviousPostSnapshotWithTiersAndTags({ tiersCount: 2, tags: false }) } }); }); it('post.tag.detached event is triggered', async function () { const webhookURL = 'https://test-webhook-receiver.com/post-tag-detached/'; await webhookMockReceiver.mock(webhookURL); await fixtureManager.insertWebhook({ event: 'post.tag.detached', url: webhookURL }); const res = await adminAPIAgent .post('posts/') .body({ posts: [{ title: 'test post tag detached webhook', status: 'draft', mobiledoc: fixtureManager.get('posts', 1).mobiledoc }] }) .expectStatus(201); const id = res.body.posts[0].id; const updatedPost = res.body.posts[0]; updatedPost.tags = ['Getting Started']; await adminAPIAgent .put('posts/' + id) .body({ posts: [updatedPost] }) .expectStatus(200); updatedPost.tags = []; await adminAPIAgent .put('posts/' + id) .body({ posts: [updatedPost] }) .expectStatus(200); await webhookMockReceiver.receivedRequest(); webhookMockReceiver .matchHeaderSnapshot({ 'content-version': anyContentVersion, 'content-length': anyNumber, 'user-agent': anyGhostAgent }) .matchBodySnapshot({ post: { current: buildPostSnapshotWithTiersAndTags({ published: false, tiersCount: 2, tags: false }), previous: buildPreviousPostSnapshotWithTiersAndTags({ tiersCount: 0, tags: true }) } }); }); it('post.edited event is triggered', async function () { const webhookURL = 'https://test-webhook-receiver.com/post-edited/'; await webhookMockReceiver.mock(webhookURL); await fixtureManager.insertWebhook({ event: 'post.edited', url: webhookURL }); const res = await adminAPIAgent .post('posts/') .body({ posts: [{ title: 'testing post.edited webhook', status: 'draft' }] }) .expectStatus(201); const id = res.body.posts[0].id; const updatedPost = res.body.posts[0]; updatedPost.title = 'testing post.edited webhook - Updated'; await adminAPIAgent .put('posts/' + id) .body({ posts: [updatedPost] }) .expectStatus(200); await webhookMockReceiver.receivedRequest(); webhookMockReceiver .matchHeaderSnapshot({ 'content-version': anyContentVersion, 'content-length': anyNumber, 'user-agent': anyGhostAgent }) .matchBodySnapshot({ post: { current: buildPostSnapshotWithTiers({ published: false, tiersCount: 2, roles: true }), previous: buildPreviousPostSnapshotWithTiers({tiersCount: 2}) } }); }); it('post.published.edited event is triggered', async function () { const webhookURL = 'https://test-webhook-receiver.com/post-published-edited/'; await webhookMockReceiver.mock(webhookURL); await fixtureManager.insertWebhook({ event: 'post.published.edited', url: webhookURL }); const res = await adminAPIAgent .post('posts/') .body({ posts: [{ title: 'testing post published edited webhook', status: 'published' }] }) .expectStatus(201); const id = res.body.posts[0].id; const updatedPost = res.body.posts[0]; updatedPost.title = 'testing post published edited webhook - updated'; await adminAPIAgent .put('posts/' + id) .body({ posts: [updatedPost] }) .expectStatus(200); await webhookMockReceiver.receivedRequest(); webhookMockReceiver .matchHeaderSnapshot({ 'content-version': anyContentVersion, 'content-length': anyNumber, 'user-agent': anyGhostAgent }) .matchBodySnapshot({ post: { current: buildPostSnapshotWithTiers({ published: true, tiersCount: 2 }), previous: buildPreviousPostSnapshotWithTiers({ tiersCount: 2 }) } }); }); it('post.rescheduled event is triggered', async function () { const webhookURL = 'https://test-webhook-receiver.com/post-rescheduled/'; await webhookMockReceiver.mock(webhookURL); await fixtureManager.insertWebhook({ event: 'post.rescheduled', url: webhookURL }); const published_at = moment().add(1, 'days').toISOString(); const res = await adminAPIAgent .post('posts/') .body({ posts: [{ title: 'testing post rescheduled webhook', status: 'scheduled', published_at: published_at }] }) .expectStatus(201); const id = res.body.posts[0].id; const rescheduledPost = res.body.posts[0]; rescheduledPost.status = 'scheduled'; rescheduledPost.published_at = moment().add(2, 'days').toISOString(); await adminAPIAgent .put('posts/' + id) .body({ posts: [rescheduledPost] }) .expectStatus(200); await webhookMockReceiver.receivedRequest(); webhookMockReceiver .matchHeaderSnapshot({ 'content-version': anyContentVersion, 'content-length': anyNumber, 'user-agent': anyGhostAgent }) .matchBodySnapshot({ post: { current: buildPostSnapshotWithTiers({ published: true, tiersCount: 2, roles: true }), previous: buildPreviousPostSnapshotWithTiersPublished({ tiersCount: 2, published: true }) } }); }); });