mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-05-29 20:11:32 +02:00
Add helper functions for creating change events
GitOrigin-RevId: 26a4cbc8e322c52e12cd3eb7f891d9914cefc70d
This commit is contained in:
122
services/web/app/src/Features/Analytics/EmailChangeHelper.js
Normal file
122
services/web/app/src/Features/Analytics/EmailChangeHelper.js
Normal file
@@ -0,0 +1,122 @@
|
||||
// @ts-check
|
||||
const UserGetter = require('../User/UserGetter')
|
||||
const { registerEmailChange } = require('./AnalyticsManager')
|
||||
|
||||
/**
|
||||
* @typedef {object} EmailData
|
||||
* @property {string} email
|
||||
* @property {Date} createdAt
|
||||
* @property {Date} confirmedAt
|
||||
* @property {boolean} default
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {object} EventData
|
||||
* @property {Date} [emailCreatedAt] ISO string of when the email was created
|
||||
* @property {Date} [emailConfirmedAt] ISO string of when the email was confirmed
|
||||
* @property {Date} [emailDeletedAt] ISO string of when the email was deleted
|
||||
* @property {boolean} [isPrimary] Whether the email is the primary email
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {import('./types').EmailChangePayload} EmailChangePayload
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} userId
|
||||
* @param {string} email
|
||||
* @param {EventData} eventData
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function registerEmailUpdate(userId, email, eventData = {}) {
|
||||
const emailChangeEvent = await makeEmailChangeEvent(userId, email, eventData)
|
||||
|
||||
registerEmailChange({
|
||||
...emailChangeEvent,
|
||||
action: 'updated',
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} userId
|
||||
* @param {string} email
|
||||
* @param {EventData} eventData
|
||||
* @returns {Promise<Omit<EmailChangePayload, 'action'>>}
|
||||
*/
|
||||
async function makeEmailChangeEvent(userId, email, eventData) {
|
||||
const userEmails = await UserGetter.promises.getUserFullEmails(userId)
|
||||
const emailData = userEmails.find(userEmail => userEmail.email === email)
|
||||
|
||||
const filledEventData = fillMissingEventData(eventData, emailData)
|
||||
|
||||
return {
|
||||
userId,
|
||||
email,
|
||||
createdAt: new Date().toISOString(),
|
||||
emailCreatedAt: filledEventData?.emailCreatedAt?.toISOString() ?? null,
|
||||
emailConfirmedAt: filledEventData?.emailConfirmedAt?.toISOString() ?? null,
|
||||
emailDeletedAt: filledEventData?.emailDeletedAt?.toISOString() ?? null,
|
||||
isPrimary: filledEventData?.isPrimary ?? false,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} userId
|
||||
* @param {string} email
|
||||
* @param {EventData} eventData
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function registerEmailCreation(userId, email, eventData = {}) {
|
||||
const emailChangeEvent = await makeEmailChangeEvent(userId, email, eventData)
|
||||
|
||||
registerEmailChange({
|
||||
...emailChangeEvent,
|
||||
action: 'created',
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} userId
|
||||
* @param {string} email
|
||||
* @param {EventData} eventData
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function registerEmailDeletion(userId, email, eventData = {}) {
|
||||
const emailChangeEvent = await makeEmailChangeEvent(userId, email, eventData)
|
||||
|
||||
registerEmailChange({
|
||||
...emailChangeEvent,
|
||||
action: 'deleted',
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {EventData} eventData
|
||||
* @param {EmailData | null} emailData
|
||||
* @return {EventData}
|
||||
*/
|
||||
function fillMissingEventData(eventData, emailData) {
|
||||
if (emailData) {
|
||||
if (!eventData.emailCreatedAt) {
|
||||
eventData.emailCreatedAt = emailData.createdAt
|
||||
}
|
||||
if (!eventData.emailConfirmedAt && emailData.confirmedAt) {
|
||||
eventData.emailConfirmedAt = emailData.confirmedAt
|
||||
}
|
||||
if (eventData.isPrimary === undefined) {
|
||||
eventData.isPrimary = emailData.default
|
||||
}
|
||||
}
|
||||
return eventData
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
registerEmailUpdate,
|
||||
registerEmailCreation,
|
||||
registerEmailDeletion,
|
||||
}
|
||||
268
services/web/test/unit/src/Analytics/EmailChangeHelpersTests.js
Normal file
268
services/web/test/unit/src/Analytics/EmailChangeHelpersTests.js
Normal file
@@ -0,0 +1,268 @@
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
const sinon = require('sinon')
|
||||
const { expect } = require('chai')
|
||||
|
||||
describe('EmailChangeHelper', function () {
|
||||
let AnalyticsManager
|
||||
let UserGetter
|
||||
let EmailChangeHelpers
|
||||
const email = 'test@example.com'
|
||||
const userId = '507f1f77bcf86cd799439011'
|
||||
beforeEach(function () {
|
||||
UserGetter = {
|
||||
promises: {
|
||||
getUserFullEmails: sinon.stub().resolves([]),
|
||||
},
|
||||
}
|
||||
AnalyticsManager = {
|
||||
registerEmailChange: sinon.stub(),
|
||||
}
|
||||
EmailChangeHelpers = SandboxedModule.require(
|
||||
'../../../../app/src/Features/Analytics/EmailChangeHelper',
|
||||
{
|
||||
requires: {
|
||||
'../User/UserGetter': UserGetter,
|
||||
'./AnalyticsManager': AnalyticsManager,
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
describe('registerEmailUpdate', function () {
|
||||
describe('when the email cannot be matched', function () {
|
||||
beforeEach(function () {
|
||||
UserGetter.promises.getUserFullEmails.resolves([
|
||||
{
|
||||
email: 'test2@example.com',
|
||||
reversedHostname: 'moc.elpmaxe',
|
||||
createdAt: new Date('2023-01-01T00:00:00'),
|
||||
confirmedAt: new Date('2023-02-01T00:00:00'),
|
||||
default: false,
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it('calls registerEmailChange with the passed event data', async function () {
|
||||
const eventData = {
|
||||
emailCreatedAt: new Date('2024-01-01T00:00:00'),
|
||||
isPrimary: true,
|
||||
}
|
||||
await EmailChangeHelpers.registerEmailUpdate(userId, email, eventData)
|
||||
expect(AnalyticsManager.registerEmailChange).to.have.been.calledOnce
|
||||
const callArgs = AnalyticsManager.registerEmailChange.getCall(0).args[0]
|
||||
expect(callArgs).to.include({
|
||||
userId,
|
||||
email,
|
||||
action: 'updated',
|
||||
isPrimary: true,
|
||||
})
|
||||
expect(callArgs.emailCreatedAt).to.eql('2024-01-01T00:00:00.000Z')
|
||||
expect(callArgs.emailConfirmedAt).to.be.null
|
||||
})
|
||||
})
|
||||
describe('when the email can be matched', function () {
|
||||
beforeEach(function () {
|
||||
UserGetter.promises.getUserFullEmails.resolves([
|
||||
{
|
||||
email,
|
||||
reversedHostname: 'moc.elpmaxe',
|
||||
createdAt: new Date('2023-01-01T00:00:00'),
|
||||
confirmedAt: new Date('2023-02-01T00:00:00'),
|
||||
default: false,
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it('calls registerEmailChange with the email data', async function () {
|
||||
await EmailChangeHelpers.registerEmailUpdate(userId, email)
|
||||
expect(AnalyticsManager.registerEmailChange).to.have.been.calledOnce
|
||||
const callArgs = AnalyticsManager.registerEmailChange.getCall(0).args[0]
|
||||
expect(callArgs).to.include({
|
||||
userId,
|
||||
email,
|
||||
action: 'updated',
|
||||
isPrimary: false,
|
||||
})
|
||||
expect(callArgs.emailCreatedAt).to.eql('2023-01-01T00:00:00.000Z')
|
||||
expect(callArgs.emailConfirmedAt).to.eql('2023-02-01T00:00:00.000Z')
|
||||
expect(callArgs.emailDeletedAt).to.be.null
|
||||
})
|
||||
|
||||
it('prefers supplied event data over fetched email data', async function () {
|
||||
const eventData = {
|
||||
emailCreatedAt: new Date('2024-01-01T00:00:00'),
|
||||
emailConfirmedAt: new Date('2024-02-01T00:00:00'),
|
||||
isPrimary: true,
|
||||
}
|
||||
await EmailChangeHelpers.registerEmailUpdate(userId, email, eventData)
|
||||
expect(AnalyticsManager.registerEmailChange).to.have.been.calledOnce
|
||||
const callArgs = AnalyticsManager.registerEmailChange.getCall(0).args[0]
|
||||
expect(callArgs).to.include({
|
||||
userId,
|
||||
email,
|
||||
action: 'updated',
|
||||
isPrimary: true,
|
||||
})
|
||||
expect(callArgs.emailCreatedAt).to.eql('2024-01-01T00:00:00.000Z')
|
||||
expect(callArgs.emailConfirmedAt).to.eql('2024-02-01T00:00:00.000Z')
|
||||
expect(callArgs.emailDeletedAt).to.be.null
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the user is not found', function () {
|
||||
beforeEach(function () {
|
||||
UserGetter.promises.getUserFullEmails.rejects(
|
||||
new Error('User not found')
|
||||
)
|
||||
})
|
||||
it('throws the error', async function () {
|
||||
await expect(
|
||||
EmailChangeHelpers.registerEmailUpdate(userId, email)
|
||||
).to.eventually.be.rejectedWith('User not found')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('registerEmailCreation', function () {
|
||||
describe('when the email cannot be matched', function () {
|
||||
beforeEach(function () {
|
||||
UserGetter.promises.getUserFullEmails.resolves([
|
||||
{
|
||||
email: 'test2@example.com',
|
||||
reversedHostname: 'moc.elpmaxe',
|
||||
createdAt: new Date('2023-01-01T00:00:00'),
|
||||
confirmedAt: new Date('2023-02-01T00:00:00'),
|
||||
default: false,
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it('calls registerEmailChange with the passed event data', async function () {
|
||||
const eventData = {
|
||||
emailCreatedAt: new Date('2024-01-01T00:00:00'),
|
||||
isPrimary: true,
|
||||
}
|
||||
await EmailChangeHelpers.registerEmailCreation(userId, email, eventData)
|
||||
expect(AnalyticsManager.registerEmailChange).to.have.been.calledOnce
|
||||
const callArgs = AnalyticsManager.registerEmailChange.getCall(0).args[0]
|
||||
expect(callArgs).to.include({
|
||||
userId,
|
||||
email,
|
||||
action: 'created',
|
||||
isPrimary: true,
|
||||
})
|
||||
expect(callArgs.emailCreatedAt).to.eql('2024-01-01T00:00:00.000Z')
|
||||
expect(callArgs.emailConfirmedAt).to.be.null
|
||||
expect(callArgs.emailDeletedAt).to.be.null
|
||||
})
|
||||
})
|
||||
describe('when the email can be matched', function () {
|
||||
beforeEach(function () {
|
||||
UserGetter.promises.getUserFullEmails.resolves([
|
||||
{
|
||||
email,
|
||||
reversedHostname: 'moc.elpmaxe',
|
||||
createdAt: new Date('2023-01-01T00:00:00'),
|
||||
confirmedAt: new Date('2023-02-01T00:00:00'),
|
||||
default: false,
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it('calls registerEmailChange with the email data', async function () {
|
||||
await EmailChangeHelpers.registerEmailCreation(userId, email)
|
||||
expect(AnalyticsManager.registerEmailChange).to.have.been.calledOnce
|
||||
const callArgs = AnalyticsManager.registerEmailChange.getCall(0).args[0]
|
||||
expect(callArgs).to.include({
|
||||
userId,
|
||||
email,
|
||||
action: 'created',
|
||||
isPrimary: false,
|
||||
})
|
||||
expect(callArgs.emailCreatedAt).to.eql('2023-01-01T00:00:00.000Z')
|
||||
expect(callArgs.emailConfirmedAt).to.eql('2023-02-01T00:00:00.000Z')
|
||||
expect(callArgs.emailDeletedAt).to.be.null
|
||||
})
|
||||
|
||||
it('prefers supplied event data over fetched email data', async function () {
|
||||
const eventData = {
|
||||
emailCreatedAt: new Date('2024-01-01T00:00:00'),
|
||||
emailConfirmedAt: new Date('2024-02-01T00:00:00'),
|
||||
isPrimary: true,
|
||||
}
|
||||
await EmailChangeHelpers.registerEmailCreation(userId, email, eventData)
|
||||
expect(AnalyticsManager.registerEmailChange).to.have.been.calledOnce
|
||||
const callArgs = AnalyticsManager.registerEmailChange.getCall(0).args[0]
|
||||
expect(callArgs).to.include({
|
||||
userId,
|
||||
email,
|
||||
action: 'created',
|
||||
isPrimary: true,
|
||||
})
|
||||
expect(callArgs.emailCreatedAt).to.eql('2024-01-01T00:00:00.000Z')
|
||||
expect(callArgs.emailConfirmedAt).to.eql('2024-02-01T00:00:00.000Z')
|
||||
expect(callArgs.emailDeletedAt).to.be.null
|
||||
})
|
||||
})
|
||||
describe('when the user is not found', function () {
|
||||
beforeEach(function () {
|
||||
UserGetter.promises.getUserFullEmails.rejects(
|
||||
new Error('User not found')
|
||||
)
|
||||
})
|
||||
it('throws the error', async function () {
|
||||
await expect(
|
||||
EmailChangeHelpers.registerEmailCreation(userId, email)
|
||||
).to.eventually.be.rejectedWith('User not found')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('registerEmailDeletion', function () {
|
||||
describe('when the email cannot be matched', function () {
|
||||
beforeEach(function () {
|
||||
UserGetter.promises.getUserFullEmails.resolves([
|
||||
{
|
||||
email: 'test2@example.com',
|
||||
reversedHostname: 'moc.elpmaxe',
|
||||
createdAt: new Date('2023-01-01T00:00:00'),
|
||||
confirmedAt: new Date('2023-02-01T00:00:00'),
|
||||
default: false,
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it('calls registerEmailChange with the passed event data', async function () {
|
||||
const eventData = {
|
||||
emailCreatedAt: new Date('2024-01-01T00:00:00'),
|
||||
emailDeletedAt: new Date('2025-02-01T00:00:00'),
|
||||
isPrimary: true,
|
||||
}
|
||||
await EmailChangeHelpers.registerEmailDeletion(userId, email, eventData)
|
||||
expect(AnalyticsManager.registerEmailChange).to.have.been.calledOnce
|
||||
const callArgs = AnalyticsManager.registerEmailChange.getCall(0).args[0]
|
||||
expect(callArgs).to.include({
|
||||
userId,
|
||||
email,
|
||||
action: 'deleted',
|
||||
isPrimary: true,
|
||||
})
|
||||
expect(callArgs.emailCreatedAt).to.eql('2024-01-01T00:00:00.000Z')
|
||||
expect(callArgs.emailDeletedAt).to.eql('2025-02-01T00:00:00.000Z')
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the user is not found', function () {
|
||||
beforeEach(function () {
|
||||
UserGetter.promises.getUserFullEmails.rejects(
|
||||
new Error('User not found')
|
||||
)
|
||||
})
|
||||
it('throws the error', async function () {
|
||||
await expect(
|
||||
EmailChangeHelpers.registerEmailDeletion(userId, email)
|
||||
).to.eventually.be.rejectedWith('User not found')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user