mirror of
https://github.com/overleaf/overleaf.git
synced 2025-04-26 15:32:06 +00:00
Merge pull request #23457 from overleaf/ar-recurly-account-mapping-initial
[web] setup Recurly -> Mongo account mapping GitOrigin-RevId: ee08cad60ee04e62100f3d5a4f76fdbcf5543917
This commit is contained in:
parent
be247b8cc9
commit
0e64fe2b21
10 changed files with 559 additions and 308 deletions
|
@ -0,0 +1,97 @@
|
|||
const mappings = new Map([
|
||||
['salesforce_id', generateSubscriptionToSalesforceMapping],
|
||||
['v1_id', generateSubscriptionToV1Mapping],
|
||||
['recurlySubscription_id', generateSubscriptionToRecurlyMapping],
|
||||
])
|
||||
|
||||
/**
|
||||
* @typedef {(import('./types.d.ts').AccountMapping)} AccountMapping
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object} subscription
|
||||
* @param {Object} updatedSubscription
|
||||
* @return {Array<AccountMapping>}
|
||||
*/
|
||||
function extractAccountMappingsFromSubscription(
|
||||
subscription,
|
||||
updatedSubscription
|
||||
) {
|
||||
const accountMappings = []
|
||||
mappings.forEach((generateMapping, param) => {
|
||||
if (updatedSubscription[param] || updatedSubscription[param] === '') {
|
||||
if (subscription[param] !== updatedSubscription[param]) {
|
||||
accountMappings.push(
|
||||
generateMapping(subscription.id, updatedSubscription[param])
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
return accountMappings
|
||||
}
|
||||
|
||||
function generateV1Mapping(v1Id, salesforceId, createdAt) {
|
||||
return {
|
||||
source: 'salesforce',
|
||||
sourceEntity: 'account',
|
||||
sourceEntityId: salesforceId,
|
||||
target: 'v1',
|
||||
targetEntity: 'university',
|
||||
targetEntityId: v1Id,
|
||||
createdAt,
|
||||
}
|
||||
}
|
||||
|
||||
function generateSubscriptionToV1Mapping(subscriptionId, v1Id) {
|
||||
return {
|
||||
source: 'v1',
|
||||
sourceEntity: 'university',
|
||||
sourceEntityId: v1Id,
|
||||
target: 'v2',
|
||||
targetEntity: 'subscription',
|
||||
targetEntityId: subscriptionId,
|
||||
createdAt: new Date().toISOString(),
|
||||
}
|
||||
}
|
||||
|
||||
function generateSubscriptionToSalesforceMapping(subscriptionId, salesforceId) {
|
||||
return {
|
||||
source: 'salesforce',
|
||||
sourceEntity: 'account',
|
||||
sourceEntityId: salesforceId,
|
||||
target: 'v2',
|
||||
targetEntity: 'subscription',
|
||||
targetEntityId: subscriptionId,
|
||||
createdAt: new Date().toISOString(),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} subscriptionId
|
||||
* @param {string} recurlyId
|
||||
* @param {string} [createdAt] - Should be an ISO date
|
||||
* @return {AccountMapping}
|
||||
*/
|
||||
function generateSubscriptionToRecurlyMapping(
|
||||
subscriptionId,
|
||||
recurlyId,
|
||||
createdAt = new Date().toISOString()
|
||||
) {
|
||||
return {
|
||||
source: 'recurly',
|
||||
sourceEntity: 'subscription',
|
||||
sourceEntityId: recurlyId,
|
||||
target: 'v2',
|
||||
targetEntity: 'subscription',
|
||||
targetEntityId: subscriptionId,
|
||||
createdAt,
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
extractAccountMappingsFromSubscription,
|
||||
generateV1Mapping,
|
||||
generateSubscriptionToRecurlyMapping,
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
export function extractAccountMappingsFromSubscription(
|
||||
subscription,
|
||||
updatedSubscription
|
||||
) {
|
||||
const accountMappings = []
|
||||
if (
|
||||
updatedSubscription.salesforce_id ||
|
||||
updatedSubscription.salesforce_id === ''
|
||||
) {
|
||||
if (subscription.salesforce_id !== updatedSubscription.salesforce_id) {
|
||||
accountMappings.push(
|
||||
generateSubscriptionToSalesforceMapping(
|
||||
subscription.id,
|
||||
updatedSubscription.salesforce_id
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
if (updatedSubscription.v1_id || updatedSubscription.v1_id === '') {
|
||||
if (subscription.v1_id !== updatedSubscription.v1_id) {
|
||||
accountMappings.push(
|
||||
generateSubscriptionToV1Mapping(
|
||||
subscription.id,
|
||||
updatedSubscription.v1_id
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
return accountMappings
|
||||
}
|
||||
|
||||
export function generateV1Mapping(v1Id, salesforceId, createdAt) {
|
||||
return {
|
||||
source: 'salesforce',
|
||||
sourceEntity: 'account',
|
||||
sourceEntityId: salesforceId,
|
||||
target: 'v1',
|
||||
targetEntity: 'university',
|
||||
targetEntityId: v1Id,
|
||||
createdAt,
|
||||
}
|
||||
}
|
||||
|
||||
function generateSubscriptionToV1Mapping(subscriptionId, v1Id) {
|
||||
return {
|
||||
source: 'v1',
|
||||
sourceEntity: 'university',
|
||||
sourceEntityId: v1Id,
|
||||
target: 'v2',
|
||||
targetEntity: 'subscription',
|
||||
targetEntityId: subscriptionId,
|
||||
createdAt: new Date().toISOString(),
|
||||
}
|
||||
}
|
||||
|
||||
function generateSubscriptionToSalesforceMapping(subscriptionId, salesforceId) {
|
||||
return {
|
||||
source: 'salesforce',
|
||||
sourceEntity: 'account',
|
||||
sourceEntityId: salesforceId,
|
||||
target: 'v2',
|
||||
targetEntity: 'subscription',
|
||||
targetEntityId: subscriptionId,
|
||||
createdAt: new Date().toISOString(),
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
extractAccountMappingsFromSubscription,
|
||||
generateV1Mapping,
|
||||
}
|
|
@ -4,7 +4,7 @@ import SessionManager from '../Authentication/SessionManager.js'
|
|||
import GeoIpLookup from '../../infrastructure/GeoIpLookup.js'
|
||||
import Features from '../../infrastructure/Features.js'
|
||||
import { expressify } from '@overleaf/promise-utils'
|
||||
import { generateV1Mapping } from './AccountMappingHelper.mjs'
|
||||
import AccountMappingHelper from './AccountMappingHelper.js'
|
||||
|
||||
async function registerSalesforceMapping(req, res, next) {
|
||||
if (!Features.hasFeature('analytics')) {
|
||||
|
@ -12,7 +12,7 @@ async function registerSalesforceMapping(req, res, next) {
|
|||
}
|
||||
const { createdAt, salesforceId, v1Id } = req.body
|
||||
AnalyticsManager.registerAccountMapping(
|
||||
generateV1Mapping(v1Id, salesforceId, createdAt)
|
||||
AccountMappingHelper.generateV1Mapping(v1Id, salesforceId, createdAt)
|
||||
)
|
||||
res.sendStatus(202)
|
||||
}
|
||||
|
|
|
@ -146,16 +146,14 @@ function setUserPropertyForSessionInBackground(session, property, value) {
|
|||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {(import('./types').AccountMapping)} AccountMapping
|
||||
*/
|
||||
|
||||
/**
|
||||
* Register mapping between two accounts.
|
||||
*
|
||||
* @param {object} payload - The event payload to send to Analytics
|
||||
* @param {string} payload.source - The type of account linked from
|
||||
* @param {string} payload.sourceId - The ID of the account linked from
|
||||
* @param {string} payload.target - The type of account linked to
|
||||
* @param {string} payload.targetId - The ID of the account linked to
|
||||
* @param {Date} payload.createdAt - The date the mapping was created
|
||||
* @property
|
||||
* @param {AccountMapping} payload - The event payload to send to Analytics
|
||||
*/
|
||||
function registerAccountMapping({
|
||||
source,
|
||||
|
|
9
services/web/app/src/Features/Analytics/types.d.ts
vendored
Normal file
9
services/web/app/src/Features/Analytics/types.d.ts
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
export type AccountMapping = {
|
||||
source: string
|
||||
sourceEntity: string
|
||||
sourceEntityId: string
|
||||
target: string
|
||||
targetEntity: string
|
||||
targetEntityId: string
|
||||
createdAt: string
|
||||
}
|
|
@ -10,6 +10,7 @@ const { DeletedSubscription } = require('../../models/DeletedSubscription')
|
|||
const logger = require('@overleaf/logger')
|
||||
const Features = require('../../infrastructure/Features')
|
||||
const UserAuditLogHandler = require('../User/UserAuditLogHandler')
|
||||
const AccountMappingHelper = require('../Analytics/AccountMappingHelper')
|
||||
const { SSOConfig } = require('../../models/SSOConfig')
|
||||
|
||||
/**
|
||||
|
@ -345,6 +346,16 @@ async function updateSubscriptionFromRecurly(
|
|||
}
|
||||
}
|
||||
await subscription.save()
|
||||
|
||||
const accountMapping =
|
||||
AccountMappingHelper.generateSubscriptionToRecurlyMapping(
|
||||
subscription._id,
|
||||
subscription.recurlySubscription_id
|
||||
)
|
||||
if (accountMapping) {
|
||||
AnalyticsManager.registerAccountMapping(accountMapping)
|
||||
}
|
||||
|
||||
await _scheduleRefreshFeatures(subscription)
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
/**
|
||||
* This script backfills the account mapping for subscriptions that are active and have a group plan.
|
||||
*
|
||||
* The mapping joins a recurlySubscription_id to a subscription _id in BigQuery.
|
||||
*
|
||||
* This script has an assumption that it is being run in a clean slate condition, it will create some
|
||||
* duplicate mappings if run multiple times. The Analytics team will have the expectation
|
||||
* that this table may need to be deduplicated as it is an event sourcing record.
|
||||
*
|
||||
* Call it with `--commit` to actually register the mappings.
|
||||
* Call it with `--verbose` to see debug logs.
|
||||
* Call it with `--endDate=<subscription ID>` to stop processing at a certain date
|
||||
*/
|
||||
import logger from '@overleaf/logger'
|
||||
import minimist from 'minimist'
|
||||
import { batchedUpdate } from '@overleaf/mongo-utils/batchedUpdate.js'
|
||||
import { db } from '../../app/src/infrastructure/mongodb.js'
|
||||
import AccountMappingHelper from '../../app/src/Features/Analytics/AccountMappingHelper.js'
|
||||
import { registerAccountMapping } from '../../app/src/Features/Analytics/AnalyticsManager.js'
|
||||
import { triggerGracefulShutdown } from '../../app/src/infrastructure/GracefulShutdown.js'
|
||||
import Validation from '../../app/src/infrastructure/Validation.js'
|
||||
|
||||
const paramsSchema = Validation.Joi.object({
|
||||
endDate: Validation.Joi.string().isoDate(),
|
||||
commit: Validation.Joi.boolean().default(false),
|
||||
verbose: Validation.Joi.boolean().default(false),
|
||||
}).unknown(true)
|
||||
|
||||
let mapped = 0
|
||||
let subscriptionCount = 0
|
||||
|
||||
const now = new Date().toISOString() // use the same timestamp for all mappings
|
||||
|
||||
const seenSubscriptions = new Set()
|
||||
|
||||
function registerMapping(subscription) {
|
||||
if (seenSubscriptions.has(subscription._id)) {
|
||||
logger.warn({ subscription }, 'duplicate subscription found, skipping')
|
||||
return
|
||||
}
|
||||
seenSubscriptions.add(subscription._id)
|
||||
subscriptionCount++
|
||||
|
||||
const mapping = AccountMappingHelper.generateSubscriptionToRecurlyMapping(
|
||||
subscription._id,
|
||||
subscription.recurlySubscription_id,
|
||||
now
|
||||
)
|
||||
logger.debug(
|
||||
{
|
||||
recurly: subscription.recurlySubscription_id,
|
||||
mapping,
|
||||
},
|
||||
`processing subscription ${subscription._id}`
|
||||
)
|
||||
if (commit) {
|
||||
registerAccountMapping(mapping)
|
||||
mapped++
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const additionalBatchedUpdateOptions = {}
|
||||
|
||||
if (endDate) {
|
||||
additionalBatchedUpdateOptions.BATCH_RANGE_END = endDate
|
||||
}
|
||||
|
||||
await batchedUpdate(
|
||||
db.subscriptions,
|
||||
{
|
||||
'recurlyStatus.state': 'active',
|
||||
groupPlan: true,
|
||||
},
|
||||
subscriptions => subscriptions.forEach(registerMapping),
|
||||
{
|
||||
_id: 1,
|
||||
recurlySubscription_id: 1,
|
||||
},
|
||||
{
|
||||
readPreference: 'secondaryPreferred',
|
||||
},
|
||||
{
|
||||
verboseLogging: verbose,
|
||||
...additionalBatchedUpdateOptions,
|
||||
}
|
||||
)
|
||||
|
||||
logger.debug({}, `${subscriptionCount} subscriptions processed`)
|
||||
if (commit) {
|
||||
logger.debug({}, `${mapped} mappings registered`)
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
error,
|
||||
value: { commit, endDate, verbose },
|
||||
} = paramsSchema.validate(
|
||||
minimist(process.argv.slice(2), {
|
||||
boolean: ['commit', 'verbose'],
|
||||
string: ['endDate'],
|
||||
})
|
||||
)
|
||||
|
||||
logger.logger.level(verbose ? 'debug' : 'info')
|
||||
|
||||
if (error) {
|
||||
logger.error({ error }, 'error with parameters')
|
||||
triggerGracefulShutdown(done => done(1))
|
||||
} else {
|
||||
logger.info({ verbose, commit, endDate }, commit ? 'COMMITTING' : 'DRY RUN')
|
||||
await main()
|
||||
|
||||
triggerGracefulShutdown({
|
||||
close(done) {
|
||||
logger.info({}, 'shutting down')
|
||||
done()
|
||||
},
|
||||
})
|
||||
}
|
|
@ -0,0 +1,276 @@
|
|||
const SandboxedModule = require('sandboxed-module')
|
||||
const { expect } = require('chai')
|
||||
const { ObjectId } = require('mongodb-legacy')
|
||||
const path = require('node:path')
|
||||
|
||||
const MODULE_PATH = path.join(
|
||||
__dirname,
|
||||
'../../../../app/src/Features/Analytics/AccountMappingHelper'
|
||||
)
|
||||
|
||||
describe('AccountMappingHelper', function () {
|
||||
beforeEach(function () {
|
||||
this.AccountMappingHelper = SandboxedModule.require(MODULE_PATH)
|
||||
})
|
||||
|
||||
describe('extractAccountMappingsFromSubscription', function () {
|
||||
describe('when the v1 id is the same in the updated subscription and the subscription', function () {
|
||||
describe('when the salesforce id is the same in the updated subscription and the subscription', function () {
|
||||
beforeEach(function () {
|
||||
this.subscription = {
|
||||
id: new ObjectId('abc123abc123abc123abc123'),
|
||||
salesforce_id: 'def456def456def456',
|
||||
}
|
||||
this.updatedSubscription = { salesforce_id: 'def456def456def456' }
|
||||
this.result =
|
||||
this.AccountMappingHelper.extractAccountMappingsFromSubscription(
|
||||
this.subscription,
|
||||
this.updatedSubscription
|
||||
)
|
||||
})
|
||||
|
||||
it('returns an empty array', function () {
|
||||
expect(this.result).to.be.an('array')
|
||||
expect(this.result).to.have.length(0)
|
||||
})
|
||||
})
|
||||
describe('when the salesforce id has changed between the subscription and the updated subscription', function () {
|
||||
beforeEach(function () {
|
||||
this.subscription = {
|
||||
id: new ObjectId('abc123abc123abc123abc123'),
|
||||
salesforce_id: 'def456def456def456',
|
||||
}
|
||||
this.updatedSubscription = { salesforce_id: 'ghi789ghi789ghi789' }
|
||||
this.result =
|
||||
this.AccountMappingHelper.extractAccountMappingsFromSubscription(
|
||||
this.subscription,
|
||||
this.updatedSubscription
|
||||
)
|
||||
})
|
||||
|
||||
it('returns an array with a single item', function () {
|
||||
expect(this.result).to.be.an('array')
|
||||
expect(this.result).to.have.length(1)
|
||||
})
|
||||
|
||||
it('uses "account" as sourceEntity', function () {
|
||||
expect(this.result[0]).to.haveOwnProperty('sourceEntity', 'account')
|
||||
})
|
||||
|
||||
it('uses the salesforceId from the updated subscription as sourceEntityId', function () {
|
||||
expect(this.result[0]).to.haveOwnProperty(
|
||||
'sourceEntityId',
|
||||
this.updatedSubscription.salesforce_id
|
||||
)
|
||||
})
|
||||
|
||||
it('uses "subscription" as targetEntity', function () {
|
||||
expect(this.result[0]).to.haveOwnProperty(
|
||||
'targetEntity',
|
||||
'subscription'
|
||||
)
|
||||
})
|
||||
|
||||
it('uses the subscriptionId as targetEntityId', function () {
|
||||
expect(this.result[0]).to.haveOwnProperty(
|
||||
'targetEntityId',
|
||||
this.subscription.id
|
||||
)
|
||||
})
|
||||
})
|
||||
describe('when the update subscription has a salesforce id and the subscription has no salesforce_id', function () {
|
||||
beforeEach(function () {
|
||||
this.subscription = { id: new ObjectId('abc123abc123abc123abc123') }
|
||||
this.updatedSubscription = { salesforce_id: 'def456def456def456' }
|
||||
this.result =
|
||||
this.AccountMappingHelper.extractAccountMappingsFromSubscription(
|
||||
this.subscription,
|
||||
this.updatedSubscription
|
||||
)
|
||||
})
|
||||
|
||||
it('returns an array with a single item', function () {
|
||||
expect(this.result).to.be.an('array')
|
||||
expect(this.result).to.have.length(1)
|
||||
})
|
||||
|
||||
it('uses "account" as sourceEntity', function () {
|
||||
expect(this.result[0]).to.haveOwnProperty('sourceEntity', 'account')
|
||||
})
|
||||
|
||||
it('uses the salesforceId from the updated subscription as sourceEntityId', function () {
|
||||
expect(this.result[0]).to.haveOwnProperty(
|
||||
'sourceEntityId',
|
||||
this.updatedSubscription.salesforce_id
|
||||
)
|
||||
})
|
||||
|
||||
it('uses "subscription" as targetEntity', function () {
|
||||
expect(this.result[0]).to.haveOwnProperty(
|
||||
'targetEntity',
|
||||
'subscription'
|
||||
)
|
||||
})
|
||||
|
||||
it('uses the subscriptionId as targetEntityId', function () {
|
||||
expect(this.result[0]).to.haveOwnProperty(
|
||||
'targetEntityId',
|
||||
this.subscription.id
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the v1 id has changed between the subscription and the updated subscription', function () {
|
||||
describe('when the salesforce id has not changed between the subscription and the updated subscription', function () {
|
||||
beforeEach(function () {
|
||||
this.subscription = {
|
||||
id: new ObjectId('abc123abc123abc123abc123'),
|
||||
v1_id: '1',
|
||||
salesforce_id: '',
|
||||
}
|
||||
this.updatedSubscription = { v1_id: '2', salesforce_id: '' }
|
||||
this.result =
|
||||
this.AccountMappingHelper.extractAccountMappingsFromSubscription(
|
||||
this.subscription,
|
||||
this.updatedSubscription
|
||||
)
|
||||
})
|
||||
|
||||
it('returns an array with a single item', function () {
|
||||
expect(this.result).to.be.an('array')
|
||||
expect(this.result).to.have.length(1)
|
||||
})
|
||||
|
||||
it('uses "university" as the sourceEntity', function () {
|
||||
expect(this.result[0]).to.haveOwnProperty(
|
||||
'sourceEntity',
|
||||
'university'
|
||||
)
|
||||
})
|
||||
|
||||
it('uses the v1_id from the updated subscription as the sourceEntityId', function () {
|
||||
expect(this.result[0]).to.haveOwnProperty(
|
||||
'sourceEntityId',
|
||||
this.updatedSubscription.v1_id
|
||||
)
|
||||
})
|
||||
|
||||
it('uses "subscription" as the targetEntity', function () {
|
||||
expect(this.result[0]).to.haveOwnProperty(
|
||||
'targetEntity',
|
||||
'subscription'
|
||||
)
|
||||
})
|
||||
|
||||
it('uses the subscription id as the targetEntityId', function () {
|
||||
expect(this.result[0]).to.haveOwnProperty(
|
||||
'targetEntityId',
|
||||
this.subscription.id
|
||||
)
|
||||
})
|
||||
})
|
||||
describe('when the salesforce id has changed between the subscription and the updated subscription', function () {
|
||||
beforeEach(function () {
|
||||
this.subscription = {
|
||||
id: new ObjectId('abc123abc123abc123abc123'),
|
||||
v1_id: '',
|
||||
salesforce_id: 'def456def456def456',
|
||||
}
|
||||
this.updatedSubscription = {
|
||||
v1_id: '2',
|
||||
salesforce_id: '',
|
||||
}
|
||||
this.result =
|
||||
this.AccountMappingHelper.extractAccountMappingsFromSubscription(
|
||||
this.subscription,
|
||||
this.updatedSubscription
|
||||
)
|
||||
})
|
||||
|
||||
it('returns an array with two items', function () {
|
||||
expect(this.result).to.be.an('array')
|
||||
expect(this.result).to.have.length(2)
|
||||
})
|
||||
|
||||
it('uses the salesforce_id from the updated subscription as the sourceEntityId for the first item', function () {
|
||||
expect(this.result[0]).to.haveOwnProperty(
|
||||
'sourceEntityId',
|
||||
this.updatedSubscription.salesforce_id
|
||||
)
|
||||
})
|
||||
|
||||
it('uses the subscription id as the targetEntityId for the first item', function () {
|
||||
expect(this.result[0]).to.haveOwnProperty(
|
||||
'targetEntityId',
|
||||
this.subscription.id
|
||||
)
|
||||
})
|
||||
|
||||
it('uses the v1_id from the updated subscription as the sourceEntityId for the second item', function () {
|
||||
expect(this.result[1]).to.haveOwnProperty(
|
||||
'sourceEntityId',
|
||||
this.updatedSubscription.v1_id
|
||||
)
|
||||
})
|
||||
|
||||
it('uses the subscription id as the targetEntityId for the second item', function () {
|
||||
expect(this.result[1]).to.haveOwnProperty(
|
||||
'targetEntityId',
|
||||
this.subscription.id
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
describe('when the recurlySubscription_id has changed between the subscription and the updated subscription', function () {
|
||||
beforeEach(function () {
|
||||
this.subscription = {
|
||||
id: new ObjectId('abc123abc123abc123abc123'),
|
||||
recurlySubscription_id: '',
|
||||
}
|
||||
this.updatedSubscription = {
|
||||
recurlySubscription_id: '1234a5678b90123cd4567e8f901a2b34',
|
||||
}
|
||||
this.result =
|
||||
this.AccountMappingHelper.extractAccountMappingsFromSubscription(
|
||||
this.subscription,
|
||||
this.updatedSubscription
|
||||
)
|
||||
})
|
||||
it('returns an array with one item', function () {
|
||||
expect(this.result).to.be.an('array')
|
||||
expect(this.result).to.have.length(1)
|
||||
})
|
||||
|
||||
it('uses "recurly" as the source', function () {
|
||||
expect(this.result[0]).to.haveOwnProperty('source', 'recurly')
|
||||
})
|
||||
|
||||
it('uses "subscription" as the sourceEntity', function () {
|
||||
expect(this.result[0]).to.haveOwnProperty('sourceEntity', 'subscription')
|
||||
})
|
||||
|
||||
it('uses the recurlySubscription_id as the sourceEntityId', function () {
|
||||
expect(this.result[0]).to.haveOwnProperty(
|
||||
'sourceEntityId',
|
||||
this.updatedSubscription.recurlySubscription_id
|
||||
)
|
||||
})
|
||||
|
||||
it('uses "v2" as the target', function () {
|
||||
expect(this.result[0]).to.haveOwnProperty('target', 'v2')
|
||||
})
|
||||
|
||||
it('uses "subscription" as the targetEntity', function () {
|
||||
expect(this.result[0]).to.haveOwnProperty('targetEntity', 'subscription')
|
||||
})
|
||||
|
||||
it('uses the subscription id as the targetEntityId', function () {
|
||||
expect(this.result[0]).to.haveOwnProperty(
|
||||
'targetEntityId',
|
||||
this.subscription.id
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,228 +0,0 @@
|
|||
import path from 'node:path'
|
||||
import esmock from 'esmock'
|
||||
import { expect } from 'chai'
|
||||
import mongodb from 'mongodb-legacy'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const { ObjectId } = mongodb
|
||||
|
||||
const __dirname = fileURLToPath(new URL('.', import.meta.url))
|
||||
|
||||
const MODULE_PATH = path.join(
|
||||
__dirname,
|
||||
'../../../../app/src/Features/Analytics/AccountMappingHelper'
|
||||
)
|
||||
|
||||
describe('AccountMappingHelper', function () {
|
||||
beforeEach(async function () {
|
||||
this.AccountMappingHelper = await esmock.strict(MODULE_PATH)
|
||||
})
|
||||
|
||||
describe('extractAccountMappingsFromSubscription', function () {
|
||||
describe('when the v1 id is the same in the updated subscription and the subscription', function () {
|
||||
describe('when the salesforce id is the same in the updated subscription and the subscription', function () {
|
||||
beforeEach(function () {
|
||||
this.subscription = {
|
||||
id: new ObjectId('abc123abc123abc123abc123'),
|
||||
salesforce_id: 'def456def456def456',
|
||||
}
|
||||
this.updatedSubscription = { salesforce_id: 'def456def456def456' }
|
||||
this.result =
|
||||
this.AccountMappingHelper.extractAccountMappingsFromSubscription(
|
||||
this.subscription,
|
||||
this.updatedSubscription
|
||||
)
|
||||
})
|
||||
|
||||
it('returns an empty array', function () {
|
||||
expect(this.result).to.be.an('array')
|
||||
expect(this.result).to.have.length(0)
|
||||
})
|
||||
})
|
||||
describe('when the salesforce id has changed between the subscription and the updated subscription', function () {
|
||||
beforeEach(function () {
|
||||
this.subscription = {
|
||||
id: new ObjectId('abc123abc123abc123abc123'),
|
||||
salesforce_id: 'def456def456def456',
|
||||
}
|
||||
this.updatedSubscription = { salesforce_id: 'ghi789ghi789ghi789' }
|
||||
this.result =
|
||||
this.AccountMappingHelper.extractAccountMappingsFromSubscription(
|
||||
this.subscription,
|
||||
this.updatedSubscription
|
||||
)
|
||||
})
|
||||
|
||||
it('returns an array with a single item', function () {
|
||||
expect(this.result).to.be.an('array')
|
||||
expect(this.result).to.have.length(1)
|
||||
})
|
||||
|
||||
it('uses "account" as sourceEntity', function () {
|
||||
expect(this.result[0]).to.haveOwnProperty('sourceEntity', 'account')
|
||||
})
|
||||
|
||||
it('uses the salesforceId from the updated subscription as sourceEntityId', function () {
|
||||
expect(this.result[0]).to.haveOwnProperty(
|
||||
'sourceEntityId',
|
||||
this.updatedSubscription.salesforce_id
|
||||
)
|
||||
})
|
||||
|
||||
it('uses "subscription" as targetEntity', function () {
|
||||
expect(this.result[0]).to.haveOwnProperty(
|
||||
'targetEntity',
|
||||
'subscription'
|
||||
)
|
||||
})
|
||||
|
||||
it('uses the subscriptionId as targetEntityId', function () {
|
||||
expect(this.result[0]).to.haveOwnProperty(
|
||||
'targetEntityId',
|
||||
this.subscription.id
|
||||
)
|
||||
})
|
||||
})
|
||||
describe('when the update subscription has a salesforce id and the subscription has no salesforce_id', function () {
|
||||
beforeEach(function () {
|
||||
this.subscription = { id: new ObjectId('abc123abc123abc123abc123') }
|
||||
this.updatedSubscription = { salesforce_id: 'def456def456def456' }
|
||||
this.result =
|
||||
this.AccountMappingHelper.extractAccountMappingsFromSubscription(
|
||||
this.subscription,
|
||||
this.updatedSubscription
|
||||
)
|
||||
})
|
||||
|
||||
it('returns an array with a single item', function () {
|
||||
expect(this.result).to.be.an('array')
|
||||
expect(this.result).to.have.length(1)
|
||||
})
|
||||
|
||||
it('uses "account" as sourceEntity', function () {
|
||||
expect(this.result[0]).to.haveOwnProperty('sourceEntity', 'account')
|
||||
})
|
||||
|
||||
it('uses the salesforceId from the updated subscription as sourceEntityId', function () {
|
||||
expect(this.result[0]).to.haveOwnProperty(
|
||||
'sourceEntityId',
|
||||
this.updatedSubscription.salesforce_id
|
||||
)
|
||||
})
|
||||
|
||||
it('uses "subscription" as targetEntity', function () {
|
||||
expect(this.result[0]).to.haveOwnProperty(
|
||||
'targetEntity',
|
||||
'subscription'
|
||||
)
|
||||
})
|
||||
|
||||
it('uses the subscriptionId as targetEntityId', function () {
|
||||
expect(this.result[0]).to.haveOwnProperty(
|
||||
'targetEntityId',
|
||||
this.subscription.id
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the v1 id has changed between the subscription and the updated subscription', function () {
|
||||
describe('when the salesforce id has not changed between the subscription and the updated subscription', function () {
|
||||
beforeEach(function () {
|
||||
this.subscription = {
|
||||
id: new ObjectId('abc123abc123abc123abc123'),
|
||||
v1_id: '1',
|
||||
salesforce_id: '',
|
||||
}
|
||||
this.updatedSubscription = { v1_id: '2', salesforce_id: '' }
|
||||
this.result =
|
||||
this.AccountMappingHelper.extractAccountMappingsFromSubscription(
|
||||
this.subscription,
|
||||
this.updatedSubscription
|
||||
)
|
||||
})
|
||||
|
||||
it('returns an array with a single item', function () {
|
||||
expect(this.result).to.be.an('array')
|
||||
expect(this.result).to.have.length(1)
|
||||
})
|
||||
|
||||
it('uses "university" as the sourceEntity', function () {
|
||||
expect(this.result[0]).to.haveOwnProperty('sourceEntity', 'university')
|
||||
})
|
||||
|
||||
it('uses the v1_id from the updated subscription as the sourceEntityId', function () {
|
||||
expect(this.result[0]).to.haveOwnProperty(
|
||||
'sourceEntityId',
|
||||
this.updatedSubscription.v1_id
|
||||
)
|
||||
})
|
||||
|
||||
it('uses "subscription" as the targetEntity', function () {
|
||||
expect(this.result[0]).to.haveOwnProperty(
|
||||
'targetEntity',
|
||||
'subscription'
|
||||
)
|
||||
})
|
||||
|
||||
it('uses the subscription id as the targetEntityId', function () {
|
||||
expect(this.result[0]).to.haveOwnProperty(
|
||||
'targetEntityId',
|
||||
this.subscription.id
|
||||
)
|
||||
})
|
||||
})
|
||||
describe('when the salesforce id has changed between the subscription and the updated subscription', function () {
|
||||
beforeEach(function () {
|
||||
this.subscription = {
|
||||
id: new ObjectId('abc123abc123abc123abc123'),
|
||||
v1_id: '',
|
||||
salesforce_id: 'def456def456def456',
|
||||
}
|
||||
this.updatedSubscription = {
|
||||
v1_id: '2',
|
||||
salesforce_id: '',
|
||||
}
|
||||
this.result =
|
||||
this.AccountMappingHelper.extractAccountMappingsFromSubscription(
|
||||
this.subscription,
|
||||
this.updatedSubscription
|
||||
)
|
||||
})
|
||||
|
||||
it('returns an array with two items', function () {
|
||||
expect(this.result).to.be.an('array')
|
||||
expect(this.result).to.have.length(2)
|
||||
})
|
||||
|
||||
it('uses the salesforce_id from the updated subscription as the sourceEntityId for the first item', function () {
|
||||
expect(this.result[0]).to.haveOwnProperty(
|
||||
'sourceEntityId',
|
||||
this.updatedSubscription.salesforce_id
|
||||
)
|
||||
})
|
||||
|
||||
it('uses the subscription id as the targetEntityId for the first item', function () {
|
||||
expect(this.result[0]).to.haveOwnProperty(
|
||||
'targetEntityId',
|
||||
this.subscription.id
|
||||
)
|
||||
})
|
||||
|
||||
it('uses the v1_id from the updated subscription as the sourceEntityId for the second item', function () {
|
||||
expect(this.result[1]).to.haveOwnProperty(
|
||||
'sourceEntityId',
|
||||
this.updatedSubscription.v1_id
|
||||
)
|
||||
})
|
||||
|
||||
it('uses the subscription id as the targetEntityId for the second item', function () {
|
||||
expect(this.result[1]).to.haveOwnProperty(
|
||||
'targetEntityId',
|
||||
this.subscription.id
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -148,6 +148,7 @@ describe('SubscriptionUpdater', function () {
|
|||
this.AnalyticsManager = {
|
||||
recordEventForUserInBackground: sinon.stub().resolves(),
|
||||
setUserPropertyForUserInBackground: sinon.stub(),
|
||||
registerAccountMapping: sinon.stub(),
|
||||
}
|
||||
|
||||
this.Features = {
|
||||
|
@ -175,6 +176,9 @@ describe('SubscriptionUpdater', function () {
|
|||
DeletedSubscription: this.DeletedSubscription,
|
||||
},
|
||||
'../Analytics/AnalyticsManager': this.AnalyticsManager,
|
||||
'../Analytics/AccountMappingHelper': (this.AccountMappingHelper = {
|
||||
generateSubscriptionToRecurlyMapping: sinon.stub(),
|
||||
}),
|
||||
'../../infrastructure/Features': this.Features,
|
||||
'../User/UserAuditLogHandler': this.UserAuditLogHandler,
|
||||
},
|
||||
|
@ -280,6 +284,41 @@ describe('SubscriptionUpdater', function () {
|
|||
).to.have.been.calledWith(this.adminUser._id)
|
||||
})
|
||||
|
||||
it('should send a recurly account mapping event', async function () {
|
||||
const createdAt = new Date().toISOString()
|
||||
this.AccountMappingHelper.generateSubscriptionToRecurlyMapping.returns({
|
||||
source: 'recurly',
|
||||
sourceEntity: 'subscription',
|
||||
sourceEntityId: this.recurlySubscription.uuid,
|
||||
target: 'v2',
|
||||
targetEntity: 'subscription',
|
||||
targetEntityId: this.subscription._id,
|
||||
createdAt,
|
||||
})
|
||||
await this.SubscriptionUpdater.promises.updateSubscriptionFromRecurly(
|
||||
this.recurlySubscription,
|
||||
this.subscription,
|
||||
{}
|
||||
)
|
||||
expect(
|
||||
this.AccountMappingHelper.generateSubscriptionToRecurlyMapping
|
||||
).to.have.been.calledWith(
|
||||
this.subscription._id,
|
||||
this.recurlySubscription.uuid
|
||||
)
|
||||
expect(
|
||||
this.AnalyticsManager.registerAccountMapping
|
||||
).to.have.been.calledWith({
|
||||
source: 'recurly',
|
||||
sourceEntity: 'subscription',
|
||||
sourceEntityId: this.recurlySubscription.uuid,
|
||||
target: 'v2',
|
||||
targetEntity: 'subscription',
|
||||
targetEntityId: this.subscription._id,
|
||||
createdAt,
|
||||
})
|
||||
})
|
||||
|
||||
it('should remove the subscription when expired', async function () {
|
||||
this.recurlySubscription.state = 'expired'
|
||||
await this.SubscriptionUpdater.promises.updateSubscriptionFromRecurly(
|
||||
|
|
Loading…
Add table
Reference in a new issue