From a8239bfa9736865c8b9de97684eac50ccf11a746 Mon Sep 17 00:00:00 2001 From: Simon Backx Date: Thu, 25 Aug 2022 15:39:37 +0200 Subject: [PATCH] Added ENUM validation for member/subscription created events (#15312) closes https://github.com/TryGhost/Team/issues/1842 - members_created_events: source + attribution_type - members_subscription_created_events: attribution_type - members_subscribe_events: source --- ghost/core/core/server/data/schema/schema.js | 24 +++++-- .../models/member-created-event.test.js | 64 +++++++++++++++++++ .../models/member-subscribe-event.test.js | 28 ++++++++ .../models/subscription-created-event.test.js | 52 +++++++++++++++ 4 files changed, 164 insertions(+), 4 deletions(-) create mode 100644 ghost/core/test/unit/server/models/member-created-event.test.js create mode 100644 ghost/core/test/unit/server/models/member-subscribe-event.test.js create mode 100644 ghost/core/test/unit/server/models/subscription-created-event.test.js diff --git a/ghost/core/core/server/data/schema/schema.js b/ghost/core/core/server/data/schema/schema.js index 2b590d091c..bed6fa6c89 100644 --- a/ghost/core/core/server/data/schema/schema.js +++ b/ghost/core/core/server/data/schema/schema.js @@ -483,9 +483,17 @@ module.exports = { created_at: {type: 'dateTime', nullable: false}, member_id: {type: 'string', maxlength: 24, nullable: false, references: 'members.id', cascadeDelete: true}, attribution_id: {type: 'string', maxlength: 24, nullable: true}, - attribution_type: {type: 'string', maxlength: 50, nullable: true}, + attribution_type: { + type: 'string', maxlength: 50, nullable: true, validations: { + isIn: [['url', 'post', 'page', 'author', 'tag']] + } + }, attribution_url: {type: 'string', maxlength: 2000, nullable: true}, - source: {type: 'string', maxlength: 50, nullable: false} + source: { + type: 'string', maxlength: 50, nullable: false, validations: { + isIn: [['member', 'import', 'system', 'api', 'admin']] + } + } }, members_cancel_events: { id: {type: 'string', maxlength: 24, nullable: false, primary: true}, @@ -613,7 +621,11 @@ module.exports = { member_id: {type: 'string', maxlength: 24, nullable: false, references: 'members.id', cascadeDelete: true}, subscription_id: {type: 'string', maxlength: 24, nullable: false, references: 'members_stripe_customers_subscriptions.id', cascadeDelete: true}, attribution_id: {type: 'string', maxlength: 24, nullable: true}, - attribution_type: {type: 'string', maxlength: 50, nullable: true}, + attribution_type: { + type: 'string', maxlength: 50, nullable: true, validations: { + isIn: [['url', 'post', 'page', 'author', 'tag']] + } + }, attribution_url: {type: 'string', maxlength: 2000, nullable: true} }, offer_redemptions: { @@ -628,7 +640,11 @@ module.exports = { member_id: {type: 'string', maxlength: 24, nullable: false, unique: false, references: 'members.id', cascadeDelete: true}, subscribed: {type: 'bool', nullable: false, defaultTo: true}, created_at: {type: 'dateTime', nullable: false}, - source: {type: 'string', maxlength: 50, nullable: true}, + source: { + type: 'string', maxlength: 50, nullable: true, validations: { + isIn: [['member', 'import', 'system', 'api', 'admin']] + } + }, newsletter_id: {type: 'string', maxlength: 24, nullable: true, references: 'newsletters.id', cascadeDelete: false} }, stripe_products: { diff --git a/ghost/core/test/unit/server/models/member-created-event.test.js b/ghost/core/test/unit/server/models/member-created-event.test.js new file mode 100644 index 0000000000..1f6b33d3a2 --- /dev/null +++ b/ghost/core/test/unit/server/models/member-created-event.test.js @@ -0,0 +1,64 @@ +const should = require('should'); +const sinon = require('sinon'); +const errors = require('@tryghost/errors'); +const models = require('../../../../core/server/models'); + +describe('Unit: models/MemberCreatedEvent', function () { + before(function () { + models.init(); + }); + + afterEach(function () { + sinon.restore(); + }); + + describe('validation', function () { + it('throws error for invalid attribution_type', function () { + return models.MemberCreatedEvent.add({attribution_type: 'invalid', source: 'member', member_id: '123'}) + .then(function () { + throw new Error('expected ValidationError'); + }) + .catch(function (err) { + should(err).lengthOf(1); + (err[0] instanceof errors.ValidationError).should.eql(true); + err[0].context.should.match(/members_created_events\.attribution_type/); + }); + }); + + it('throws if member_id is missing', function () { + return models.MemberCreatedEvent.add({attribution_type: 'post', source: 'member'}) + .then(function () { + throw new Error('expected ValidationError'); + }) + .catch(function (err) { + should(err).lengthOf(1); + (err[0] instanceof errors.ValidationError).should.eql(true); + err[0].context.should.match(/members_created_events\.member_id/); + }); + }); + + it('throws if source is missing', function () { + return models.MemberCreatedEvent.add({attribution_type: 'post', member_id: '123'}) + .then(function () { + throw new Error('expected ValidationError'); + }) + .catch(function (err) { + should(err).lengthOf(1); + (err[0] instanceof errors.ValidationError).should.eql(true); + err[0].context.should.match(/members_created_events\.source/); + }); + }); + + it('throws if source is invalid', function () { + return models.MemberCreatedEvent.add({attribution_type: 'post', member_id: '123', source: 'invalid'}) + .then(function () { + throw new Error('expected ValidationError'); + }) + .catch(function (err) { + should(err).lengthOf(1); + (err[0] instanceof errors.ValidationError).should.eql(true); + err[0].context.should.match(/members_created_events\.source/); + }); + }); + }); +}); diff --git a/ghost/core/test/unit/server/models/member-subscribe-event.test.js b/ghost/core/test/unit/server/models/member-subscribe-event.test.js new file mode 100644 index 0000000000..b275eb098a --- /dev/null +++ b/ghost/core/test/unit/server/models/member-subscribe-event.test.js @@ -0,0 +1,28 @@ +const should = require('should'); +const sinon = require('sinon'); +const errors = require('@tryghost/errors'); +const models = require('../../../../core/server/models'); + +describe('Unit: models/MemberSubscribeEvent', function () { + before(function () { + models.init(); + }); + + afterEach(function () { + sinon.restore(); + }); + + describe('validation', function () { + it('throws if source is invalid', function () { + return models.MemberSubscribeEvent.add({member_id: '123', source: 'invalid'}) + .then(function () { + throw new Error('expected ValidationError'); + }) + .catch(function (err) { + should(err).lengthOf(1); + (err[0] instanceof errors.ValidationError).should.eql(true); + err[0].context.should.match(/members_subscribe_events\.source/); + }); + }); + }); +}); diff --git a/ghost/core/test/unit/server/models/subscription-created-event.test.js b/ghost/core/test/unit/server/models/subscription-created-event.test.js new file mode 100644 index 0000000000..1f145ae496 --- /dev/null +++ b/ghost/core/test/unit/server/models/subscription-created-event.test.js @@ -0,0 +1,52 @@ +const should = require('should'); +const sinon = require('sinon'); +const errors = require('@tryghost/errors'); +const models = require('../../../../core/server/models'); + +describe('Unit: models/SubscriptionCreatedEvent', function () { + before(function () { + models.init(); + }); + + afterEach(function () { + sinon.restore(); + }); + + describe('validation', function () { + it('throws error for invalid attribution_type', function () { + return models.SubscriptionCreatedEvent.add({attribution_type: 'invalid', member_id: '123', subscription_id: '123'}) + .then(function () { + throw new Error('expected ValidationError'); + }) + .catch(function (err) { + should(err).lengthOf(1); + (err[0] instanceof errors.ValidationError).should.eql(true); + err[0].context.should.match(/members_subscription_created_events\.attribution_type/); + }); + }); + + it('throws if member_id is missing', function () { + return models.SubscriptionCreatedEvent.add({attribution_type: 'post', subscription_id: '123'}) + .then(function () { + throw new Error('expected ValidationError'); + }) + .catch(function (err) { + should(err).lengthOf(1); + (err[0] instanceof errors.ValidationError).should.eql(true); + err[0].context.should.match(/members_subscription_created_events\.member_id/); + }); + }); + + it('throws if subscription_id is missing', function () { + return models.SubscriptionCreatedEvent.add({attribution_type: 'post', member_id: '123'}) + .then(function () { + throw new Error('expected ValidationError'); + }) + .catch(function (err) { + should(err).lengthOf(1); + (err[0] instanceof errors.ValidationError).should.eql(true); + err[0].context.should.match(/members_subscription_created_events\.subscription_id/); + }); + }); + }); +});