Ghost/ghost/offers/test/lib/domain/models/Offer.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

415 lines
14 KiB
JavaScript

const should = require('should');
const ObjectID = require('bson-objectid').default;
const errors = require('../../../../lib/domain/errors');
const Offer = require('../../../../lib/domain/models/Offer');
const OfferName = require('../../../../lib/domain/models/OfferName');
const OfferCode = require('../../../../lib/domain/models/OfferCode');
function createUniqueChecker(dupe) {
return {
async isUniqueCode(code) {
return code.value !== dupe;
},
async isUniqueName(name) {
return name.value !== dupe;
}
};
}
const mockUniqueChecker = createUniqueChecker('dupe');
describe('Offer', function () {
describe('Offer#create factory', function () {
it('Creates a valid instance of an Offer', async function () {
const offer = await Offer.create({
name: 'My Offer',
code: 'offer-code',
display_title: 'My Offer Title',
display_description: 'My Offer Description',
cadence: 'month',
type: 'fixed',
amount: 1000,
duration: 'forever',
currency: 'USD',
tier: {
id: ObjectID()
}
}, mockUniqueChecker);
should.ok(
offer instanceof Offer,
'Offer.create should return an instance of Offer'
);
});
it('Creates a valid instance of a trial Offer', async function () {
const offer = await Offer.create({
name: 'My Trial Offer',
code: 'offer-code-trial',
display_title: 'My Offer Title',
display_description: 'My Offer Description',
cadence: 'month',
type: 'trial',
amount: 10,
duration: 'trial',
currency: 'USD',
tier: {
id: ObjectID()
}
}, mockUniqueChecker);
should.ok(
offer instanceof Offer,
'Offer.create should return an instance of Offer'
);
});
it('Throws an error if the duration for trial offer is not right', async function () {
await Offer.create({
name: 'My Trial Offer',
code: 'trial-test',
display_title: 'My Offer Title',
display_description: 'My Offer Description',
cadence: 'month',
type: 'trial',
amount: 10,
duration: 'forever',
currency: 'USD',
tier: {
id: ObjectID()
}
}, mockUniqueChecker).then(() => {
should.fail('Expected an error');
}, (err) => {
should.ok(err);
});
});
it('Throws an error if the code is not unique', async function () {
await Offer.create({
name: 'My Offer',
code: 'dupe',
display_title: 'My Offer Title',
display_description: 'My Offer Description',
cadence: 'month',
type: 'fixed',
amount: 1000,
duration: 'forever',
currency: 'USD',
tier: {
id: ObjectID()
}
}, mockUniqueChecker).then(() => {
should.fail('Expected an error');
}, (err) => {
should.ok(err);
});
});
it('Throws an error if the name is not unique', async function () {
await Offer.create({
name: 'dupe',
code: 'offer-code',
display_title: 'My Offer Title',
display_description: 'My Offer Description',
cadence: 'month',
type: 'fixed',
amount: 1000,
duration: 'forever',
currency: 'USD',
tier: {
id: ObjectID()
}
}, mockUniqueChecker).then(() => {
should.fail('Expected an error');
}, (err) => {
should.ok(err);
});
});
it('Wraps the input values in value objects', async function () {
const data = {
name: 'My Offer',
code: 'offer-code',
display_title: 'My Offer Title',
display_description: 'My Offer Description',
cadence: 'month',
type: 'fixed',
amount: 1000,
duration: 'forever',
currency: 'USD',
tier: {
id: ObjectID()
}
};
const offer = await Offer.create(data, mockUniqueChecker);
should.ok(offer.name.equals(OfferName.create(data.name)));
});
it('Errors if the repeating duration is applied to the year cadence', async function () {
const data = {
name: 'My Offer',
code: 'offer-code',
display_title: 'My Offer Title',
display_description: 'My Offer Description',
cadence: 'year',
type: 'fixed',
amount: 1000,
duration: 'repeating',
duration_in_months: 12,
currency: 'USD',
tier: {
id: ObjectID()
}
};
try {
await Offer.create(data, mockUniqueChecker);
should.fail();
} catch (err) {
should.ok(err instanceof errors.InvalidOfferDuration);
}
});
it('Has a currency of null if the type is percent', async function () {
const data = {
name: 'My Offer',
code: 'offer-code',
display_title: 'My Offer Title',
display_description: 'My Offer Description',
cadence: 'year',
type: 'percent',
amount: 20,
duration: 'once',
currency: 'USD',
tier: {
id: ObjectID()
}
};
const offer = await Offer.create(data, mockUniqueChecker);
should.equal(offer.currency, null);
});
it('Has a currency of null if the type is trial', async function () {
const data = {
name: 'My Trial Offer',
code: 'offer-code-trial',
display_title: 'My Offer Title',
display_description: 'My Offer Description',
cadence: 'year',
type: 'trial',
amount: 20,
duration: 'trial',
currency: 'USD',
tier: {
id: ObjectID()
}
};
const offer = await Offer.create(data, mockUniqueChecker);
should.equal(offer.currency, null);
});
it('Can handle ObjectID, string and no id', async function () {
const data = {
name: 'My Offer',
code: 'offer-code',
display_title: 'My Offer Title',
display_description: 'My Offer Description',
cadence: 'year',
type: 'percent',
amount: 20,
duration: 'once',
currency: 'USD',
tier: {
id: ObjectID()
}
};
await Offer.create({...data, id: ObjectID()}, mockUniqueChecker);
await Offer.create({...data, id: ObjectID().toHexString()}, mockUniqueChecker);
await Offer.create({...data, id: undefined}, mockUniqueChecker);
});
it('Does not accept a redemptionCount for new Offers', async function () {
const data = {
name: 'My Offer',
code: 'offer-code',
display_title: 'My Offer Title',
display_description: 'My Offer Description',
cadence: 'month',
type: 'fixed',
amount: 1000,
duration: 'repeating',
duration_in_months: 12,
currency: 'USD',
tier: {
id: ObjectID()
}
};
await Offer.create(data, mockUniqueChecker);
await Offer.create({...data, redemptionCount: 2}, mockUniqueChecker).then(() => {
should.fail('Expected an error');
}, (err) => {
should.ok(err);
});
});
it('Sets createdAt for new Offers', async function () {
const data = {
name: 'My Offer',
code: 'offer-code',
display_title: 'My Offer Title',
display_description: 'My Offer Description',
cadence: 'month',
type: 'fixed',
amount: 1000,
duration: 'forever',
currency: 'USD',
tier: {
id: ObjectID()
}
};
const offer = await Offer.create(data, mockUniqueChecker);
should.equal(typeof offer.createdAt, 'string');
});
});
describe('#updateCode', function () {
it('Requires the code to be unique if it has changed', async function () {
const data = {
name: 'My Offer',
code: 'offer-code',
display_title: 'My Offer Title',
display_description: 'My Offer Description',
cadence: 'month',
type: 'fixed',
amount: 1000,
duration: 'repeating',
duration_in_months: 12,
currency: 'USD',
tier: {
id: ObjectID()
}
};
const offer = await Offer.create(data, mockUniqueChecker);
await offer.updateCode(OfferCode.create('dupe'), mockUniqueChecker).then(() => {
should.fail('Expected an error');
}, (err) => {
should.ok(err);
});
const offer2 = await Offer.create({...data, code: 'dupe'}, createUniqueChecker());
await offer2.updateCode(OfferCode.create('dupe'), mockUniqueChecker);
});
it('Does not allow code to be changed twice', async function () {
const data = {
name: 'My Offer',
code: 'offer-code',
display_title: 'My Offer Title',
display_description: 'My Offer Description',
cadence: 'month',
type: 'fixed',
amount: 1000,
duration: 'repeating',
duration_in_months: 12,
currency: 'USD',
tier: {
id: ObjectID()
}
};
const offer = await Offer.create(data, mockUniqueChecker);
await offer.updateCode(OfferCode.create('changed'), mockUniqueChecker);
await offer.updateCode(OfferCode.create('changed-again'), mockUniqueChecker).then(() => {
should.fail('Expected an error');
}, (err) => {
should.ok(err);
});
});
});
describe('#updateName', function () {
it('Requires the name to be unique if it has changed', async function () {
const data = {
name: 'My Offer',
code: 'offer-code',
display_title: 'My Offer Title',
display_description: 'My Offer Description',
cadence: 'month',
type: 'fixed',
amount: 1000,
duration: 'repeating',
duration_in_months: 12,
currency: 'USD',
tier: {
id: ObjectID()
}
};
const offer = await Offer.create(data, mockUniqueChecker);
await offer.updateName(OfferName.create('Unique!'), mockUniqueChecker);
await offer.updateName(OfferName.create('dupe'), mockUniqueChecker).then(() => {
should.fail('Expected an error');
}, (err) => {
should.ok(err);
});
const offer2 = await Offer.create({...data, name: 'dupe'}, createUniqueChecker());
await offer2.updateName(OfferName.create('dupe'), mockUniqueChecker);
});
});
describe('Properties', function () {
it('Exposes getters for its properties', async function () {
const data = {
name: 'My Offer',
code: 'offer-code',
display_title: 'My Offer Title',
display_description: 'My Offer Description',
cadence: 'month',
type: 'fixed',
amount: 1000,
duration: 'repeating',
duration_in_months: 12,
currency: 'USD',
tier: {
id: ObjectID()
}
};
const offer = await Offer.create(data, mockUniqueChecker);
should.exist(offer.id);
should.exist(offer.name);
should.exist(offer.code);
should.exist(offer.currency);
should.exist(offer.duration);
should.exist(offer.status);
should.exist(offer.redemptionCount);
should.exist(offer.displayTitle);
should.exist(offer.displayDescription);
should.exist(offer.tier);
should.exist(offer.cadence);
should.exist(offer.type);
should.exist(offer.amount);
should.exist(offer.isNew);
});
});
});