mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-19 00:11:49 +03:00
2bd597fe0d
refs https://github.com/TryGhost/Product/issues/4153 - We need use the `created_at` timestamp in the new AdminX offers. The API doesn't return that value. - With this change the API returns the created_at property so that we can consume it. --- <!-- Leave the line below if you'd like GitHub Copilot to generate a summary from your commit --> <!-- copilot:summary --> ### <samp>🤖[[deprecated]](https://githubnext.com/copilot-for-prs-sunset) Generated by Copilot at dc282af</samp> This pull request adds a `createdAt` property to the offer domain model, data transfer object, and repository. This allows tracking and auditing the creation and modification of offers and offer codes in `ghost/offers`.
629 lines
18 KiB
JavaScript
629 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
|
|
}
|
|
}]
|
|
});
|
|
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
|
|
}
|
|
}]
|
|
});
|
|
});
|
|
|
|
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
|
|
}
|
|
}]
|
|
})
|
|
.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
|
|
}
|
|
}]
|
|
});
|
|
});
|
|
|
|
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
|
|
}
|
|
}]
|
|
});
|
|
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);
|
|
});
|
|
});
|
|
});
|