diff --git a/server/helpers/notification/notificateDelegatorUtils.js b/server/helpers/notification/notificateDelegatorUtils.js index 70b796d..75cf78c 100644 --- a/server/helpers/notification/notificateDelegatorUtils.js +++ b/server/helpers/notification/notificateDelegatorUtils.js @@ -32,6 +32,20 @@ const sendEmailRewardCallNotificationToDelegators = async currentRoundInfo => { currentRoundId ) + if ( + delegator.status === constants.DELEGATOR_STATUS.Unbonded && + subscriber.lastEmailSentForUnbondedStatus + ) { + console.log( + `[Notificate-Delegators] - Not sending email to ${subscriber.email} because is in Unbonded state and already sent an email in the last ${subscriber.lastEmailSentForUnbondedStatus} round` + ) + continue + } else if (subscriber.lastEmailSentForUnbondedStatus) { + // Unset lastEmailSentForUnbondedStatus for any other status than Unbonded and property lastEmailSentForUnbondedStatus not null + subscriber.lastEmailSentForUnbondedStatus = null + await subscriber.save() + } + if (!shouldSubscriberReceiveNotifications) { console.log( `[Notificate-Delegators] - Not sending email to ${subscriber.email} because already sent an email in the last ${subscriber.lastEmailSent} round and the frequency is ${subscriber.emailFrequency}` diff --git a/server/helpers/sendDelegatorEmail.js b/server/helpers/sendDelegatorEmail.js index 88a0110..94bb976 100644 --- a/server/helpers/sendDelegatorEmail.js +++ b/server/helpers/sendDelegatorEmail.js @@ -83,6 +83,9 @@ const getBodyAndTemplateIdBasedOnDelegatorStatus = ( let body = {} switch (delegator.status) { case constants.DELEGATOR_STATUS.Unbonded: + subscriber.lastEmailSentForUnbondedStatus = currentRoundInfo.id + subscriber.save().then(doc => {}) + templateId = sendgridTemplateIdClaimRewardUnbondedState break case constants.DELEGATOR_STATUS.Unbonding: diff --git a/server/subscriber/subscriber.model.js b/server/subscriber/subscriber.model.js index ca34d75..b84ee38 100644 --- a/server/subscriber/subscriber.model.js +++ b/server/subscriber/subscriber.model.js @@ -44,6 +44,11 @@ let SubscriberSchema = new mongoose.Schema({ return Math.floor(Math.random() * 900000000300000000000) + 1000000000000000 } }, + // References the roundID in which the last email was sent for the unbonded status + lastEmailSentForUnbondedStatus: { + type: String, + default: null + }, // References the roundID in which the last email was sent lastEmailSent: { type: String, diff --git a/test/notificate-delegators-utils.test.js b/test/notificate-delegators-utils.test.js index 9f6145f..ef6aef1 100644 --- a/test/notificate-delegators-utils.test.js +++ b/test/notificate-delegators-utils.test.js @@ -612,6 +612,196 @@ describe('## NotificateDelegatorsUtils', () => { expect(getCurrentRoundInfoStub.called) expect(getSubscriptorRoleStub.called) }) + + it('Should continue if the subscriber is in unbonded state, and lastEmailSentForUnbondedStatus is set', async () => { + // given + let delegator = createDelegator('0x12312312312') + + const currentRound = 10 + const currentRoundInfo = { + id: currentRound + } + const subscriber = createSubscriber() + subscriber.lastEmailSentForUnbondedStatus = 2 + const subscribers = [subscriber] + + const constants = getLivepeerDefaultConstants() + delegator.status = constants.DELEGATOR_STATUS.Unbonded + + const subscriptorRoleReturn = { role: constants.ROLE.DELEGATOR, constants, delegator } + const logExpectation1 = `[Notificate-Delegators] - Start sending email notification to delegators` + const logExpectation2 = `[Notificate-Delegators] - Not sending email to ${subscriber.email} because is in Unbonded state and already sent an email in the last ${subscriber.lastEmailSentForUnbondedStatus} round` + const logExpectation3 = `[Notificate-Delegators] - Emails subscribers to notify 0` + const logExpectation4 = `[Subscribers-utils] - Returning list of email subscribers delegators` + const logExpectation5 = `[Subscribers-utils] - Amount of email subscribers delegators: ${subscribers.length}` + + // Stubs the return of Subscriber.find to return the list of subscribers + subscriberMock = sinon.mock(Subscriber) + + const expectationSubscriber = subscriberMock + .expects('find') + .once() + .resolves(subscribers) + + // Stubs the return of getSubscriptorRole to make the subscriber a delegate + getSubscriptorRoleStub = sinon + .stub(subscriberUtils, 'getSubscriptorRole') + .returns(subscriptorRoleReturn) + + consoleLogMock = sinon.mock(console) + + const expectationConsole1 = consoleLogMock + .expects('log') + .once() + .withArgs(logExpectation1) + + const expectationConsole2 = consoleLogMock + .expects('log') + .once() + .withArgs(logExpectation2) + + const expectationConsole3 = consoleLogMock + .expects('log') + .once() + .withArgs(logExpectation3) + + const expectationConsole4 = consoleLogMock + .expects('log') + .once() + .withArgs(logExpectation4) + + const expectationConsole5 = consoleLogMock + .expects('log') + .once() + .withArgs(logExpectation5) + + // Stubs the return of getCurrentRoundInfo to return an mocked id + getCurrentRoundInfoStub = sinon + .stub(protocolService, 'getCurrentRoundInfo') + .returns(currentRoundInfo) + + getDidDelegateCalledRewardStub = sinon.stub(utils, 'getDidDelegateCalledReward').returns(true) + + getDelegatorNextRewardStub = sinon.stub(delegatorService, 'getDelegatorNextReward').returns(1) + + getLivepeerDefaultConstantsStub = sinon + .stub(protocolService, 'getLivepeerDefaultConstants') + .returns(constants) + + delegatorEmailUtilsMock = sinon.mock(delegatorEmailUtils) + + const expectation = delegatorEmailUtilsMock + .expects('sendDelegatorNotificationEmail') + .never() + .resolves(null) + + // when + await notificateDelegatorUtils.sendEmailRewardCallNotificationToDelegators(currentRoundInfo) + + // then + consoleLogMock.verify() + subscriberMock.verify() + delegatorEmailUtilsMock.verify() + expect(getLivepeerDefaultConstantsStub.called) + expect(getDelegatorNextRewardStub.called) + expect(getDidDelegateCalledRewardStub.called) + expect(getCurrentRoundInfoStub.called) + expect(getSubscriptorRoleStub.called) + }) + + it('Should not continue if the subscriber is not in the unbonded state, and lastEmailSentForUnbondedStatus is set', async () => { + // given + let delegator = createDelegator('0x12312312312') + + const currentRound = 10 + const currentRoundInfo = { + id: currentRound + } + let subscriberData = createSubscriber() + subscriberData.lastEmailSentForUnbondedStatus = 2 + const subscriber = new Subscriber(subscriberData) + + const subscribers = [subscriber] + + const constants = getLivepeerDefaultConstants() + delegator.status = constants.DELEGATOR_STATUS.Bonded + + const subscriptorRoleReturn = { role: constants.ROLE.DELEGATOR, constants, delegator } + const logExpectation1 = `[Notificate-Delegators] - Start sending email notification to delegators` + const logExpectation2 = `[Notificate-Delegators] - Emails subscribers to notify 1` + const logExpectation3 = `[Subscribers-utils] - Returning list of email subscribers delegators` + const logExpectation4 = `[Subscribers-utils] - Amount of email subscribers delegators: ${subscribers.length}` + + // Stubs the return of Subscriber.find to return the list of subscribers + subscriberMock = sinon.mock(Subscriber) + + const expectationSubscriber = subscriberMock + .expects('find') + .once() + .resolves(subscribers) + + subscriberMockSave = sinon.stub(Subscriber.prototype, 'save').returns(subscriber) + + // Stubs the return of getSubscriptorRole to make the subscriber a delegate + getSubscriptorRoleStub = sinon + .stub(subscriberUtils, 'getSubscriptorRole') + .returns(subscriptorRoleReturn) + + consoleLogMock = sinon.mock(console) + + const expectationConsole1 = consoleLogMock + .expects('log') + .once() + .withArgs(logExpectation1) + + const expectationConsole2 = consoleLogMock + .expects('log') + .once() + .withArgs(logExpectation2) + + const expectationConsole3 = consoleLogMock + .expects('log') + .once() + .withArgs(logExpectation3) + + const expectationConsole4 = consoleLogMock + .expects('log') + .once() + .withArgs(logExpectation4) + + // Stubs the return of getCurrentRoundInfo to return an mocked id + getCurrentRoundInfoStub = sinon + .stub(protocolService, 'getCurrentRoundInfo') + .returns(currentRoundInfo) + + getDidDelegateCalledRewardStub = sinon.stub(utils, 'getDidDelegateCalledReward').returns(true) + + getDelegatorNextRewardStub = sinon.stub(delegatorService, 'getDelegatorNextReward').returns(1) + + getLivepeerDefaultConstantsStub = sinon + .stub(protocolService, 'getLivepeerDefaultConstants') + .returns(constants) + + delegatorEmailUtilsMock = sinon.mock(delegatorEmailUtils) + + const expectation = delegatorEmailUtilsMock + .expects('sendDelegatorNotificationEmail') + .once() + .resolves(null) + + // when + await notificateDelegatorUtils.sendEmailRewardCallNotificationToDelegators(currentRoundInfo) + + // then + consoleLogMock.verify() + subscriberMock.verify() + delegatorEmailUtilsMock.verify() + expect(getLivepeerDefaultConstantsStub.called) + expect(getDelegatorNextRewardStub.called) + expect(getDidDelegateCalledRewardStub.called) + expect(getCurrentRoundInfoStub.called) + expect(getSubscriptorRoleStub.called) + }) }) describe('# sendEmailAfterBondingPeriodHasEndedNotificationToDelegators', () => {