Ghost/ghost/core/test/e2e-api/admin/offers.test.js
Jono M 3f70cc08b7
Fixed create offer API not returning created_at (#19143)
refs https://github.com/TryGhost/Product/issues/4196

The offers API basically returns the data you pass to it, rather than
the created database record. It looks like this is how it was intended
to work in the first place; the `setMilliseconds` is because the test
helper expects `.000Z`, which I assume is because MySQL will strip off
the milliseconds when it's saved.
2023-11-28 15:11:35 +00:00

634 lines
18 KiB
JavaScript

const {agentProvider, fixtureManager, matchers} = require('../../utils/e2e-framework');
const {anyContentVersion, anyEtag, anyObjectId, anyLocationFor, anyErrorId, anyISODateTime} = matchers;
const should = require('should');
const models = require('../../../core/server/models');
const sinon = require('sinon');
const logging = require('@tryghost/logging');
let agent;
async function getPaidProduct() {
return await models.Product.findOne({type: 'paid'});
}
async function getFreeProduct() {
return await models.Product.findOne({type: 'free'});
}
describe('Offers API', function () {
let defaultTier;
let savedOffer;
let trialOffer;
before(async function () {
agent = await agentProvider.getAdminAPIAgent();
await fixtureManager.init();
await agent.loginAsOwner();
defaultTier = await getPaidProduct();
});
this.afterEach(function () {
sinon.restore();
});
it('Has no initial offers', async function () {
await agent
.get(`offers/`)
.expectStatus(200)
.matchHeaderSnapshot({
'content-version': anyContentVersion,
etag: anyEtag
})
.matchBodySnapshot();
});
it('Can add a new offer', async function () {
const newOffer = {
name: 'Black Friday',
code: 'black-friday',
display_title: 'Black Friday Sale!',
display_description: '10% off on yearly plan',
type: 'percent',
cadence: 'year',
amount: 12,
duration: 'once',
duration_in_months: null,
currency_restriction: false,
currency: null,
status: 'active',
redemption_count: 0,
tier: {
id: defaultTier.id
}
};
const {body} = await agent
.post(`offers/`)
.body({offers: [newOffer]})
.expectStatus(200)
.matchHeaderSnapshot({
'content-version': anyContentVersion,
etag: anyEtag,
location: anyLocationFor('offers')
})
.matchBodySnapshot({
offers: [{
id: anyObjectId,
tier: {
id: anyObjectId
},
created_at: anyISODateTime
}]
});
savedOffer = body.offers[0];
});
it('Can add a new offer with minimal fields', async function () {
const newOffer = {
name: 'Easter Sales',
code: 'easter',
cadence: 'month',
amount: 50,
duration: 'once',
type: 'percent',
tier: {
id: defaultTier.id
}
};
await agent
.post(`offers/`)
.body({offers: [newOffer]})
.expectStatus(200)
.matchHeaderSnapshot({
'content-version': anyContentVersion,
etag: anyEtag,
location: anyLocationFor('offers')
})
.matchBodySnapshot({
offers: [{
id: anyObjectId,
tier: {
id: anyObjectId
},
created_at: anyISODateTime
}]
});
});
it('Slugifies offer codes', async function () {
const newOffer = {
name: 'Summer Sale',
code: 'Summer sale',
cadence: 'year',
amount: 20,
duration: 'once',
type: 'percent',
tier: {
id: defaultTier.id
}
};
await agent
.post(`offers/`)
.body({offers: [newOffer]})
.expectStatus(200)
.matchHeaderSnapshot({
'content-version': anyContentVersion,
etag: anyEtag,
location: anyLocationFor('offers')
})
.matchBodySnapshot({
offers: [{
id: anyObjectId,
tier: {
id: anyObjectId
},
created_at: anyISODateTime
}]
})
.expect(({body}) => {
body.offers[0].code.should.eql('summer-sale');
});
});
it('Can add a fixed offer', async function () {
const newOffer = {
name: 'Fourth of July Sales',
code: '4th',
cadence: 'year',
amount: 100,
duration: 'once',
type: 'fixed',
currency: 'USD',
tier: {
id: defaultTier.id
}
};
await agent
.post(`offers/`)
.body({offers: [newOffer]})
.expectStatus(200)
.matchHeaderSnapshot({
'content-version': anyContentVersion,
etag: anyEtag,
location: anyLocationFor('offers')
})
.matchBodySnapshot({
offers: [{
id: anyObjectId,
tier: {
id: anyObjectId
},
created_at: anyISODateTime
}]
});
});
it('Can add a trial offer', async function () {
const newOffer = {
name: 'Fourth of July Sales trial',
code: '4th-trial',
cadence: 'year',
amount: 20,
duration: 'trial',
type: 'trial',
currency: 'USD',
tier: {
id: defaultTier.id
}
};
const {body} = await agent
.post(`offers/`)
.body({offers: [newOffer]})
.expectStatus(200)
.matchHeaderSnapshot({
'content-version': anyContentVersion,
etag: anyEtag,
location: anyLocationFor('offers')
})
.matchBodySnapshot({
offers: [{
id: anyObjectId,
tier: {
id: anyObjectId
},
created_at: anyISODateTime
}]
});
trialOffer = body.offers[0];
});
it('Cannot create offer with same code', async function () {
sinon.stub(logging, 'error');
const newOffer = {
name: 'Fourth of July',
code: '4th',
cadence: 'year',
amount: 200,
duration: 'once',
type: 'fixed',
currency: 'USD',
tier: {
id: defaultTier.id
}
};
await agent
.post(`offers/`)
.body({offers: [newOffer]})
.expectStatus(400)
.matchHeaderSnapshot({
'content-version': anyContentVersion,
etag: anyEtag
})
.matchBodySnapshot({
errors: [{
id: anyErrorId
}]
});
});
it('Cannot create offer with same slugified code', async function () {
sinon.stub(logging, 'error');
const newOffer = {
name: 'Another Black Friday Sale',
code: 'black friday',
cadence: 'year',
amount: 200,
duration: 'once',
type: 'fixed',
currency: 'USD',
tier: {
id: defaultTier.id
}
};
await agent
.post(`offers/`)
.body({offers: [newOffer]})
.expectStatus(400)
.matchHeaderSnapshot({
'content-version': anyContentVersion,
etag: anyEtag
})
.matchBodySnapshot({
errors: [{
id: anyErrorId
}]
});
});
it('Cannot create offer with same name', async function () {
sinon.stub(logging, 'error');
const newOffer = {
name: 'Fourth of July Sales',
code: 'july4',
cadence: 'year',
amount: 150,
duration: 'once',
type: 'fixed',
currency: 'USD',
tier: {
id: defaultTier.id
}
};
await agent
.post(`offers/`)
.body({offers: [newOffer]})
.expectStatus(400)
.matchHeaderSnapshot({
'content-version': anyContentVersion,
etag: anyEtag
})
.matchBodySnapshot({
errors: [{
id: anyErrorId
}]
});
});
it('Can browse', async function () {
await agent
.get(`offers/`)
.expectStatus(200)
.matchHeaderSnapshot({
'content-version': anyContentVersion,
etag: anyEtag
})
.matchBodySnapshot({
offers: new Array(5).fill({
id: anyObjectId,
tier: {
id: anyObjectId
},
created_at: anyISODateTime
})
});
});
it('Can get a single offer', async function () {
await agent
.get(`offers/${savedOffer.id}/`)
.expectStatus(200)
.matchHeaderSnapshot({
'content-version': anyContentVersion,
etag: anyEtag
})
.matchBodySnapshot({
offers: new Array(1).fill({
id: anyObjectId,
tier: {
id: anyObjectId
},
created_at: anyISODateTime
})
});
});
it('Can get a trial offer', async function () {
await agent
.get(`offers/${trialOffer.id}/`)
.expectStatus(200)
.matchHeaderSnapshot({
'content-version': anyContentVersion,
etag: anyEtag
})
.matchBodySnapshot({
offers: new Array(1).fill({
id: anyObjectId,
type: 'trial',
duration: 'trial',
tier: {
id: anyObjectId
},
created_at: anyISODateTime
})
});
});
it('Can edit an offer', async function () {
// We can change all fields except discount related fields
let updatedOffer = {
name: 'Cyber Monday',
code: 'cyber monday',
display_title: 'Cyber Monday Sale!',
display_description: '10% off on yearly plan, only today'
};
await agent
.put(`offers/${savedOffer.id}/`)
.body({offers: [updatedOffer]})
.expectStatus(200)
.matchHeaderSnapshot({
'content-version': anyContentVersion,
etag: anyEtag
})
.matchBodySnapshot({
offers: new Array(1).fill({
id: anyObjectId,
tier: {
id: anyObjectId
},
created_at: anyISODateTime
})
})
.expect(({body}) => {
// Test if all the changes were applied, and that the code has been slugified
body.offers[0].should.match({...updatedOffer, code: 'cyber-monday'});
});
});
it('Cannot update offer code to one that exists', async function () {
sinon.stub(logging, 'error');
// We can change all fields except discount related fields
let updatedOffer = {
code: '4th'
};
await agent
.put(`offers/${savedOffer.id}/`)
.body({offers: [updatedOffer]})
.expectStatus(400)
.matchHeaderSnapshot({
'content-version': anyContentVersion,
etag: anyEtag
})
.matchBodySnapshot({
errors: new Array(1).fill({
id: anyErrorId
})
});
});
it('Cannot update offer code to one that exists after it is slugified', async function () {
sinon.stub(logging, 'error');
// We can change all fields except discount related fields
let updatedOffer = {
code: 'Summer sale'
};
await agent
.put(`offers/${savedOffer.id}/`)
.body({offers: [updatedOffer]})
.expectStatus(400)
.matchHeaderSnapshot({
'content-version': anyContentVersion,
etag: anyEtag
})
.matchBodySnapshot({
errors: new Array(1).fill({
id: anyErrorId
})
});
});
it('Cannot update offer name to one that exists', async function () {
sinon.stub(logging, 'error');
// We can change all fields except discount related fields
let updatedOffer = {
name: 'Easter Sales'
};
await agent
.put(`offers/${savedOffer.id}/`)
.body({offers: [updatedOffer]})
.expectStatus(400)
.matchHeaderSnapshot({
'content-version': anyContentVersion,
etag: anyEtag
})
.matchBodySnapshot({
errors: new Array(1).fill({
id: anyErrorId
})
});
});
it('Can archive an offer', async function () {
// We can change all fields except discount related fields
let updatedOffer = {
status: 'archived'
};
await agent
.put(`offers/${savedOffer.id}/`)
.body({offers: [updatedOffer]})
.expectStatus(200)
.matchHeaderSnapshot({
'content-version': anyContentVersion,
etag: anyEtag
})
.matchBodySnapshot({
offers: new Array(1).fill({
id: anyObjectId,
tier: {
id: anyObjectId
},
created_at: anyISODateTime
})
})
.expect(({body}) => {
body.offers[0].should.match(updatedOffer);
});
});
it('Can browse archived', async function () {
const filter = encodeURIComponent(`status:archived`);
await agent
.get(`offers/?filter=${filter}`)
.expectStatus(200)
.matchHeaderSnapshot({
'content-version': anyContentVersion,
etag: anyEtag
})
.matchBodySnapshot({
offers: new Array(1).fill({
id: anyObjectId,
tier: {
id: anyObjectId
},
created_at: anyISODateTime
})
});
});
it('Can browse active', async function () {
const filter = encodeURIComponent(`status:active`);
await agent
.get(`offers/?filter=${filter}`)
.expectStatus(200)
.matchHeaderSnapshot({
'content-version': anyContentVersion,
etag: anyEtag
})
.matchBodySnapshot({
offers: new Array(4).fill({
id: anyObjectId,
tier: {
id: anyObjectId
},
created_at: anyISODateTime
})
});
});
it('Cannot update offer cadence', async function () {
// We can change all fields except discount related fields
let updatedOffer = {
cadence: 'month'
};
await agent
.put(`offers/${savedOffer.id}/`)
.body({offers: [updatedOffer]})
.expectStatus(200)
.matchHeaderSnapshot({
'content-version': anyContentVersion,
etag: anyEtag
})
.matchBodySnapshot({
offers: new Array(1).fill({
id: anyObjectId,
tier: {
id: anyObjectId
},
created_at: anyISODateTime
})
})
.expect(({body}) => {
body.offers[0].cadence.should.eql('year');
});
});
it('Cannot update offer amount', async function () {
// We can change all fields except discount related fields
let updatedOffer = {
amount: 20
};
// No validation errors are thrown at the moment
await agent
.put(`offers/${savedOffer.id}/`)
.body({offers: [updatedOffer]})
.expectStatus(200)
.matchHeaderSnapshot({
'content-version': anyContentVersion,
etag: anyEtag
})
.matchBodySnapshot({
offers: new Array(1).fill({
id: anyObjectId,
tier: {
id: anyObjectId
},
created_at: anyISODateTime
})
})
.expect(({body}) => {
body.offers[0].amount.should.eql(12);
});
});
it('Cannot update offer tier', async function () {
// We can change all fields except discount related fields
const freeTier = await getFreeProduct();
let updatedOffer = {
tier: {
id: freeTier.id
}
};
// No validation errors are thrown at the moment
await agent
.put(`offers/${savedOffer.id}/`)
.body({offers: [updatedOffer]})
.expectStatus(200)
.matchHeaderSnapshot({
'content-version': anyContentVersion,
etag: anyEtag
})
.matchBodySnapshot({
offers: new Array(1).fill({
id: anyObjectId,
tier: {
id: anyObjectId
},
created_at: anyISODateTime
})
})
.expect(({body}) => {
body.offers[0].tier.id.should.eql(defaultTier.id);
});
});
});