Merge pull request #3800 from overleaf/ab-queue-onboarding-emails
Implement queuing for onboarding emails GitOrigin-RevId: f1eca149a6a2cab35b4cf9c3889dc384372fd453
This commit is contained in:
@@ -13,15 +13,28 @@ describe('AnalyticsManager', function() {
|
||||
this.Settings = {
|
||||
analytics: { enabled: true }
|
||||
}
|
||||
|
||||
this.analyticsEventsQueue = {
|
||||
add: sinon.stub().resolves(),
|
||||
process: sinon.stub().resolves()
|
||||
}
|
||||
this.analyticsEditingSessionQueue = {
|
||||
add: sinon.stub().resolves(),
|
||||
process: sinon.stub().resolves()
|
||||
}
|
||||
this.onboardingEmailsQueue = {
|
||||
add: sinon.stub().resolves(),
|
||||
process: sinon.stub().resolves()
|
||||
}
|
||||
const self = this
|
||||
this.Queues = {
|
||||
analytics: {
|
||||
events: {
|
||||
add: sinon.stub().resolves()
|
||||
},
|
||||
editingSessions: {
|
||||
add: sinon.stub().resolves()
|
||||
}
|
||||
getAnalyticsEventsQueue: () => {
|
||||
return self.analyticsEventsQueue
|
||||
},
|
||||
getAnalyticsEditingSessionsQueue: () => {
|
||||
return self.analyticsEditingSessionQueue
|
||||
},
|
||||
getOnboardingEmailsQueue: () => {
|
||||
return self.onboardingEmailsQueue
|
||||
}
|
||||
}
|
||||
this.backgroundRequest = sinon.stub().yields()
|
||||
@@ -44,13 +57,13 @@ describe('AnalyticsManager', function() {
|
||||
it('user is smoke test user', function() {
|
||||
this.Settings.smokeTest = { userId: this.fakeUserId }
|
||||
this.AnalyticsManager.identifyUser(this.fakeUserId, '')
|
||||
sinon.assert.notCalled(this.Queues.analytics.events.add)
|
||||
sinon.assert.notCalled(this.analyticsEventsQueue.add)
|
||||
})
|
||||
|
||||
it('analytics service is disabled', function() {
|
||||
this.Settings.analytics.enabled = false
|
||||
this.AnalyticsManager.identifyUser(this.fakeUserId, '')
|
||||
sinon.assert.notCalled(this.Queues.analytics.events.add)
|
||||
sinon.assert.notCalled(this.analyticsEventsQueue.add)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -58,20 +71,16 @@ describe('AnalyticsManager', function() {
|
||||
it('identifyUser', function() {
|
||||
const oldUserId = '456def'
|
||||
this.AnalyticsManager.identifyUser(this.fakeUserId, oldUserId)
|
||||
sinon.assert.calledWithMatch(
|
||||
this.Queues.analytics.events.add,
|
||||
'identify',
|
||||
{
|
||||
userId: this.fakeUserId,
|
||||
oldUserId
|
||||
}
|
||||
)
|
||||
sinon.assert.calledWithMatch(this.analyticsEventsQueue.add, 'identify', {
|
||||
userId: this.fakeUserId,
|
||||
oldUserId
|
||||
})
|
||||
})
|
||||
|
||||
it('recordEvent', function() {
|
||||
const event = 'fake-event'
|
||||
this.AnalyticsManager.recordEvent(this.fakeUserId, event, null)
|
||||
sinon.assert.calledWithMatch(this.Queues.analytics.events.add, 'event', {
|
||||
sinon.assert.calledWithMatch(this.analyticsEventsQueue.add, 'event', {
|
||||
event,
|
||||
userId: this.fakeUserId,
|
||||
segmentation: null
|
||||
@@ -86,7 +95,7 @@ describe('AnalyticsManager', function() {
|
||||
projectId,
|
||||
countryCode
|
||||
)
|
||||
sinon.assert.calledWithMatch(this.Queues.analytics.editingSessions.add, {
|
||||
sinon.assert.calledWithMatch(this.analyticsEditingSessionQueue.add, {
|
||||
userId: this.fakeUserId,
|
||||
projectId,
|
||||
countryCode
|
||||
|
||||
@@ -50,6 +50,9 @@ describe('UserCreator', function() {
|
||||
}),
|
||||
'../Analytics/AnalyticsManager': (this.Analytics = {
|
||||
recordEvent: sinon.stub()
|
||||
}),
|
||||
'./UserOnboardingEmailManager': (this.UserOnboardingEmailManager = {
|
||||
scheduleOnboardingEmail: sinon.stub()
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -276,6 +279,17 @@ describe('UserCreator', function() {
|
||||
'user-registered'
|
||||
)
|
||||
})
|
||||
|
||||
it('should schedule an onboarding email on registration', async function() {
|
||||
const user = await this.UserCreator.promises.createNewUser({
|
||||
email: this.email
|
||||
})
|
||||
assert.equal(user.email, this.email)
|
||||
sinon.assert.calledWith(
|
||||
this.UserOnboardingEmailManager.scheduleOnboardingEmail,
|
||||
user
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
const modulePath =
|
||||
'../../../../app/src/Features/User/UserOnboardingController.js'
|
||||
const { ObjectId } = require('mongodb')
|
||||
const sinon = require('sinon')
|
||||
|
||||
describe('UserOnboardingController', function() {
|
||||
beforeEach(function() {
|
||||
this.date = new Date().getTime()
|
||||
sinon.useFakeTimers(this.date)
|
||||
|
||||
this.users = [
|
||||
{
|
||||
_id: ObjectId('00000001f037be01a0e3a541')
|
||||
},
|
||||
{
|
||||
_id: ObjectId('00000001f037be01a0e3a542')
|
||||
},
|
||||
{
|
||||
_id: ObjectId('00000001f037be01a0e3a543')
|
||||
}
|
||||
]
|
||||
|
||||
this.mongodb = {
|
||||
db: {
|
||||
users: {
|
||||
find: sinon
|
||||
.stub()
|
||||
.returns({ toArray: sinon.stub().yields(null, this.users) })
|
||||
}
|
||||
},
|
||||
ObjectId: ObjectId
|
||||
}
|
||||
|
||||
this.logger = {
|
||||
log() {}
|
||||
}
|
||||
|
||||
this.UserUpdater = {
|
||||
updateUser: sinon.stub().callsArgWith(2, null)
|
||||
}
|
||||
|
||||
this.EmailHandler = {
|
||||
sendEmail: sinon.stub().callsArgWith(2)
|
||||
}
|
||||
|
||||
this.UserOnboardingController = SandboxedModule.require(modulePath, {
|
||||
requires: {
|
||||
'../../infrastructure/mongodb': this.mongodb,
|
||||
'./UserUpdater': this.UserUpdater,
|
||||
'../Email/EmailHandler': this.EmailHandler,
|
||||
'logger-sharelatex': this.logger
|
||||
}
|
||||
})
|
||||
this.req = {}
|
||||
this.res = {
|
||||
setTimeout: sinon.stub()
|
||||
}
|
||||
})
|
||||
|
||||
it('sends onboarding emails', function(done) {
|
||||
this.res.send = ids => {
|
||||
ids.length.should.equal(3)
|
||||
this.mongodb.db.users.find.calledOnce.should.equal(true)
|
||||
this.EmailHandler.sendEmail.calledThrice.should.equal(true)
|
||||
this.UserUpdater.updateUser.calledThrice.should.equal(true)
|
||||
for (var i = 0; i < 3; i++) {
|
||||
this.UserUpdater.updateUser
|
||||
.calledWith(
|
||||
this.users[0]._id,
|
||||
sinon.match({
|
||||
$set: { onboardingEmailSentAt: new Date(this.date) }
|
||||
})
|
||||
)
|
||||
.should.equal(true)
|
||||
}
|
||||
done()
|
||||
}
|
||||
this.UserOnboardingController.sendRecentSignupOnboardingEmails(
|
||||
this.req,
|
||||
this.res
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,105 @@
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
const path = require('path')
|
||||
const sinon = require('sinon')
|
||||
|
||||
const MODULE_PATH = path.join(
|
||||
__dirname,
|
||||
'../../../../app/src/Features/User/UserOnboardingEmailManager'
|
||||
)
|
||||
|
||||
describe('UserOnboardingEmailManager', function() {
|
||||
beforeEach(function() {
|
||||
this.fakeUserId = '123abc'
|
||||
this.fakeUserEmail = 'frog@overleaf.com'
|
||||
this.onboardingEmailsQueue = {
|
||||
add: sinon.stub().resolves(),
|
||||
process: callback => {
|
||||
this.queueProcessFunction = callback
|
||||
}
|
||||
}
|
||||
const self = this
|
||||
this.Queues = {
|
||||
getOnboardingEmailsQueue: () => {
|
||||
return self.onboardingEmailsQueue
|
||||
}
|
||||
}
|
||||
this.UserGetter = {
|
||||
promises: {
|
||||
getUser: sinon.stub().resolves({
|
||||
_id: this.fakeUserId,
|
||||
email: this.fakeUserEmail
|
||||
})
|
||||
}
|
||||
}
|
||||
this.EmailHandler = {
|
||||
promises: {
|
||||
sendEmail: sinon.stub().resolves()
|
||||
}
|
||||
}
|
||||
this.UserUpdater = {
|
||||
promises: {
|
||||
updateUser: sinon.stub().resolves()
|
||||
}
|
||||
}
|
||||
this.request = sinon.stub().yields()
|
||||
this.UserOnboardingEmailManager = SandboxedModule.require(MODULE_PATH, {
|
||||
globals: {
|
||||
console: console
|
||||
},
|
||||
requires: {
|
||||
'../../infrastructure/Queues': this.Queues,
|
||||
'../Email/EmailHandler': this.EmailHandler,
|
||||
'./UserGetter': this.UserGetter,
|
||||
'./UserUpdater': this.UserUpdater
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('schedule email', function() {
|
||||
it('should schedule delayed job on queue', function() {
|
||||
this.UserOnboardingEmailManager.scheduleOnboardingEmail({
|
||||
_id: this.fakeUserId
|
||||
})
|
||||
sinon.assert.calledWith(
|
||||
this.onboardingEmailsQueue.add,
|
||||
{ userId: this.fakeUserId },
|
||||
{ delay: 24 * 60 * 60 * 1000 }
|
||||
)
|
||||
})
|
||||
|
||||
it('queue process callback should send onboarding email and update user', async function() {
|
||||
await this.queueProcessFunction({ data: { userId: this.fakeUserId } })
|
||||
sinon.assert.calledWith(
|
||||
this.UserGetter.promises.getUser,
|
||||
{ _id: this.fakeUserId },
|
||||
{ email: 1 }
|
||||
)
|
||||
sinon.assert.calledWith(
|
||||
this.EmailHandler.promises.sendEmail,
|
||||
'userOnboardingEmail',
|
||||
{
|
||||
to: this.fakeUserEmail
|
||||
}
|
||||
)
|
||||
sinon.assert.calledWith(
|
||||
this.UserUpdater.promises.updateUser,
|
||||
this.fakeUserId,
|
||||
{
|
||||
$set: { onboardingEmailSentAt: sinon.match.date }
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('queue process callback should stop if user is not found', async function() {
|
||||
this.UserGetter.promises.getUser = sinon.stub().resolves()
|
||||
await this.queueProcessFunction({ data: { userId: 'deleted-user' } })
|
||||
sinon.assert.calledWith(
|
||||
this.UserGetter.promises.getUser,
|
||||
{ _id: 'deleted-user' },
|
||||
{ email: 1 }
|
||||
)
|
||||
sinon.assert.notCalled(this.EmailHandler.promises.sendEmail)
|
||||
sinon.assert.notCalled(this.UserUpdater.promises.updateUser)
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user