mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-30 14:22:07 +03:00
3f70cc08b7
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.
634 lines
18 KiB
JavaScript
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);
|
|
});
|
|
});
|
|
});
|