feat(logging): send amplitude events to the logs
https://github.com/mozilla/fxa-auth-server/pull/2069 r=rfk,vbudhram
This commit is contained in:
Родитель
f136268dd9
Коммит
5800418902
|
@ -320,6 +320,7 @@ those common validations are defined here.
|
|||
#### lib/metrics/context
|
||||
|
||||
* `SCHEMA`: object({
|
||||
* `deviceId`: string, length(32), regex(HEX_STRING), optional
|
||||
* `flowId`: string, length(64), regex(HEX_STRING), optional
|
||||
* `flowBeginTime`: number, integer, positive, optional
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const emailDomains = require('../../../config/popular-email-domains')
|
||||
let amplitude
|
||||
|
||||
function getInsensitiveHeaderValueFromArray(headerName, headers) {
|
||||
var value = ''
|
||||
|
@ -61,6 +62,31 @@ function logEmailEventSent(log, message) {
|
|||
msg.domain = getAnonymizedEmailDomain(addr)
|
||||
log.info(msg)
|
||||
})
|
||||
|
||||
logAmplitudeEvent(log, message, emailEventInfo)
|
||||
}
|
||||
|
||||
function logAmplitudeEvent (log, message, eventInfo) {
|
||||
if (! amplitude) {
|
||||
amplitude = require('../../metrics/amplitude')(log)
|
||||
}
|
||||
|
||||
amplitude(`email.${eventInfo.template}.${eventInfo.type}`, {
|
||||
app: {
|
||||
locale: eventInfo.locale,
|
||||
ua: {}
|
||||
},
|
||||
auth: {},
|
||||
query: {},
|
||||
payload: {}
|
||||
}, {
|
||||
device_id: getHeaderValue('X-Device-Id', message),
|
||||
service: getHeaderValue('X-Service-Id', message),
|
||||
uid: getHeaderValue('X-Uid', message)
|
||||
}, {
|
||||
flow_id: eventInfo.flow_id,
|
||||
time: Date.now()
|
||||
})
|
||||
}
|
||||
|
||||
function logEmailEventFromMessage(log, message, type, emailDomain) {
|
||||
|
@ -89,6 +115,8 @@ function logEmailEventFromMessage(log, message, type, emailDomain) {
|
|||
}
|
||||
|
||||
log.info(emailEventInfo)
|
||||
|
||||
logAmplitudeEvent(log, message, emailEventInfo)
|
||||
}
|
||||
|
||||
function logFlowEventFromMessage(log, message, type) {
|
||||
|
|
|
@ -158,6 +158,15 @@ Lug.prototype.flowEvent = function (data) {
|
|||
this.logger.info('flowEvent', data)
|
||||
}
|
||||
|
||||
Lug.prototype.amplitudeEvent = function (data) {
|
||||
if (! data || ! data.event_type) {
|
||||
this.error({ op: 'log.amplitudeEvent', data })
|
||||
return
|
||||
}
|
||||
|
||||
this.logger.info('amplitudeEvent', data)
|
||||
}
|
||||
|
||||
module.exports = function (level, name, options) {
|
||||
if (arguments.length === 1 && typeof level === 'object') {
|
||||
options = level
|
||||
|
|
|
@ -0,0 +1,209 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
// This module contains mappings from activity/flow event names to
|
||||
// amplitude event definitions. The intention is for the returned
|
||||
// `receiveEvent` function to be invoked for every event and the
|
||||
// mappings determine which of those will be transformed into an
|
||||
// amplitude event. You can read more about the amplitude event
|
||||
// structure here:
|
||||
//
|
||||
// https://amplitude.zendesk.com/hc/en-us/articles/204771828-HTTP-API
|
||||
|
||||
'use strict'
|
||||
|
||||
const APP_VERSION = /^[0-9]+\.([0-9]+)\./.exec(require('../../package.json').version)[1]
|
||||
|
||||
const GROUPS = {
|
||||
activity: 'fxa_activity',
|
||||
email: 'fxa_email',
|
||||
login: 'fxa_login',
|
||||
registration: 'fxa_reg',
|
||||
sms: 'fxa_sms'
|
||||
}
|
||||
|
||||
const EVENTS = {
|
||||
'account.confirmed': {
|
||||
group: GROUPS.login,
|
||||
event: 'email_confirmed'
|
||||
},
|
||||
'account.created': {
|
||||
group: GROUPS.registration,
|
||||
event: 'created'
|
||||
},
|
||||
'account.login': {
|
||||
group: GROUPS.login,
|
||||
event: 'success'
|
||||
},
|
||||
'account.login.blocked': {
|
||||
group: GROUPS.login,
|
||||
event: 'blocked'
|
||||
},
|
||||
'account.login.confirmedUnblockCode': {
|
||||
group: GROUPS.login,
|
||||
event: 'unblock_success'
|
||||
},
|
||||
'account.reset': {
|
||||
group: GROUPS.login,
|
||||
event: 'forgot_complete'
|
||||
},
|
||||
'account.signed': {
|
||||
group: GROUPS.activity,
|
||||
event: 'cert_signed'
|
||||
},
|
||||
'account.verified': {
|
||||
group: GROUPS.registration,
|
||||
event: 'email_confirmed'
|
||||
},
|
||||
'flow.complete': {
|
||||
isDynamicGroup: true,
|
||||
group: (request, data, metricsContext) => GROUPS[metricsContext.flowType],
|
||||
event: 'complete'
|
||||
},
|
||||
'password.forgot.resend_code.completed': {
|
||||
group: GROUPS.login,
|
||||
event: 'forgot_sent'
|
||||
},
|
||||
'password.forgot.send_code.completed': {
|
||||
group: GROUPS.login,
|
||||
event: 'forgot_sent'
|
||||
},
|
||||
'sms.installFirefox.sent': {
|
||||
group: GROUPS.sms,
|
||||
event: 'sent'
|
||||
}
|
||||
}
|
||||
|
||||
const EMAIL_EVENTS = /^email\.(\w+)\.(bounced|sent)$/
|
||||
|
||||
const EMAIL_TYPES = {
|
||||
newDeviceLoginEmail: 'login',
|
||||
passwordChangedEmail: 'change_password',
|
||||
passwordResetEmail: 'reset_password',
|
||||
passwordResetRequiredEmail: 'reset_password',
|
||||
postRemoveSecondaryEmail: 'secondary_email',
|
||||
postVerifyEmail: 'registration',
|
||||
postVerifySecondaryEmail: 'secondary_email',
|
||||
recoveryEmail: 'reset_password',
|
||||
unblockCode: 'unblock',
|
||||
verificationReminderFirstEmail: 'registration',
|
||||
verificationReminderSecondEmail: 'registration',
|
||||
verificationReminderEmail: 'registration',
|
||||
verifyEmail: 'registration',
|
||||
verifyLoginEmail: 'login',
|
||||
verifySyncEmail: 'registration',
|
||||
verifySecondaryEmail: 'secondary_email'
|
||||
}
|
||||
|
||||
const NOP = () => {}
|
||||
|
||||
const EVENT_PROPERTIES = {
|
||||
[GROUPS.activity]: NOP,
|
||||
[GROUPS.email]: mapEmailType,
|
||||
[GROUPS.login]: NOP,
|
||||
[GROUPS.registration]: NOP,
|
||||
[GROUPS.sms]: NOP
|
||||
}
|
||||
|
||||
const USER_PROPERTIES = {
|
||||
[GROUPS.activity]: mapUid,
|
||||
[GROUPS.email]: mapUid,
|
||||
[GROUPS.login]: mapUid,
|
||||
[GROUPS.registration]: mapUid,
|
||||
[GROUPS.sms]: NOP
|
||||
}
|
||||
|
||||
module.exports = log => {
|
||||
if (log) {
|
||||
return receiveEvent
|
||||
}
|
||||
|
||||
function receiveEvent (event, request, data = {}, metricsContext = {}) {
|
||||
if (! event || ! request) {
|
||||
log.error({ op: 'amplitudeMetrics', err: 'Bad argument', event, hasRequest: !! request })
|
||||
return
|
||||
}
|
||||
|
||||
let mapping = EVENTS[event]
|
||||
|
||||
if (! mapping) {
|
||||
const matches = EMAIL_EVENTS.exec(event)
|
||||
if (matches) {
|
||||
const eventCategory = matches[1]
|
||||
if (EMAIL_TYPES[eventCategory]) {
|
||||
mapping = {
|
||||
group: GROUPS.email,
|
||||
event: matches[2],
|
||||
eventCategory: matches[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mapping) {
|
||||
let group = mapping.group
|
||||
if (mapping.isDynamicGroup) {
|
||||
group = group(request, data, metricsContext)
|
||||
if (! group) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if (mapping.eventCategory) {
|
||||
data.eventCategory = mapping.eventCategory
|
||||
}
|
||||
log.amplitudeEvent({
|
||||
time: metricsContext.time || Date.now(),
|
||||
event_type: `${group} - ${mapping.event}`,
|
||||
session_id: getFromMetricsContext(metricsContext, 'flowBeginTime', request, 'flowBeginTime'),
|
||||
event_properties: mapEventProperties(group, request, data, metricsContext),
|
||||
user_properties: mapUserProperties(group, request, data, metricsContext),
|
||||
app_version: APP_VERSION,
|
||||
language: request.app.locale || undefined
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getFromMetricsContext (metricsContext, key, request, payloadKey) {
|
||||
return metricsContext[key] ||
|
||||
(request.payload.metricsContext && request.payload.metricsContext[payloadKey])
|
||||
}
|
||||
|
||||
function mapEventProperties (group, request, data, metricsContext) {
|
||||
return Object.assign({
|
||||
device_id: getFromMetricsContext(metricsContext, 'device_id', request, 'deviceId'),
|
||||
service: data.service || request.query.service || request.payload.service
|
||||
}, EVENT_PROPERTIES[group](request, data, metricsContext))
|
||||
}
|
||||
|
||||
function mapEmailType (request, data) {
|
||||
const emailType = EMAIL_TYPES[data.eventCategory]
|
||||
if (emailType) {
|
||||
return {
|
||||
email_type: emailType
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function mapUserProperties (group, request, data, metricsContext) {
|
||||
const { browser, browserVersion, os } = request.app.ua
|
||||
return Object.assign({
|
||||
flow_id: getFromMetricsContext(metricsContext, 'flow_id', request, 'flowId'),
|
||||
ua_browser: browser,
|
||||
ua_version: browserVersion,
|
||||
ua_os: os
|
||||
}, USER_PROPERTIES[group](request, data, metricsContext))
|
||||
}
|
||||
|
||||
function mapUid (request, data) {
|
||||
return {
|
||||
fxa_uid: data.uid || getFromToken(request, 'uid')
|
||||
}
|
||||
}
|
||||
|
||||
function getFromToken (request, key) {
|
||||
if (request.auth.credentials) {
|
||||
return request.auth.credentials[key]
|
||||
}
|
||||
}
|
|
@ -13,6 +13,11 @@ const P = require('../promise')
|
|||
const FLOW_ID_LENGTH = 64
|
||||
|
||||
const SCHEMA = isA.object({
|
||||
// The metrics context device id is a client-generated property
|
||||
// that is entirely separate to the devices table in our db.
|
||||
// All clients can generate a metrics context device id, whereas
|
||||
// only Sync creates records in the devices table.
|
||||
deviceId: isA.string().length(32).regex(HEX_STRING).optional(),
|
||||
flowId: isA.string().length(64).regex(HEX_STRING).optional(),
|
||||
flowBeginTime: isA.number().integer().positive().optional()
|
||||
})
|
||||
|
@ -23,11 +28,11 @@ module.exports = function (log, config) {
|
|||
const cache = require('../cache')(log, config, 'fxa-metrics~')
|
||||
|
||||
return {
|
||||
stash: stash,
|
||||
gather: gather,
|
||||
clear: clear,
|
||||
validate: validate,
|
||||
setFlowCompleteSignal: setFlowCompleteSignal
|
||||
stash,
|
||||
gather,
|
||||
clear,
|
||||
validate,
|
||||
setFlowCompleteSignal
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -85,9 +90,12 @@ module.exports = function (log, config) {
|
|||
.then(metadata => {
|
||||
if (metadata) {
|
||||
data.time = Date.now()
|
||||
data.device_id = metadata.deviceId
|
||||
data.flow_id = metadata.flowId
|
||||
data.flow_time = calculateFlowTime(data.time, metadata.flowBeginTime)
|
||||
data.flowBeginTime = metadata.flowBeginTime
|
||||
data.flowCompleteSignal = metadata.flowCompleteSignal
|
||||
data.flowType = metadata.flowType
|
||||
}
|
||||
})
|
||||
.catch(err => log.error({
|
||||
|
@ -218,9 +226,10 @@ module.exports = function (log, config) {
|
|||
* @this request
|
||||
* @param {String} flowCompleteSignal
|
||||
*/
|
||||
function setFlowCompleteSignal (flowCompleteSignal) {
|
||||
function setFlowCompleteSignal (flowCompleteSignal, flowType) {
|
||||
if (this.payload && this.payload.metricsContext) {
|
||||
this.payload.metricsContext.flowCompleteSignal = flowCompleteSignal
|
||||
this.payload.metricsContext.flowType = flowType
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,6 +64,8 @@ const FLOW_EVENT_ROUTES = new Set([
|
|||
const PATH_PREFIX = /^\/v1/
|
||||
|
||||
module.exports = log => {
|
||||
const amplitude = require('./amplitude')(log)
|
||||
|
||||
return {
|
||||
/**
|
||||
* Asynchronously emit a flow event and/or an activity event.
|
||||
|
@ -98,7 +100,15 @@ module.exports = log => {
|
|||
}
|
||||
|
||||
return emitFlowEvent(event, request, data)
|
||||
.then(() => {})
|
||||
})
|
||||
.then(metricsContext => {
|
||||
amplitude(event, request, data, metricsContext)
|
||||
|
||||
if (metricsContext && event === metricsContext.flowCompleteSignal) {
|
||||
log.flowEvent(Object.assign({}, metricsContext, { event: 'flow.complete' }))
|
||||
amplitude('flow.complete', request, data, metricsContext)
|
||||
request.clearMetricsContext()
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
|
@ -170,14 +180,6 @@ module.exports = log => {
|
|||
}
|
||||
|
||||
log.flowEvent(data)
|
||||
|
||||
if (event === data.flowCompleteSignal) {
|
||||
log.flowEvent(Object.assign({}, data, {
|
||||
event: 'flow.complete'
|
||||
}))
|
||||
|
||||
request.clearMetricsContext()
|
||||
}
|
||||
} else if (! OPTIONAL_FLOW_EVENTS[event]) {
|
||||
log.error({ op: 'metricsEvents.emitFlowEvent', event, missingFlowId: true })
|
||||
}
|
||||
|
|
|
@ -130,7 +130,7 @@ module.exports = (log, db, mailer, Password, config, customs, checkPassword, pus
|
|||
} else {
|
||||
flowCompleteSignal = 'account.verified'
|
||||
}
|
||||
request.setMetricsFlowCompleteSignal(flowCompleteSignal)
|
||||
request.setMetricsFlowCompleteSignal(flowCompleteSignal, 'registration')
|
||||
|
||||
return P.resolve()
|
||||
}
|
||||
|
@ -283,7 +283,8 @@ module.exports = (log, db, mailer, Password, config, customs, checkPassword, pus
|
|||
uaBrowserVersion: sessionToken.uaBrowserVersion,
|
||||
uaOS: sessionToken.uaOS,
|
||||
uaOSVersion: sessionToken.uaOSVersion,
|
||||
uaDeviceType: sessionToken.uaDeviceType
|
||||
uaDeviceType: sessionToken.uaDeviceType,
|
||||
uid: sessionToken.uid
|
||||
})
|
||||
.then(function () {
|
||||
// only create reminder if sendVerifyCode succeeds
|
||||
|
@ -673,7 +674,7 @@ module.exports = (log, db, mailer, Password, config, customs, checkPassword, pus
|
|||
} else {
|
||||
flowCompleteSignal = 'account.login'
|
||||
}
|
||||
request.setMetricsFlowCompleteSignal(flowCompleteSignal)
|
||||
request.setMetricsFlowCompleteSignal(flowCompleteSignal, 'login')
|
||||
|
||||
return checkPassword(accountRecord, authPW, request.app.clientAddress)
|
||||
.then(
|
||||
|
@ -886,7 +887,8 @@ module.exports = (log, db, mailer, Password, config, customs, checkPassword, pus
|
|||
uaBrowserVersion: sessionToken.uaBrowserVersion,
|
||||
uaOS: sessionToken.uaOS,
|
||||
uaOSVersion: sessionToken.uaOSVersion,
|
||||
uaDeviceType: sessionToken.uaDeviceType
|
||||
uaDeviceType: sessionToken.uaDeviceType,
|
||||
uid: sessionToken.uid
|
||||
})
|
||||
}
|
||||
)
|
||||
|
@ -918,12 +920,14 @@ module.exports = (log, db, mailer, Password, config, customs, checkPassword, pus
|
|||
flowBeginTime: flowBeginTime,
|
||||
ip: ip,
|
||||
location: geoData.location,
|
||||
service,
|
||||
timeZone: geoData.timeZone,
|
||||
uaBrowser: sessionToken.uaBrowser,
|
||||
uaBrowserVersion: sessionToken.uaBrowserVersion,
|
||||
uaOS: sessionToken.uaOS,
|
||||
uaOSVersion: sessionToken.uaOSVersion,
|
||||
uaDeviceType: sessionToken.uaDeviceType
|
||||
uaDeviceType: sessionToken.uaDeviceType,
|
||||
uid: sessionToken.uid
|
||||
}
|
||||
)
|
||||
.catch(e => {
|
||||
|
@ -966,7 +970,8 @@ module.exports = (log, db, mailer, Password, config, customs, checkPassword, pus
|
|||
uaBrowserVersion: sessionToken.uaBrowserVersion,
|
||||
uaOS: sessionToken.uaOS,
|
||||
uaOSVersion: sessionToken.uaOSVersion,
|
||||
uaDeviceType: sessionToken.uaDeviceType
|
||||
uaDeviceType: sessionToken.uaDeviceType,
|
||||
uid: sessionToken.uid
|
||||
}
|
||||
)
|
||||
})
|
||||
|
@ -1282,7 +1287,8 @@ module.exports = (log, db, mailer, Password, config, customs, checkPassword, pus
|
|||
uaBrowserVersion,
|
||||
uaOS,
|
||||
uaOSVersion,
|
||||
uaDeviceType
|
||||
uaDeviceType,
|
||||
uid: emailRecord.uid
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -175,6 +175,8 @@ module.exports = (log, db, mailer, config, customs, push) => {
|
|||
let verifyFunction
|
||||
let event
|
||||
let emails = []
|
||||
let flowId
|
||||
let flowBeginTime
|
||||
|
||||
// Return immediately if this session or token is already verified. Only exception
|
||||
// is if the email param has been specified, which means that this is
|
||||
|
@ -183,13 +185,21 @@ module.exports = (log, db, mailer, config, customs, push) => {
|
|||
return reply({})
|
||||
}
|
||||
|
||||
if (request.payload.metricsContext) {
|
||||
flowId = request.payload.metricsContext.flowId
|
||||
flowBeginTime = request.payload.metricsContext.flowBeginTime
|
||||
}
|
||||
|
||||
customs.check(request, sessionToken.email, 'recoveryEmailResendCode')
|
||||
.then(setVerifyCode)
|
||||
.then(setVerifyFunction)
|
||||
.then(() => {
|
||||
const mailerOpts = {
|
||||
code: code,
|
||||
service: service,
|
||||
code,
|
||||
deviceId: sessionToken.deviceId,
|
||||
flowId,
|
||||
flowBeginTime,
|
||||
service,
|
||||
timestamp: Date.now(),
|
||||
redirectTo: request.payload.redirectTo,
|
||||
resume: request.payload.resume,
|
||||
|
@ -198,7 +208,8 @@ module.exports = (log, db, mailer, config, customs, push) => {
|
|||
uaBrowserVersion: sessionToken.uaBrowserVersion,
|
||||
uaOS: sessionToken.uaOS,
|
||||
uaOSVersion: sessionToken.uaOSVersion,
|
||||
uaDeviceType: sessionToken.uaDeviceType
|
||||
uaDeviceType: sessionToken.uaDeviceType,
|
||||
uid: sessionToken.uid
|
||||
}
|
||||
|
||||
return verifyFunction(emails, sessionToken, mailerOpts)
|
||||
|
@ -351,7 +362,9 @@ module.exports = (log, db, mailer, config, customs, push) => {
|
|||
|
||||
return mailer.sendPostVerifySecondaryEmail([], account, {
|
||||
acceptLanguage: request.app.acceptLanguage,
|
||||
secondaryEmail: matchedEmail.email
|
||||
secondaryEmail: matchedEmail.email,
|
||||
service,
|
||||
uid
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -473,7 +486,9 @@ module.exports = (log, db, mailer, config, customs, push) => {
|
|||
// so only send it if we're sure this is for sync.
|
||||
if (service === 'sync') {
|
||||
return mailer.sendPostVerifyEmail([], account, {
|
||||
acceptLanguage: request.app.acceptLanguage
|
||||
acceptLanguage: request.app.acceptLanguage,
|
||||
service,
|
||||
uid
|
||||
})
|
||||
}
|
||||
})
|
||||
|
@ -633,6 +648,7 @@ module.exports = (log, db, mailer, config, customs, push) => {
|
|||
.then((geoData) => {
|
||||
return mailer.sendVerifySecondaryEmail([emailData], sessionToken, {
|
||||
code: emailData.emailCode,
|
||||
deviceId: sessionToken.deviceId,
|
||||
acceptLanguage: request.app.acceptLanguage,
|
||||
email: emailData.email,
|
||||
primaryEmail: primaryEmail,
|
||||
|
@ -643,6 +659,7 @@ module.exports = (log, db, mailer, config, customs, push) => {
|
|||
uaBrowserVersion: sessionToken.uaBrowserVersion,
|
||||
uaOS: sessionToken.uaOS,
|
||||
uaOSVersion: sessionToken.uaOSVersion,
|
||||
uid
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -695,7 +712,11 @@ module.exports = (log, db, mailer, config, customs, push) => {
|
|||
return item
|
||||
}
|
||||
})
|
||||
return mailer.sendPostRemoveSecondaryEmail(emails, account, {secondaryEmail: email})
|
||||
return mailer.sendPostRemoveSecondaryEmail(emails, account, {
|
||||
deviceId: sessionToken.deviceId,
|
||||
secondaryEmail: email,
|
||||
uid
|
||||
})
|
||||
})
|
||||
.then(
|
||||
() => reply({}),
|
||||
|
|
|
@ -282,7 +282,8 @@ module.exports = function (
|
|||
uaBrowserVersion,
|
||||
uaOS,
|
||||
uaOSVersion,
|
||||
uaDeviceType
|
||||
uaDeviceType,
|
||||
uid: passwordChangeToken.uid
|
||||
})
|
||||
.catch(e => {
|
||||
// If we couldn't email them, no big deal. Log
|
||||
|
@ -466,7 +467,8 @@ module.exports = function (
|
|||
uaBrowserVersion,
|
||||
uaOS,
|
||||
uaOSVersion,
|
||||
uaDeviceType
|
||||
uaDeviceType,
|
||||
uid: passwordForgotToken.uid
|
||||
})
|
||||
})
|
||||
.then(
|
||||
|
@ -571,7 +573,8 @@ module.exports = function (
|
|||
uaBrowserVersion,
|
||||
uaOS,
|
||||
uaOSVersion,
|
||||
uaDeviceType
|
||||
uaDeviceType,
|
||||
uid: passwordForgotToken.uid
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -649,7 +652,8 @@ module.exports = function (
|
|||
code: code,
|
||||
acceptLanguage: request.app.acceptLanguage,
|
||||
flowId: flowId,
|
||||
flowBeginTime: flowBeginTime
|
||||
flowBeginTime: flowBeginTime,
|
||||
uid: passwordForgotToken.uid
|
||||
}
|
||||
)
|
||||
})
|
||||
|
|
|
@ -226,10 +226,18 @@ module.exports = function (log) {
|
|||
|
||||
var localized = this.localize(message)
|
||||
|
||||
if (message.flowBeginTime && message.flowId) {
|
||||
message.headers['X-Flow-Id'] = message.flowId
|
||||
message.headers['X-Flow-Begin-Time'] = message.flowBeginTime
|
||||
}
|
||||
const headers = Object.assign(
|
||||
{
|
||||
'Content-Language': localized.language,
|
||||
'X-Template-Name': message.template
|
||||
},
|
||||
message.headers,
|
||||
optionalHeader('X-Device-Id', message.deviceId),
|
||||
optionalHeader('X-Flow-Id', message.flowId),
|
||||
optionalHeader('X-Flow-Begin-Time', message.flowBeginTime),
|
||||
optionalHeader('X-Service-Id', message.service),
|
||||
optionalHeader('X-Uid', message.uid)
|
||||
)
|
||||
|
||||
var emailConfig = {
|
||||
sender: this.sender,
|
||||
|
@ -239,10 +247,7 @@ module.exports = function (log) {
|
|||
text: localized.text,
|
||||
html: localized.html,
|
||||
xMailer: false,
|
||||
headers: extend({
|
||||
'Content-Language': localized.language,
|
||||
'X-Template-Name': message.template
|
||||
}, message.headers)
|
||||
headers
|
||||
}
|
||||
|
||||
// Utilize nodemailer's cc ability to send to multiple addresses
|
||||
|
@ -313,8 +318,6 @@ module.exports = function (log) {
|
|||
|
||||
var headers = {
|
||||
'X-Link': links.link,
|
||||
'X-Service-ID': message.service,
|
||||
'X-Uid': message.uid,
|
||||
'X-Verify-Code': message.code
|
||||
}
|
||||
|
||||
|
@ -329,10 +332,12 @@ module.exports = function (log) {
|
|||
|
||||
return this.send({
|
||||
acceptLanguage: message.acceptLanguage,
|
||||
deviceId: message.deviceId,
|
||||
email: message.email,
|
||||
flowId: message.flowId,
|
||||
flowBeginTime: message.flowBeginTime,
|
||||
headers: headers,
|
||||
service: message.service,
|
||||
subject: subject,
|
||||
template: templateName,
|
||||
templateValues: {
|
||||
|
@ -364,7 +369,6 @@ module.exports = function (log) {
|
|||
var links = this._generateLinks(null, message.email, query, templateName)
|
||||
|
||||
var headers = {
|
||||
'X-Uid': message.uid,
|
||||
'X-Unblock-Code': message.unblockCode,
|
||||
'X-Report-SignIn-Link': links.reportSignInLink
|
||||
}
|
||||
|
@ -376,10 +380,12 @@ module.exports = function (log) {
|
|||
return this.send({
|
||||
acceptLanguage: message.acceptLanguage,
|
||||
ccEmails: message.ccEmails,
|
||||
deviceId: message.deviceId,
|
||||
email: message.email,
|
||||
flowId: message.flowId,
|
||||
flowBeginTime: message.flowBeginTime,
|
||||
headers: headers,
|
||||
service: message.service,
|
||||
subject: gettext('Firefox Account authorization code'),
|
||||
template: templateName,
|
||||
templateValues: {
|
||||
|
@ -414,8 +420,6 @@ module.exports = function (log) {
|
|||
|
||||
var headers = {
|
||||
'X-Link': links.link,
|
||||
'X-Service-ID': message.service,
|
||||
'X-Uid': message.uid,
|
||||
'X-Verify-Code': message.code
|
||||
}
|
||||
|
||||
|
@ -426,10 +430,12 @@ module.exports = function (log) {
|
|||
return this.send({
|
||||
acceptLanguage: message.acceptLanguage,
|
||||
ccEmails: message.ccEmails,
|
||||
deviceId: message.deviceId,
|
||||
email: message.email,
|
||||
flowId: message.flowId,
|
||||
flowBeginTime: message.flowBeginTime,
|
||||
headers: headers,
|
||||
service: message.service,
|
||||
subject: gettext('Confirm new sign-in to Firefox'),
|
||||
template: templateName,
|
||||
templateValues: {
|
||||
|
@ -469,8 +475,6 @@ module.exports = function (log) {
|
|||
|
||||
var headers = {
|
||||
'X-Link': links.link,
|
||||
'X-Service-ID': message.service,
|
||||
'X-Uid': message.uid,
|
||||
'X-Verify-Code': message.code
|
||||
}
|
||||
|
||||
|
@ -480,10 +484,12 @@ module.exports = function (log) {
|
|||
|
||||
return this.send({
|
||||
acceptLanguage: message.acceptLanguage,
|
||||
deviceId: message.deviceId,
|
||||
email: message.email,
|
||||
flowId: message.flowId,
|
||||
flowBeginTime: message.flowBeginTime,
|
||||
headers: headers,
|
||||
service: message.service,
|
||||
subject: gettext('Verify email for Firefox Accounts'),
|
||||
template: templateName,
|
||||
templateValues: {
|
||||
|
@ -533,10 +539,12 @@ module.exports = function (log) {
|
|||
return this.send({
|
||||
acceptLanguage: message.acceptLanguage,
|
||||
ccEmails: message.ccEmails,
|
||||
deviceId: message.deviceId,
|
||||
email: message.email,
|
||||
flowId: message.flowId,
|
||||
flowBeginTime: message.flowBeginTime,
|
||||
headers: headers,
|
||||
service: message.service,
|
||||
subject: gettext('Reset your Firefox Account password'),
|
||||
template: templateName,
|
||||
templateValues: {
|
||||
|
@ -571,10 +579,12 @@ module.exports = function (log) {
|
|||
return this.send({
|
||||
acceptLanguage: message.acceptLanguage,
|
||||
ccEmails: message.ccEmails,
|
||||
deviceId: message.deviceId,
|
||||
email: message.email,
|
||||
flowId: message.flowId,
|
||||
flowBeginTime: message.flowBeginTime,
|
||||
headers: headers,
|
||||
service: message.service,
|
||||
subject: gettext('Your Firefox Account password has been changed'),
|
||||
template: templateName,
|
||||
templateValues: {
|
||||
|
@ -607,10 +617,12 @@ module.exports = function (log) {
|
|||
return this.send({
|
||||
acceptLanguage: message.acceptLanguage,
|
||||
ccEmails: message.ccEmails,
|
||||
deviceId: message.deviceId,
|
||||
email: message.email,
|
||||
flowId: message.flowId,
|
||||
flowBeginTime: message.flowBeginTime,
|
||||
headers: headers,
|
||||
service: message.service,
|
||||
subject: gettext('Your Firefox Account password has been reset'),
|
||||
template: templateName,
|
||||
templateValues: {
|
||||
|
@ -638,10 +650,12 @@ module.exports = function (log) {
|
|||
|
||||
return this.send({
|
||||
acceptLanguage: message.acceptLanguage,
|
||||
deviceId: message.deviceId,
|
||||
email: message.email,
|
||||
flowId: message.flowId,
|
||||
flowBeginTime: message.flowBeginTime,
|
||||
headers: headers,
|
||||
service: message.service,
|
||||
subject: gettext('Firefox Account password reset required'),
|
||||
template: templateName,
|
||||
templateValues: {
|
||||
|
@ -669,10 +683,12 @@ module.exports = function (log) {
|
|||
return this.send({
|
||||
acceptLanguage: message.acceptLanguage,
|
||||
ccEmails: message.ccEmails,
|
||||
deviceId: message.deviceId,
|
||||
email: message.email,
|
||||
flowId: message.flowId,
|
||||
flowBeginTime: message.flowBeginTime,
|
||||
headers: headers,
|
||||
service: message.service,
|
||||
subject: gettext('New sign-in to Firefox'),
|
||||
template: templateName,
|
||||
templateValues: {
|
||||
|
@ -706,10 +722,12 @@ module.exports = function (log) {
|
|||
|
||||
return this.send({
|
||||
acceptLanguage: message.acceptLanguage,
|
||||
deviceId: message.deviceId,
|
||||
email: message.email,
|
||||
flowId: message.flowId,
|
||||
flowBeginTime: message.flowBeginTime,
|
||||
headers: headers,
|
||||
service: message.service,
|
||||
subject: gettext('Firefox Account verified'),
|
||||
template: templateName,
|
||||
templateValues: {
|
||||
|
@ -742,10 +760,12 @@ module.exports = function (log) {
|
|||
|
||||
return this.send({
|
||||
acceptLanguage: message.acceptLanguage,
|
||||
deviceId: message.deviceId,
|
||||
email: message.email,
|
||||
flowId: message.flowId,
|
||||
flowBeginTime: message.flowBeginTime,
|
||||
headers: headers,
|
||||
service: message.service,
|
||||
subject: gettext('Secondary Firefox Account email added'),
|
||||
template: templateName,
|
||||
templateValues: {
|
||||
|
@ -777,11 +797,13 @@ module.exports = function (log) {
|
|||
|
||||
return this.send({
|
||||
acceptLanguage: message.acceptLanguage,
|
||||
email: message.email,
|
||||
ccEmails: message.ccEmails,
|
||||
deviceId: message.deviceId,
|
||||
email: message.email,
|
||||
flowId: message.flowId,
|
||||
flowBeginTime: message.flowBeginTime,
|
||||
headers: headers,
|
||||
service: message.service,
|
||||
subject: gettext('Secondary Firefox Account email removed'),
|
||||
template: templateName,
|
||||
templateValues: {
|
||||
|
@ -825,7 +847,6 @@ module.exports = function (log) {
|
|||
|
||||
var headers = {
|
||||
'X-Link': links.link,
|
||||
'X-Uid': message.uid,
|
||||
'X-Verify-Code': message.code
|
||||
}
|
||||
|
||||
|
@ -835,9 +856,13 @@ module.exports = function (log) {
|
|||
|
||||
return this.send({
|
||||
acceptLanguage: message.acceptLanguage || 'en',
|
||||
deviceId: message.deviceId,
|
||||
email: message.email,
|
||||
flowId: message.flowId,
|
||||
flowBeginTime: message.flowBeginTime,
|
||||
headers: headers,
|
||||
subject: subject,
|
||||
service: message.service,
|
||||
template: templateName,
|
||||
templateValues: {
|
||||
email: message.email,
|
||||
|
@ -948,3 +973,10 @@ module.exports = function (log) {
|
|||
|
||||
return Mailer
|
||||
}
|
||||
|
||||
function optionalHeader (key, value) {
|
||||
if (value) {
|
||||
return { [key]: value }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -789,10 +789,10 @@ function parseMetricsContext () {
|
|||
.then(metricsContext => metricsContext.map(item => {
|
||||
item.value = marshallValidation(
|
||||
item.value
|
||||
.replace(/{ /g, '{\n * `')
|
||||
.replace(/ }/g, '\n\n }')
|
||||
.replace(/, /, '\n * `')
|
||||
.replace(/:/g, '`:')
|
||||
.replace('{ ', '{\n * ')
|
||||
.replace(/ }/, '\n\n }')
|
||||
.replace(/, ([a-zA-Z]+):/g, '\n * $1:')
|
||||
.replace(/([a-zA-Z]+):/g, '`$1`:')
|
||||
)
|
||||
return item
|
||||
}))
|
||||
|
|
|
@ -5,11 +5,18 @@
|
|||
'use strict'
|
||||
|
||||
const assert = require('insist')
|
||||
const emailHelpers = require('../../../lib/email/utils/helpers')
|
||||
const proxyquire = require('proxyquire')
|
||||
const sinon = require('sinon')
|
||||
const spyLog = require('../../mocks').spyLog
|
||||
|
||||
const amplitude = sinon.spy()
|
||||
const emailHelpers = proxyquire('../../../lib/email/utils/helpers', {
|
||||
'../../metrics/amplitude': () => amplitude
|
||||
})
|
||||
|
||||
describe('email utils helpers', () => {
|
||||
afterEach(() => amplitude.reset())
|
||||
|
||||
describe('getHeaderValue', () => {
|
||||
|
||||
it('works with message.mail.headers', () => {
|
||||
|
@ -69,7 +76,74 @@ describe('email utils helpers', () => {
|
|||
assert.equal(mockLog.info.args[1][0].domain, 'gmail.com')
|
||||
assert.equal(mockLog.info.args[2][0].domain, 'yahoo.com')
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
it('logEmailEventSent should call amplitude correctly', () => {
|
||||
emailHelpers.logEmailEventSent(spyLog(), {
|
||||
email: 'foo@example.com',
|
||||
ccEmails: [ 'bar@example.com', 'baz@example.com' ],
|
||||
template: 'verifyEmail',
|
||||
headers: [
|
||||
{ name: 'Content-Language', value: 'aaa' },
|
||||
{ name: 'X-Device-Id', value: 'bbb' },
|
||||
{ name: 'X-Flow-Id', value: 'ccc' },
|
||||
{ name: 'X-Service-Id', value: 'ddd' },
|
||||
{ name: 'X-Uid', value: 'eee' }
|
||||
]
|
||||
})
|
||||
assert.equal(amplitude.callCount, 1)
|
||||
const args = amplitude.args[0]
|
||||
assert.equal(args.length, 4)
|
||||
assert.equal(args[0], 'email.verifyEmail.sent')
|
||||
assert.deepEqual(args[1], {
|
||||
app: {
|
||||
locale: 'aaa',
|
||||
ua: {}
|
||||
},
|
||||
auth: {},
|
||||
query: {},
|
||||
payload: {}
|
||||
})
|
||||
assert.deepEqual(args[2], {
|
||||
device_id: 'bbb',
|
||||
service: 'ddd',
|
||||
uid: 'eee'
|
||||
})
|
||||
assert.equal(args[3].flow_id, 'ccc')
|
||||
assert.ok(args[3].time > Date.now() - 1000)
|
||||
})
|
||||
|
||||
it('logEmailEventFromMessage should call amplitude correctly', () => {
|
||||
emailHelpers.logEmailEventFromMessage(spyLog(), {
|
||||
email: 'foo@example.com',
|
||||
ccEmails: [ 'bar@example.com', 'baz@example.com' ],
|
||||
headers: [
|
||||
{ name: 'Content-Language', value: 'a' },
|
||||
{ name: 'X-Device-Id', value: 'b' },
|
||||
{ name: 'X-Flow-Id', value: 'c' },
|
||||
{ name: 'X-Service-Id', value: 'd' },
|
||||
{ name: 'X-Template-Name', value: 'verifyLoginEmail' },
|
||||
{ name: 'X-Uid', value: 'e' }
|
||||
]
|
||||
}, 'bounced', 'gmail.com')
|
||||
assert.equal(amplitude.callCount, 1)
|
||||
const args = amplitude.args[0]
|
||||
assert.equal(args.length, 4)
|
||||
assert.equal(args[0], 'email.verifyLoginEmail.bounced')
|
||||
assert.deepEqual(args[1], {
|
||||
app: {
|
||||
locale: 'a',
|
||||
ua: {}
|
||||
},
|
||||
auth: {},
|
||||
query: {},
|
||||
payload: {}
|
||||
})
|
||||
assert.deepEqual(args[2], {
|
||||
device_id: 'b',
|
||||
service: 'd',
|
||||
uid: 'e'
|
||||
})
|
||||
assert.equal(args[3].flow_id, 'c')
|
||||
})
|
||||
})
|
||||
|
|
|
@ -55,7 +55,11 @@ describe('log', () => {
|
|||
assert.equal(typeof log.begin, 'function', 'log.begin method was exported')
|
||||
assert.equal(typeof log.notifyAttachedServices, 'function', 'log.notifyAttachedServices method was exported')
|
||||
assert.equal(typeof log.activityEvent, 'function', 'log.activityEvent method was exported')
|
||||
assert.equal(log.activityEvent.length, 1, 'log.activityEvent expects 1 argument')
|
||||
assert.equal(typeof log.flowEvent, 'function', 'log.flowEvent method was exported')
|
||||
assert.equal(log.flowEvent.length, 1, 'log.flowEvent expects 1 argument')
|
||||
assert.equal(typeof log.amplitudeEvent, 'function', 'log.amplitudeEvent method was exported')
|
||||
assert.equal(log.amplitudeEvent.length, 1, 'log.amplitudeEvent expects 1 argument')
|
||||
assert.equal(typeof log.summary, 'function', 'log.summary method was exported')
|
||||
}
|
||||
)
|
||||
|
@ -340,6 +344,63 @@ describe('log', () => {
|
|||
}
|
||||
)
|
||||
|
||||
it('.amplitudeEvent', () => {
|
||||
log.amplitudeEvent({ event_type: 'foo' })
|
||||
|
||||
assert.equal(logger.info.callCount, 1, 'logger.info was called once')
|
||||
const args = logger.info.args[0]
|
||||
assert.equal(args.length, 2, 'logger.info was passed two arguments')
|
||||
assert.equal(args[0], 'amplitudeEvent', 'first argument was correct')
|
||||
assert.deepEqual(args[1], { event_type: 'foo' }, 'second argument was event data')
|
||||
|
||||
assert.equal(logger.debug.callCount, 0, 'logger.debug was not called')
|
||||
assert.equal(logger.error.callCount, 0, 'logger.error was not called')
|
||||
assert.equal(logger.critical.callCount, 0, 'logger.critical was not called')
|
||||
assert.equal(logger.warn.callCount, 0, 'logger.warn was not called')
|
||||
|
||||
logger.info.reset()
|
||||
})
|
||||
|
||||
it('.amplitudeEvent with missing data', () => {
|
||||
log.amplitudeEvent()
|
||||
|
||||
assert.equal(logger.error.callCount, 1, 'logger.error was called once')
|
||||
const args = logger.error.args[0]
|
||||
assert.equal(args.length, 2, 'logger.error was passed two arguments')
|
||||
assert.equal(args[0], 'log.amplitudeEvent', 'first argument was function name')
|
||||
assert.deepEqual(args[1], {
|
||||
op: 'log.amplitudeEvent',
|
||||
data: undefined
|
||||
}, 'second argument was correct')
|
||||
|
||||
assert.equal(logger.info.callCount, 0, 'logger.info was not called')
|
||||
assert.equal(logger.debug.callCount, 0, 'logger.debug was not called')
|
||||
assert.equal(logger.critical.callCount, 0, 'logger.critical was not called')
|
||||
assert.equal(logger.warn.callCount, 0, 'logger.warn was not called')
|
||||
|
||||
logger.error.reset()
|
||||
})
|
||||
|
||||
it('.amplitudeEvent with missing event_type', () => {
|
||||
log.amplitudeEvent({})
|
||||
|
||||
assert.equal(logger.error.callCount, 1, 'logger.error was called once')
|
||||
const args = logger.error.args[0]
|
||||
assert.equal(args.length, 2, 'logger.error was passed two arguments')
|
||||
assert.equal(args[0], 'log.amplitudeEvent', 'first argument was function name')
|
||||
assert.deepEqual(args[1], {
|
||||
op: 'log.amplitudeEvent',
|
||||
data: {}
|
||||
}, 'second argument was correct')
|
||||
|
||||
assert.equal(logger.info.callCount, 0, 'logger.info was not called')
|
||||
assert.equal(logger.debug.callCount, 0, 'logger.debug was not called')
|
||||
assert.equal(logger.critical.callCount, 0, 'logger.critical was not called')
|
||||
assert.equal(logger.warn.callCount, 0, 'logger.warn was not called')
|
||||
|
||||
logger.error.reset()
|
||||
})
|
||||
|
||||
it(
|
||||
'.error removes PII from error objects',
|
||||
() => {
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -68,7 +68,7 @@ describe('metricsContext', () => {
|
|||
assert.equal(metricsContext.validate.length, 0, 'metricsContext.validate expects no arguments')
|
||||
|
||||
assert.equal(typeof metricsContext.setFlowCompleteSignal, 'function', 'metricsContext.setFlowCompleteSignal is function')
|
||||
assert.equal(metricsContext.setFlowCompleteSignal.length, 1, 'metricsContext.setFlowCompleteSignal expects 1 argument')
|
||||
assert.equal(metricsContext.setFlowCompleteSignal.length, 2, 'metricsContext.setFlowCompleteSignal expects 2 arguments')
|
||||
|
||||
}
|
||||
)
|
||||
|
@ -187,9 +187,11 @@ describe('metricsContext', () => {
|
|||
return metricsContext.gather.call({
|
||||
payload: {
|
||||
metricsContext: {
|
||||
deviceId: 'mock device id',
|
||||
flowId: 'mock flow id',
|
||||
flowBeginTime: time,
|
||||
flowCompleteSignal: 'mock flow complete signal',
|
||||
flowType: 'mock flow type',
|
||||
context: 'mock context',
|
||||
entrypoint: 'mock entry point',
|
||||
migration: 'mock migration',
|
||||
|
@ -205,12 +207,15 @@ describe('metricsContext', () => {
|
|||
}, {}).then(function (result) {
|
||||
assert.equal(typeof result, 'object', 'result is object')
|
||||
assert.notEqual(result, null, 'result is not null')
|
||||
assert.equal(Object.keys(result).length, 4, 'result has 4 properties')
|
||||
assert.equal(Object.keys(result).length, 7, 'result has 7 properties')
|
||||
assert.ok(result.time > time, 'result.time seems correct')
|
||||
assert.equal(result.device_id, 'mock device id', 'result.device_id is correct')
|
||||
assert.equal(result.flow_id, 'mock flow id', 'result.flow_id is correct')
|
||||
assert.ok(result.flow_time > 0, 'result.flow_time is greater than zero')
|
||||
assert.ok(result.flow_time < time, 'result.flow_time is less than the current time')
|
||||
assert.equal(result.flowBeginTime, time, 'result.flowBeginTime is correct')
|
||||
assert.equal(result.flowCompleteSignal, 'mock flow complete signal', 'result.flowCompleteSignal is correct')
|
||||
assert.equal(result.flowType, 'mock flow type', 'result.flowType is correct')
|
||||
|
||||
assert.equal(cache.get.callCount, 0, 'cache.get was not called')
|
||||
assert.equal(log.error.callCount, 0, 'log.error was not called')
|
||||
|
@ -247,9 +252,11 @@ describe('metricsContext', () => {
|
|||
id: 'wibble'
|
||||
}
|
||||
results.get = P.resolve({
|
||||
deviceId: 'deviceId',
|
||||
flowId: 'flowId',
|
||||
flowBeginTime: time,
|
||||
flowCompleteSignal: 'flowCompleteSignal'
|
||||
flowCompleteSignal: 'flowCompleteSignal',
|
||||
flowType: 'flowType'
|
||||
})
|
||||
return metricsContext.gather.call({
|
||||
auth: {
|
||||
|
@ -262,12 +269,15 @@ describe('metricsContext', () => {
|
|||
|
||||
assert.equal(typeof result, 'object', 'result is object')
|
||||
assert.notEqual(result, null, 'result is not null')
|
||||
assert.equal(Object.keys(result).length, 4, 'result has 4 properties')
|
||||
assert.equal(Object.keys(result).length, 7, 'result has 7 properties')
|
||||
assert.ok(result.time > time, 'result.time seems correct')
|
||||
assert.equal(result.device_id, 'deviceId', 'result.device_id is correct')
|
||||
assert.equal(result.flow_id, 'flowId', 'result.flow_id is correct')
|
||||
assert.ok(result.flow_time > 0, 'result.flow_time is greater than zero')
|
||||
assert.ok(result.flow_time < time, 'result.flow_time is less than the current time')
|
||||
assert.equal(result.flowBeginTime, time, 'result.flowBeginTime is correct')
|
||||
assert.equal(result.flowCompleteSignal, 'flowCompleteSignal', 'result.flowCompleteSignal is correct')
|
||||
assert.equal(result.flowType, 'flowType', 'result.flowType is correct')
|
||||
|
||||
assert.equal(log.error.callCount, 0, 'log.error was not called')
|
||||
})
|
||||
|
@ -296,7 +306,7 @@ describe('metricsContext', () => {
|
|||
|
||||
assert.equal(typeof result, 'object', 'result is object')
|
||||
assert.notEqual(result, null, 'result is not null')
|
||||
assert.equal(Object.keys(result).length, 4, 'result has 4 properties')
|
||||
assert.equal(Object.keys(result).length, 7, 'result has 7 properties')
|
||||
assert.ok(result.time > time, 'result.time seems correct')
|
||||
assert.equal(result.flow_id, 'flowId', 'result.flow_id is correct')
|
||||
assert.ok(result.flow_time > 0, 'result.flow_time is greater than zero')
|
||||
|
@ -356,26 +366,29 @@ describe('metricsContext', () => {
|
|||
() => {
|
||||
const time = Date.now() - 1
|
||||
results.get = P.resolve({
|
||||
flowId: 'foo',
|
||||
flowBeginTime: time
|
||||
deviceId: 'foo',
|
||||
flowId: 'bar',
|
||||
flowBeginTime: time - 1
|
||||
})
|
||||
return metricsContext.gather.call({
|
||||
auth: {
|
||||
credentials: {
|
||||
uid: Array(16).fill('f').join(''),
|
||||
id: 'bar'
|
||||
id: 'baz'
|
||||
}
|
||||
},
|
||||
payload: {
|
||||
metricsContext: {
|
||||
flowId: 'baz',
|
||||
deviceId: 'qux',
|
||||
flowId: 'wibble',
|
||||
flowBeginTime: time
|
||||
}
|
||||
}
|
||||
}, {}).then(function (result) {
|
||||
assert.equal(typeof result, 'object', 'result is object')
|
||||
assert.notEqual(result, null, 'result is not null')
|
||||
assert.equal(result.flow_id, 'baz', 'result.flow_id is correct')
|
||||
assert.equal(result.device_id, 'qux', 'result.device_id is correct')
|
||||
assert.equal(result.flow_id, 'wibble', 'result.flow_id is correct')
|
||||
|
||||
assert.equal(cache.get.callCount, 0, 'cache.get was not called')
|
||||
assert.equal(log.error.callCount, 0, 'log.error was not called')
|
||||
|
@ -896,8 +909,11 @@ describe('metricsContext', () => {
|
|||
metricsContext: {}
|
||||
}
|
||||
}
|
||||
metricsContext.setFlowCompleteSignal.call(request, 'wibble')
|
||||
assert.deepEqual(request.payload.metricsContext, { flowCompleteSignal: 'wibble' }, 'flowCompleteSignal was set correctly')
|
||||
metricsContext.setFlowCompleteSignal.call(request, 'wibble', 'blee')
|
||||
assert.deepEqual(request.payload.metricsContext, {
|
||||
flowCompleteSignal: 'wibble',
|
||||
flowType: 'blee'
|
||||
}, 'flowCompleteSignal and flowType were set correctly')
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -907,8 +923,8 @@ describe('metricsContext', () => {
|
|||
const request = {
|
||||
payload: {}
|
||||
}
|
||||
metricsContext.setFlowCompleteSignal.call(request, 'wibble')
|
||||
assert.deepEqual(request.payload, {}, 'flowCompleteSignal was not set')
|
||||
metricsContext.setFlowCompleteSignal.call(request, 'wibble', 'blee')
|
||||
assert.deepEqual(request.payload, {}, 'flowCompleteSignal and flowType were not set')
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ const assert = require('insist')
|
|||
const sinon = require('sinon')
|
||||
const log = {
|
||||
activityEvent: sinon.spy(),
|
||||
amplitudeEvent: sinon.spy(),
|
||||
error: sinon.spy(),
|
||||
flowEvent: sinon.spy()
|
||||
}
|
||||
|
@ -15,6 +16,13 @@ const events = require('../../../lib/metrics/events')(log)
|
|||
const mocks = require('../../mocks')
|
||||
|
||||
describe('metrics/events', () => {
|
||||
afterEach(() => {
|
||||
log.activityEvent.reset()
|
||||
log.amplitudeEvent.reset()
|
||||
log.error.reset()
|
||||
log.flowEvent.reset()
|
||||
})
|
||||
|
||||
it('interface is correct', () => {
|
||||
assert.equal(typeof events, 'object', 'events is object')
|
||||
assert.notEqual(events, null, 'events is not null')
|
||||
|
@ -44,11 +52,10 @@ describe('metrics/events', () => {
|
|||
}, 'argument was correct')
|
||||
|
||||
assert.equal(log.activityEvent.callCount, 0, 'log.activityEvent was not called')
|
||||
assert.equal(log.amplitudeEvent.callCount, 0, 'log.amplitudeEvent was not called')
|
||||
assert.equal(metricsContext.gather.callCount, 0, 'metricsContext.gather was not called')
|
||||
assert.equal(log.flowEvent.callCount, 0, 'log.flowEvent was not called')
|
||||
assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called')
|
||||
}).finally(() => {
|
||||
log.error.reset()
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -78,12 +85,11 @@ describe('metrics/events', () => {
|
|||
uid: 'baz'
|
||||
}, 'argument was event data')
|
||||
|
||||
assert.equal(log.amplitudeEvent.callCount, 0, 'log.amplitudeEvent was not called')
|
||||
assert.equal(metricsContext.gather.callCount, 0, 'metricsContext.gather was not called')
|
||||
assert.equal(log.flowEvent.callCount, 0, 'log.flowEvent was not called')
|
||||
assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called')
|
||||
assert.equal(log.error.callCount, 0, 'log.error was not called')
|
||||
}).finally(() => {
|
||||
log.activityEvent.reset()
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -106,12 +112,11 @@ describe('metrics/events', () => {
|
|||
service: 'bar'
|
||||
}, 'argument was event data')
|
||||
|
||||
assert.equal(log.amplitudeEvent.callCount, 0, 'log.amplitudeEvent was not called')
|
||||
assert.equal(metricsContext.gather.callCount, 0, 'metricsContext.gather was not called')
|
||||
assert.equal(log.flowEvent.callCount, 0, 'log.flowEvent was not called')
|
||||
assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called')
|
||||
assert.equal(log.error.callCount, 0, 'log.error was not called')
|
||||
}).finally(() => {
|
||||
log.activityEvent.reset()
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -129,12 +134,11 @@ describe('metrics/events', () => {
|
|||
userAgent: 'test user-agent'
|
||||
}, 'argument was event data')
|
||||
|
||||
assert.equal(log.amplitudeEvent.callCount, 0, 'log.amplitudeEvent was not called')
|
||||
assert.equal(metricsContext.gather.callCount, 0, 'metricsContext.gather was not called')
|
||||
assert.equal(log.flowEvent.callCount, 0, 'log.flowEvent was not called')
|
||||
assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called')
|
||||
assert.equal(log.error.callCount, 0, 'log.error was not called')
|
||||
}).finally(() => {
|
||||
log.activityEvent.reset()
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -167,7 +171,9 @@ describe('metrics/events', () => {
|
|||
event: 'account.reminder',
|
||||
flow_id: 'bar',
|
||||
flow_time: 1000,
|
||||
flowBeginTime: time - 1000,
|
||||
flowCompleteSignal: 'account.signed',
|
||||
flowType: undefined,
|
||||
locale: 'en-US',
|
||||
time,
|
||||
uid: 'deadbeef',
|
||||
|
@ -175,11 +181,10 @@ describe('metrics/events', () => {
|
|||
}, 'argument was event data')
|
||||
|
||||
assert.equal(log.activityEvent.callCount, 0, 'log.activityEvent was not called')
|
||||
assert.equal(log.amplitudeEvent.callCount, 0, 'log.amplitudeEvent was not called')
|
||||
assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called')
|
||||
assert.equal(log.error.callCount, 0, 'log.error was not called')
|
||||
}).finally(() => {
|
||||
metricsContext.gather.reset()
|
||||
log.flowEvent.reset()
|
||||
Date.now.restore()
|
||||
})
|
||||
})
|
||||
|
@ -217,18 +222,19 @@ describe('metrics/events', () => {
|
|||
event: 'account.reminder',
|
||||
flow_id: 'bar',
|
||||
flow_time: 1000,
|
||||
flowBeginTime: time - 1000,
|
||||
flowCompleteSignal: 'account.signed',
|
||||
flowType: undefined,
|
||||
locale: 'en',
|
||||
time,
|
||||
userAgent: 'foo'
|
||||
}, 'argument was event data')
|
||||
|
||||
assert.equal(log.activityEvent.callCount, 0, 'log.activityEvent was not called')
|
||||
assert.equal(log.amplitudeEvent.callCount, 0, 'log.amplitudeEvent was not called')
|
||||
assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called')
|
||||
assert.equal(log.error.callCount, 0, 'log.error was not called')
|
||||
}).finally(() => {
|
||||
metricsContext.gather.reset()
|
||||
log.flowEvent.reset()
|
||||
Date.now.restore()
|
||||
})
|
||||
})
|
||||
|
@ -258,7 +264,9 @@ describe('metrics/events', () => {
|
|||
event: 'account.reminder',
|
||||
flow_id: 'bar',
|
||||
flow_time: 1000,
|
||||
flowBeginTime: time - 1000,
|
||||
flowCompleteSignal: 'account.signed',
|
||||
flowType: undefined,
|
||||
locale: 'en-US',
|
||||
time,
|
||||
uid: 'deadbeef',
|
||||
|
@ -266,11 +274,10 @@ describe('metrics/events', () => {
|
|||
}, 'argument was event data')
|
||||
|
||||
assert.equal(log.activityEvent.callCount, 0, 'log.activityEvent was not called')
|
||||
assert.equal(log.amplitudeEvent.callCount, 0, 'log.amplitudeEvent was not called')
|
||||
assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called')
|
||||
assert.equal(log.error.callCount, 0, 'log.error was not called')
|
||||
}).finally(() => {
|
||||
metricsContext.gather.reset()
|
||||
log.flowEvent.reset()
|
||||
Date.now.restore()
|
||||
})
|
||||
})
|
||||
|
@ -300,7 +307,9 @@ describe('metrics/events', () => {
|
|||
event: 'account.reminder',
|
||||
flow_id: 'bar',
|
||||
flow_time: 1000,
|
||||
flowBeginTime: time - 1000,
|
||||
flowCompleteSignal: 'account.signed',
|
||||
flowType: undefined,
|
||||
locale: 'en-US',
|
||||
time,
|
||||
uid: 'deadbeef',
|
||||
|
@ -308,11 +317,10 @@ describe('metrics/events', () => {
|
|||
}, 'argument was event data')
|
||||
|
||||
assert.equal(log.activityEvent.callCount, 0, 'log.activityEvent was not called')
|
||||
assert.equal(log.amplitudeEvent.callCount, 0, 'log.amplitudeEvent was not called')
|
||||
assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called')
|
||||
assert.equal(log.error.callCount, 0, 'log.error was not called')
|
||||
}).finally(() => {
|
||||
metricsContext.gather.reset()
|
||||
log.flowEvent.reset()
|
||||
Date.now.restore()
|
||||
})
|
||||
})
|
||||
|
@ -342,18 +350,19 @@ describe('metrics/events', () => {
|
|||
event: 'account.reminder',
|
||||
flow_id: 'bar',
|
||||
flow_time: 1000,
|
||||
flowBeginTime: time - 1000,
|
||||
flowCompleteSignal: 'account.signed',
|
||||
flowType: undefined,
|
||||
locale: 'en-US',
|
||||
time,
|
||||
userAgent: 'test user-agent'
|
||||
}, 'argument was event data')
|
||||
|
||||
assert.equal(log.activityEvent.callCount, 0, 'log.activityEvent was not called')
|
||||
assert.equal(log.amplitudeEvent.callCount, 0, 'log.amplitudeEvent was not called')
|
||||
assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called')
|
||||
assert.equal(log.error.callCount, 0, 'log.error was not called')
|
||||
}).finally(() => {
|
||||
metricsContext.gather.reset()
|
||||
log.flowEvent.reset()
|
||||
Date.now.restore()
|
||||
})
|
||||
})
|
||||
|
@ -369,7 +378,8 @@ describe('metrics/events', () => {
|
|||
metricsContext: {
|
||||
flowId: 'bar',
|
||||
flowBeginTime: time - 2000,
|
||||
flowCompleteSignal: 'account.reminder'
|
||||
flowCompleteSignal: 'account.reminder',
|
||||
flowType: 'registration'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -382,7 +392,9 @@ describe('metrics/events', () => {
|
|||
event: 'account.reminder',
|
||||
flow_id: 'bar',
|
||||
flow_time: 2000,
|
||||
flowBeginTime: time - 2000,
|
||||
flowCompleteSignal: 'account.reminder',
|
||||
flowType: 'registration',
|
||||
locale: 'fr',
|
||||
time,
|
||||
uid: 'qux',
|
||||
|
@ -392,22 +404,25 @@ describe('metrics/events', () => {
|
|||
event: 'flow.complete',
|
||||
flow_id: 'bar',
|
||||
flow_time: 2000,
|
||||
flowBeginTime: time - 2000,
|
||||
flowCompleteSignal: 'account.reminder',
|
||||
flowType: 'registration',
|
||||
locale: 'fr',
|
||||
time,
|
||||
uid: 'qux',
|
||||
userAgent: 'test user-agent'
|
||||
}, 'argument was complete event data second time')
|
||||
|
||||
assert.equal(log.amplitudeEvent.callCount, 1, 'log.amplitudeEvent was called once')
|
||||
assert.equal(log.amplitudeEvent.args[0].length, 1, 'log.amplitudeEvent was passed one argument')
|
||||
assert.equal(log.amplitudeEvent.args[0][0].event_type, 'fxa_reg - complete', 'log.amplitudeEvent was passed correct event_type')
|
||||
|
||||
assert.equal(metricsContext.clear.callCount, 1, 'metricsContext.clear was called once')
|
||||
assert.equal(metricsContext.clear.args[0].length, 0, 'metricsContext.clear was passed no arguments')
|
||||
|
||||
assert.equal(log.activityEvent.callCount, 0, 'log.activityEvent was not called')
|
||||
assert.equal(log.error.callCount, 0, 'log.error was not called')
|
||||
}).finally(() => {
|
||||
metricsContext.gather.reset()
|
||||
log.flowEvent.reset()
|
||||
metricsContext.clear.reset()
|
||||
Date.now.restore()
|
||||
})
|
||||
})
|
||||
|
@ -436,11 +451,10 @@ describe('metrics/events', () => {
|
|||
}, 'argument was correct')
|
||||
|
||||
assert.equal(log.activityEvent.callCount, 0, 'log.activityEvent was not called')
|
||||
assert.equal(log.amplitudeEvent.callCount, 0, 'log.amplitudeEvent was not called')
|
||||
assert.equal(metricsContext.gather.callCount, 0, 'metricsContext.gather was not called')
|
||||
assert.equal(log.flowEvent.callCount, 0, 'log.flowEvent was not called')
|
||||
assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called')
|
||||
}).finally(() => {
|
||||
log.error.reset()
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -466,11 +480,9 @@ describe('metrics/events', () => {
|
|||
}, 'argument was correct')
|
||||
|
||||
assert.equal(log.activityEvent.callCount, 0, 'log.activityEvent was not called')
|
||||
assert.equal(log.amplitudeEvent.callCount, 0, 'log.amplitudeEvent was not called')
|
||||
assert.equal(log.flowEvent.callCount, 0, 'log.flowEvent was not called')
|
||||
assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called')
|
||||
}).finally(() => {
|
||||
metricsContext.gather.reset()
|
||||
log.error.reset()
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -508,18 +520,18 @@ describe('metrics/events', () => {
|
|||
event: 'account.keyfetch',
|
||||
flow_id: 'bar',
|
||||
flow_time: 42,
|
||||
flowBeginTime: time - 42,
|
||||
flowCompleteSignal: undefined,
|
||||
flowType: undefined,
|
||||
locale: 'en-US',
|
||||
uid: 'baz',
|
||||
userAgent: 'test user-agent'
|
||||
}, 'flow event data was correct')
|
||||
|
||||
assert.equal(log.amplitudeEvent.callCount, 0, 'log.amplitudeEvent was not called')
|
||||
assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called')
|
||||
assert.equal(log.error.callCount, 0, 'log.error was not called')
|
||||
}).finally(() => {
|
||||
log.activityEvent.reset()
|
||||
metricsContext.gather.reset()
|
||||
log.flowEvent.reset()
|
||||
Date.now.restore()
|
||||
})
|
||||
})
|
||||
|
@ -542,12 +554,10 @@ describe('metrics/events', () => {
|
|||
assert.equal(log.activityEvent.callCount, 1, 'log.activityEvent was called once')
|
||||
assert.equal(metricsContext.gather.callCount, 1, 'metricsContext.gather was called once')
|
||||
|
||||
assert.equal(log.amplitudeEvent.callCount, 0, 'log.amplitudeEvent was not called')
|
||||
assert.equal(log.flowEvent.callCount, 0, 'log.flowEvent was not called')
|
||||
assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called')
|
||||
assert.equal(log.error.callCount, 0, 'log.error was not called')
|
||||
}).finally(() => {
|
||||
log.activityEvent.reset()
|
||||
metricsContext.gather.reset()
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -571,12 +581,26 @@ describe('metrics/events', () => {
|
|||
return events.emit.call(request, 'account.signed', data)
|
||||
.then(() => {
|
||||
assert.equal(log.activityEvent.callCount, 1, 'log.activityEvent was called once')
|
||||
|
||||
assert.equal(log.amplitudeEvent.callCount, 1, 'log.amplitudeEvent was called once')
|
||||
assert.equal(log.amplitudeEvent.args[0].length, 1, 'log.amplitudeEvent was passed one argument')
|
||||
assert.equal(log.amplitudeEvent.args[0][0].event_type, 'fxa_activity - cert_signed', 'log.amplitudeEvent was passed correct event_type')
|
||||
assert.deepEqual(log.amplitudeEvent.args[0][0].event_properties, {
|
||||
device_id: undefined,
|
||||
service: 'content-server'
|
||||
}, 'log.amplitudeEvent was passed correct event properties')
|
||||
assert.deepEqual(log.amplitudeEvent.args[0][0].user_properties, {
|
||||
flow_id: 'bar',
|
||||
ua_browser: request.app.ua.browser,
|
||||
ua_version: request.app.ua.browserVersion,
|
||||
ua_os: request.app.ua.os,
|
||||
fxa_uid: 'baz'
|
||||
}, 'log.amplitudeEvent was passed correct event properties')
|
||||
|
||||
assert.equal(metricsContext.gather.callCount, 0, 'metricsContext.gather was not called')
|
||||
assert.equal(log.flowEvent.callCount, 0, 'log.flowEvent was not called')
|
||||
assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called')
|
||||
assert.equal(log.error.callCount, 0, 'log.error was not called')
|
||||
}).finally(() => {
|
||||
log.activityEvent.reset()
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -599,15 +623,14 @@ describe('metrics/events', () => {
|
|||
}
|
||||
return events.emit.call(request, 'account.signed', data)
|
||||
.then(() => {
|
||||
assert.equal(log.amplitudeEvent.callCount, 1, 'log.amplitudeEvent was called once')
|
||||
assert.equal(log.amplitudeEvent.args[0][0].event_properties.service, 'sync', 'log.amplitudeEvent was passed correct service')
|
||||
|
||||
assert.equal(log.activityEvent.callCount, 1, 'log.activityEvent was called once')
|
||||
assert.equal(metricsContext.gather.callCount, 1, 'metricsContext.gather was called once')
|
||||
assert.equal(log.flowEvent.callCount, 1, 'log.flowEvent was called once')
|
||||
assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called')
|
||||
assert.equal(log.error.callCount, 0, 'log.error was not called')
|
||||
}).finally(() => {
|
||||
log.activityEvent.reset()
|
||||
metricsContext.gather.reset()
|
||||
log.flowEvent.reset()
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -638,7 +661,9 @@ describe('metrics/events', () => {
|
|||
event: 'route./account/create.200',
|
||||
flow_id: 'bar',
|
||||
flow_time: 1000,
|
||||
flowBeginTime: time - 1000,
|
||||
flowCompleteSignal: undefined,
|
||||
flowType: undefined,
|
||||
locale: 'en-US',
|
||||
time,
|
||||
userAgent: 'test user-agent'
|
||||
|
@ -650,7 +675,9 @@ describe('metrics/events', () => {
|
|||
event: 'route.performance./account/create',
|
||||
flow_id: 'bar',
|
||||
flow_time: 42,
|
||||
flowBeginTime: time - 1000,
|
||||
flowCompleteSignal: undefined,
|
||||
flowType: undefined,
|
||||
locale: 'en-US',
|
||||
time,
|
||||
userAgent: 'test user-agent'
|
||||
|
@ -660,8 +687,6 @@ describe('metrics/events', () => {
|
|||
assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called')
|
||||
assert.equal(log.error.callCount, 0, 'log.error was not called')
|
||||
}).finally(() => {
|
||||
metricsContext.gather.reset()
|
||||
log.flowEvent.reset()
|
||||
Date.now.restore()
|
||||
})
|
||||
})
|
||||
|
@ -689,7 +714,9 @@ describe('metrics/events', () => {
|
|||
event: 'route./account/login.399',
|
||||
flow_id: 'bar',
|
||||
flow_time: 1000,
|
||||
flowBeginTime: time - 1000,
|
||||
flowCompleteSignal: undefined,
|
||||
flowType: undefined,
|
||||
locale: 'en-US',
|
||||
time,
|
||||
userAgent: 'test user-agent'
|
||||
|
@ -699,8 +726,6 @@ describe('metrics/events', () => {
|
|||
assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called')
|
||||
assert.equal(log.error.callCount, 0, 'log.error was not called')
|
||||
}).finally(() => {
|
||||
metricsContext.gather.reset()
|
||||
log.flowEvent.reset()
|
||||
Date.now.restore()
|
||||
})
|
||||
})
|
||||
|
@ -728,7 +753,9 @@ describe('metrics/events', () => {
|
|||
event: 'route./recovery_email/resend_code.400.999',
|
||||
flow_id: 'bar',
|
||||
flow_time: 1000,
|
||||
flowBeginTime: time - 1000,
|
||||
flowCompleteSignal: undefined,
|
||||
flowType: undefined,
|
||||
locale: 'en-US',
|
||||
time,
|
||||
userAgent: 'test user-agent'
|
||||
|
@ -738,8 +765,6 @@ describe('metrics/events', () => {
|
|||
assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called')
|
||||
assert.equal(log.error.callCount, 0, 'log.error was not called')
|
||||
}).finally(() => {
|
||||
metricsContext.gather.reset()
|
||||
log.flowEvent.reset()
|
||||
Date.now.restore()
|
||||
})
|
||||
})
|
||||
|
@ -767,7 +792,9 @@ describe('metrics/events', () => {
|
|||
event: 'route./account/destroy.400.42',
|
||||
flow_id: 'bar',
|
||||
flow_time: 1000,
|
||||
flowBeginTime: time - 1000,
|
||||
flowCompleteSignal: undefined,
|
||||
flowType: undefined,
|
||||
locale: 'en-US',
|
||||
time,
|
||||
userAgent: 'test user-agent'
|
||||
|
@ -777,8 +804,6 @@ describe('metrics/events', () => {
|
|||
assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called')
|
||||
assert.equal(log.error.callCount, 0, 'log.error was not called')
|
||||
}).finally(() => {
|
||||
metricsContext.gather.reset()
|
||||
log.flowEvent.reset()
|
||||
Date.now.restore()
|
||||
})
|
||||
})
|
||||
|
@ -851,8 +876,6 @@ describe('metrics/events', () => {
|
|||
assert.equal(log.activityEvent.callCount, 0, 'log.activityEvent was not called')
|
||||
assert.equal(metricsContext.clear.callCount, 0, 'metricsContext.clear was not called')
|
||||
assert.equal(log.error.callCount, 0, 'log.error was not called')
|
||||
}).finally(() => {
|
||||
log.flowEvent.reset()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -157,7 +157,7 @@ describe('/account/reset', function () {
|
|||
assert.equal(mockMetricsContext.setFlowCompleteSignal.callCount, 1, 'metricsContext.setFlowCompleteSignal was called once')
|
||||
args = mockMetricsContext.setFlowCompleteSignal.args[0]
|
||||
assert.equal(args.length, 1, 'metricsContext.setFlowCompleteSignal was passed one argument')
|
||||
assert.deepEqual(args[0], 'account.signed', 'argument was event name')
|
||||
assert.equal(args[0], 'account.signed', 'argument was event name')
|
||||
|
||||
assert.equal(mockMetricsContext.stash.callCount, 2, 'metricsContext.stash was called twice')
|
||||
|
||||
|
@ -321,7 +321,9 @@ describe('/account/create', () => {
|
|||
assert.equal(eventData.data.service, 'sync', 'it was for sync')
|
||||
assert.equal(eventData.data.email, TEST_EMAIL, 'it was for the correct email')
|
||||
assert.deepEqual(eventData.data.metricsContext, {
|
||||
flowBeginTime: mockRequest.payload.metricsContext.flowBeginTime,
|
||||
flowCompleteSignal: 'account.signed',
|
||||
flowType: undefined,
|
||||
flow_id: mockRequest.payload.metricsContext.flowId,
|
||||
flow_time: now - mockRequest.payload.metricsContext.flowBeginTime,
|
||||
time: now
|
||||
|
@ -342,7 +344,9 @@ describe('/account/create', () => {
|
|||
assert.equal(args.length, 1, 'log.flowEvent was passed one argument')
|
||||
assert.deepEqual(args[0], {
|
||||
event: 'account.created',
|
||||
flowBeginTime: mockRequest.payload.metricsContext.flowBeginTime,
|
||||
flowCompleteSignal: 'account.signed',
|
||||
flowType: undefined,
|
||||
flow_time: now - mockRequest.payload.metricsContext.flowBeginTime,
|
||||
flow_id: 'F1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF103',
|
||||
locale: 'en-GB',
|
||||
|
@ -376,8 +380,9 @@ describe('/account/create', () => {
|
|||
|
||||
assert.equal(mockMetricsContext.setFlowCompleteSignal.callCount, 1, 'metricsContext.setFlowCompleteSignal was called once')
|
||||
args = mockMetricsContext.setFlowCompleteSignal.args[0]
|
||||
assert.equal(args.length, 1, 'metricsContext.setFlowCompleteSignal was passed one argument')
|
||||
assert.deepEqual(args[0], 'account.signed', 'argument was event name')
|
||||
assert.equal(args.length, 2, 'metricsContext.setFlowCompleteSignal was passed two arguments')
|
||||
assert.equal(args[0], 'account.signed', 'first argument was event name')
|
||||
assert.equal(args[1], 'registration', 'second argument was flow type')
|
||||
|
||||
var securityEvent = mockDB.securityEvent
|
||||
assert.equal(securityEvent.callCount, 1, 'db.securityEvent is called')
|
||||
|
@ -395,6 +400,10 @@ describe('/account/create', () => {
|
|||
assert.equal(args[2].uaOS, 'Mac OS X')
|
||||
assert.equal(args[2].uaOSVersion, '10.10')
|
||||
assert.strictEqual(args[2].uaDeviceType, undefined)
|
||||
assert.equal(args[2].flowId, mockRequest.payload.metricsContext.flowId)
|
||||
assert.equal(args[2].flowBeginTime, mockRequest.payload.metricsContext.flowBeginTime)
|
||||
assert.equal(args[2].service, 'sync')
|
||||
assert.equal(args[2].uid, uid)
|
||||
|
||||
assert.equal(mockLog.error.callCount, 0)
|
||||
}).finally(() => Date.now.restore())
|
||||
|
@ -462,7 +471,6 @@ describe('/account/login', function () {
|
|||
})
|
||||
const mockRequestWithUnblockCode = mocks.mockRequest({
|
||||
log: mockLog,
|
||||
query: {},
|
||||
payload: {
|
||||
authPW: hexString(32),
|
||||
email: TEST_EMAIL,
|
||||
|
@ -471,8 +479,7 @@ describe('/account/login', function () {
|
|||
reason: 'signin',
|
||||
metricsContext: {
|
||||
flowBeginTime: Date.now(),
|
||||
flowId: 'F1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF103',
|
||||
service: 'dcdb5ae7add825d2'
|
||||
flowId: 'F1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF103'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -559,7 +566,9 @@ describe('/account/login', function () {
|
|||
time: now,
|
||||
flow_id: mockRequest.payload.metricsContext.flowId,
|
||||
flow_time: now - mockRequest.payload.metricsContext.flowBeginTime,
|
||||
flowCompleteSignal: 'account.signed'
|
||||
flowBeginTime: mockRequest.payload.metricsContext.flowBeginTime,
|
||||
flowCompleteSignal: 'account.signed',
|
||||
flowType: undefined
|
||||
}, 'metrics context was correct')
|
||||
|
||||
assert.equal(mockLog.activityEvent.callCount, 1, 'log.activityEvent was called once')
|
||||
|
@ -579,7 +588,9 @@ describe('/account/login', function () {
|
|||
event: 'account.login',
|
||||
flow_time: now - mockRequest.payload.metricsContext.flowBeginTime,
|
||||
flow_id: 'F1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF103',
|
||||
flowBeginTime: mockRequest.payload.metricsContext.flowBeginTime,
|
||||
flowCompleteSignal: 'account.signed',
|
||||
flowType: undefined,
|
||||
locale: 'en-US',
|
||||
time: now,
|
||||
uid: uid,
|
||||
|
@ -591,7 +602,9 @@ describe('/account/login', function () {
|
|||
event: 'email.confirmation.sent',
|
||||
flow_time: now - mockRequest.payload.metricsContext.flowBeginTime,
|
||||
flow_id: 'F1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF103',
|
||||
flowBeginTime: mockRequest.payload.metricsContext.flowBeginTime,
|
||||
flowCompleteSignal: 'account.signed',
|
||||
flowType: undefined,
|
||||
locale: 'en-US',
|
||||
time: now,
|
||||
userAgent: 'test user-agent'
|
||||
|
@ -622,8 +635,9 @@ describe('/account/login', function () {
|
|||
|
||||
assert.equal(mockMetricsContext.setFlowCompleteSignal.callCount, 1, 'metricsContext.setFlowCompleteSignal was called once')
|
||||
args = mockMetricsContext.setFlowCompleteSignal.args[0]
|
||||
assert.equal(args.length, 1, 'metricsContext.setFlowCompleteSignal was passed one argument')
|
||||
assert.deepEqual(args[0], 'account.signed', 'argument was event name')
|
||||
assert.equal(args.length, 2, 'metricsContext.setFlowCompleteSignal was passed two arguments')
|
||||
assert.equal(args[0], 'account.signed', 'argument was event name')
|
||||
assert.equal(args[1], 'login', 'second argument was flow type')
|
||||
|
||||
assert.equal(mockMailer.sendVerifyLoginEmail.callCount, 1, 'mailer.sendVerifyLoginEmail was called')
|
||||
args = mockMailer.sendVerifyLoginEmail.args[0]
|
||||
|
@ -635,6 +649,10 @@ describe('/account/login', function () {
|
|||
assert.equal(args[2].uaOS, 'Android')
|
||||
assert.equal(args[2].uaOSVersion, '6')
|
||||
assert.equal(args[2].uaDeviceType, 'mobile')
|
||||
assert.equal(args[2].flowId, mockRequest.payload.metricsContext.flowId)
|
||||
assert.equal(args[2].flowBeginTime, mockRequest.payload.metricsContext.flowBeginTime)
|
||||
assert.equal(args[2].service, 'sync')
|
||||
assert.equal(args[2].uid, uid)
|
||||
|
||||
assert.equal(mockMailer.sendNewDeviceLoginNotification.callCount, 0, 'mailer.sendNewDeviceLoginNotification was not called')
|
||||
assert.ok(! response.verified, 'response indicates account is not verified')
|
||||
|
@ -878,6 +896,10 @@ describe('/account/login', function () {
|
|||
assert.equal(tokenData.tokenVerificationId, null, 'sessionToken was created verified')
|
||||
assert.equal(mockMailer.sendVerifyCode.callCount, 0, 'mailer.sendVerifyLoginEmail was not called')
|
||||
assert.equal(mockMailer.sendNewDeviceLoginNotification.callCount, 1, 'mailer.sendNewDeviceLoginNotification was called')
|
||||
assert.equal(mockMailer.sendNewDeviceLoginNotification.args[0][2].flowId, mockRequest.payload.metricsContext.flowId)
|
||||
assert.equal(mockMailer.sendNewDeviceLoginNotification.args[0][2].flowBeginTime, mockRequest.payload.metricsContext.flowBeginTime)
|
||||
assert.equal(mockMailer.sendNewDeviceLoginNotification.args[0][2].service, 'sync')
|
||||
assert.equal(mockMailer.sendNewDeviceLoginNotification.args[0][2].uid, uid)
|
||||
assert.ok(response.verified, 'response indicates account is verified')
|
||||
})
|
||||
})
|
||||
|
|
|
@ -288,6 +288,7 @@ describe('/recovery_email/resend_code', () => {
|
|||
metricsContext: mockMetricsContext,
|
||||
credentials: {
|
||||
uid: uuid.v4('binary').toString('hex'),
|
||||
deviceId: 'wibble',
|
||||
email: TEST_EMAIL,
|
||||
emailVerified: false,
|
||||
tokenVerified: false,
|
||||
|
@ -298,6 +299,7 @@ describe('/recovery_email/resend_code', () => {
|
|||
},
|
||||
query: {},
|
||||
payload: {
|
||||
service: 'sync',
|
||||
metricsContext: {
|
||||
flowBeginTime: Date.now(),
|
||||
flowId: 'F1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF103'
|
||||
|
@ -316,6 +318,11 @@ describe('/recovery_email/resend_code', () => {
|
|||
assert.equal(args[2].uaOS, 'Mac OS X')
|
||||
assert.equal(args[2].uaOSVersion, '10.10')
|
||||
assert.strictEqual(args[2].uaDeviceType, undefined)
|
||||
assert.equal(args[2].deviceId, mockRequest.auth.credentials.deviceId)
|
||||
assert.equal(args[2].flowId, mockRequest.payload.metricsContext.flowId)
|
||||
assert.equal(args[2].flowBeginTime, mockRequest.payload.metricsContext.flowBeginTime)
|
||||
assert.equal(args[2].service, mockRequest.payload.service)
|
||||
assert.equal(args[2].uid, mockRequest.auth.credentials.uid)
|
||||
})
|
||||
.then(() => {
|
||||
mockMailer.sendVerifyCode.reset()
|
||||
|
@ -329,6 +336,7 @@ describe('/recovery_email/resend_code', () => {
|
|||
metricsContext: mockMetricsContext,
|
||||
credentials: {
|
||||
uid: uuid.v4('binary').toString('hex'),
|
||||
deviceId: uuid.v4('binary').toString('hex'),
|
||||
email: TEST_EMAIL,
|
||||
emailVerified: true,
|
||||
tokenVerified: false,
|
||||
|
@ -338,7 +346,9 @@ describe('/recovery_email/resend_code', () => {
|
|||
uaOSVersion: '6',
|
||||
uaDeviceType: 'tablet'
|
||||
},
|
||||
query: {},
|
||||
query: {
|
||||
service: 'foo'
|
||||
},
|
||||
payload: {
|
||||
email : 'secondEmail@email.com'
|
||||
}
|
||||
|
@ -346,6 +356,12 @@ describe('/recovery_email/resend_code', () => {
|
|||
|
||||
return runTest(route, mockRequest, response => {
|
||||
assert.equal(mockMailer.sendVerifySecondaryEmail.callCount, 1)
|
||||
assert.equal(mockMailer.sendVerifySecondaryEmail.args[0][2].deviceId, mockRequest.auth.credentials.deviceId)
|
||||
assert.equal(mockMailer.sendVerifySecondaryEmail.args[0][2].flowId, mockMetricsContext.flowId)
|
||||
assert.equal(mockMailer.sendVerifySecondaryEmail.args[0][2].flowBeginTime, mockMetricsContext.flowBeginTime)
|
||||
assert.equal(mockMailer.sendVerifySecondaryEmail.args[0][2].service, 'foo')
|
||||
assert.equal(mockMailer.sendVerifySecondaryEmail.args[0][2].uid, mockRequest.auth.credentials.uid)
|
||||
|
||||
assert.equal(mockMailer.sendVerifyCode.callCount, 0)
|
||||
assert.equal(mockMailer.sendVerifyLoginEmail.callCount, 0)
|
||||
const args = mockMailer.sendVerifySecondaryEmail.getCall(0).args
|
||||
|
@ -363,6 +379,7 @@ describe('/recovery_email/resend_code', () => {
|
|||
metricsContext: mockMetricsContext,
|
||||
credentials: {
|
||||
uid: uuid.v4('binary').toString('hex'),
|
||||
deviceId: uuid.v4('binary').toString('hex'),
|
||||
email: TEST_EMAIL,
|
||||
emailVerified: true,
|
||||
tokenVerified: false,
|
||||
|
@ -374,6 +391,7 @@ describe('/recovery_email/resend_code', () => {
|
|||
},
|
||||
query: {},
|
||||
payload: {
|
||||
service: 'foo',
|
||||
metricsContext: {
|
||||
flowBeginTime: Date.now(),
|
||||
flowId: 'F1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF103'
|
||||
|
@ -393,6 +411,11 @@ describe('/recovery_email/resend_code', () => {
|
|||
assert.equal(args[2].uaOS, 'Android')
|
||||
assert.equal(args[2].uaOSVersion, '6')
|
||||
assert.strictEqual(args[2].uaDeviceType, 'tablet')
|
||||
assert.equal(args[2].deviceId, mockRequest.auth.credentials.deviceId)
|
||||
assert.equal(args[2].flowId, mockRequest.payload.metricsContext.flowId)
|
||||
assert.equal(args[2].flowBeginTime, mockRequest.payload.metricsContext.flowBeginTime)
|
||||
assert.equal(args[2].service, mockRequest.payload.service)
|
||||
assert.equal(args[2].uid, mockRequest.auth.credentials.uid)
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -467,6 +490,8 @@ describe('/recovery_email/verify_code', function () {
|
|||
assert.equal(args[2].marketingOptIn, undefined)
|
||||
|
||||
assert.equal(mockMailer.sendPostVerifyEmail.callCount, 1, 'sendPostVerifyEmail was called once')
|
||||
assert.equal(mockMailer.sendPostVerifyEmail.args[0][2].service, mockRequest.payload.service)
|
||||
assert.equal(mockMailer.sendPostVerifyEmail.args[0][2].uid, uid)
|
||||
|
||||
assert.equal(mockLog.activityEvent.callCount, 1, 'activityEvent was called once')
|
||||
args = mockLog.activityEvent.args[0]
|
||||
|
@ -643,6 +668,8 @@ describe('/recovery_email/verify_code', function () {
|
|||
assert.equal(args.length, 3, 'mockMailer.sendPostVerifySecondaryEmail was passed correct arguments')
|
||||
assert.equal(args[1].email, dbData.email, 'correct account primary email was passed')
|
||||
assert.equal(args[2].secondaryEmail, dbData.secondEmail, 'correct secondary email was passed')
|
||||
assert.equal(args[2].service, mockRequest.payload.service)
|
||||
assert.equal(args[2].uid, uid)
|
||||
})
|
||||
.then(function () {
|
||||
mockDB.verifyEmail.reset()
|
||||
|
@ -666,6 +693,7 @@ describe('/recovery_email', () => {
|
|||
mockRequest = mocks.mockRequest({
|
||||
credentials: {
|
||||
uid: uuid.v4('binary').toString('hex'),
|
||||
deviceId: uuid.v4('binary').toString('hex'),
|
||||
email: TEST_EMAIL,
|
||||
emailVerified: true,
|
||||
normalizedEmail: TEST_EMAIL.toLowerCase()
|
||||
|
@ -713,6 +741,8 @@ describe('/recovery_email', () => {
|
|||
assert.ok(response)
|
||||
assert.equal(mockDB.createEmail.callCount, 1, 'call db.createEmail')
|
||||
assert.equal(mockMailer.sendVerifySecondaryEmail.callCount, 1, 'call db.sendVerifySecondaryEmail')
|
||||
assert.equal(mockMailer.sendVerifySecondaryEmail.args[0][2].deviceId, mockRequest.auth.credentials.deviceId)
|
||||
assert.equal(mockMailer.sendVerifySecondaryEmail.args[0][2].uid, mockRequest.auth.credentials.uid)
|
||||
})
|
||||
.then(function () {
|
||||
mockDB.createEmail.reset()
|
||||
|
|
|
@ -122,6 +122,7 @@ describe('/password', () => {
|
|||
assert.equal(mockMailer.sendRecoveryCode.getCall(0).args[2].location.city, 'Mountain View')
|
||||
assert.equal(mockMailer.sendRecoveryCode.getCall(0).args[2].location.country, 'United States')
|
||||
assert.equal(mockMailer.sendRecoveryCode.getCall(0).args[2].timeZone, 'America/Los_Angeles')
|
||||
assert.equal(mockMailer.sendRecoveryCode.getCall(0).args[2].uid, uid)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
@ -177,6 +178,7 @@ describe('/password', () => {
|
|||
return runRoute(passwordRoutes, '/password/forgot/resend_code', mockRequest)
|
||||
.then(function(response) {
|
||||
assert.equal(mockMailer.sendRecoveryCode.callCount, 1, 'mailer.sendRecoveryCode was called once')
|
||||
assert.equal(mockMailer.sendRecoveryCode.args[0][2].uid, uid)
|
||||
|
||||
assert.equal(mockRequest.validateMetricsContext.callCount, 1, 'validateMetricsContext was called')
|
||||
assert.equal(mockLog.flowEvent.callCount, 2, 'log.flowEvent was called twice')
|
||||
|
@ -256,6 +258,9 @@ describe('/password', () => {
|
|||
assert.equal(mockLog.flowEvent.callCount, 2, 'log.flowEvent was called twice')
|
||||
assert.equal(mockLog.flowEvent.args[0][0].event, 'password.forgot.verify_code.start', 'password.forgot.verify_code.start event was logged')
|
||||
assert.equal(mockLog.flowEvent.args[1][0].event, 'password.forgot.verify_code.completed', 'password.forgot.verify_code.completed event was logged')
|
||||
|
||||
assert.equal(mockMailer.sendPasswordResetNotification.callCount, 1, 'mailer.sendPasswordResetNotification was called once')
|
||||
assert.equal(mockMailer.sendPasswordResetNotification.args[0][2].uid, uid, 'mailer.sendPasswordResetNotification was passed uid')
|
||||
})
|
||||
}
|
||||
)
|
||||
|
@ -317,6 +322,7 @@ describe('/password', () => {
|
|||
assert.equal(mockMailer.sendPasswordChangedNotification.getCall(0).args[2].location.city, 'Mountain View')
|
||||
assert.equal(mockMailer.sendPasswordChangedNotification.getCall(0).args[2].location.country, 'United States')
|
||||
assert.equal(mockMailer.sendPasswordChangedNotification.getCall(0).args[2].timeZone, 'America/Los_Angeles')
|
||||
assert.equal(mockMailer.sendPasswordChangedNotification.getCall(0).args[2].uid, uid)
|
||||
|
||||
assert.equal(mockLog.activityEvent.callCount, 1, 'log.activityEvent was called once')
|
||||
var args = mockLog.activityEvent.args[0]
|
||||
|
|
|
@ -12,9 +12,10 @@ var sinon = require('sinon')
|
|||
var P = require('bluebird')
|
||||
|
||||
var mockLog = {
|
||||
trace: function () {},
|
||||
amplitudeEvent () {},
|
||||
trace () {},
|
||||
info: sinon.spy(),
|
||||
error: function () {}
|
||||
error () {}
|
||||
}
|
||||
|
||||
var config = require(`${ROOT_DIR}/config`)
|
||||
|
@ -125,6 +126,7 @@ describe(
|
|||
function (type) {
|
||||
var message = {
|
||||
code: 'abc123',
|
||||
deviceId: 'foo',
|
||||
email: 'a@b.com',
|
||||
locations: [],
|
||||
service: 'sync',
|
||||
|
@ -158,21 +160,17 @@ describe(
|
|||
}
|
||||
)
|
||||
|
||||
it(
|
||||
'Contains flow headers for ' + type,
|
||||
function () {
|
||||
if (type !== 'verificationReminderEmail') {
|
||||
mailer.mailer.sendMail = function (emailConfig) {
|
||||
var flowIdHeader = emailConfig.headers['X-Flow-Id']
|
||||
assert.equal(flowIdHeader, message.flowId)
|
||||
|
||||
var flowBeginHeader = emailConfig.headers['X-Flow-Begin-Time']
|
||||
assert.equal(flowBeginHeader, message.flowBeginTime)
|
||||
}
|
||||
mailer[type](message)
|
||||
}
|
||||
it(`Contains device, flow, service and uid headers for ${type}`, () => {
|
||||
mailer.mailer.sendMail = emailConfig => {
|
||||
const headers = emailConfig.headers
|
||||
assert.equal(headers['X-Device-Id'], message.deviceId, 'device id header is correct')
|
||||
assert.equal(headers['X-Flow-Id'], message.flowId, 'flow id header is correct')
|
||||
assert.equal(headers['X-Flow-Begin-Time'], message.flowBeginTime, 'flow begin time header is correct')
|
||||
assert.equal(headers['X-Service-Id'], message.service, 'service id header is correct')
|
||||
assert.equal(headers['X-Uid'], message.uid, 'uid header is correct')
|
||||
}
|
||||
)
|
||||
mailer[type](message)
|
||||
})
|
||||
|
||||
it(
|
||||
'test privacy link is in email template output for ' + type,
|
||||
|
|
|
@ -71,6 +71,7 @@ const DB_METHOD_NAMES = [
|
|||
|
||||
const LOG_METHOD_NAMES = [
|
||||
'activityEvent',
|
||||
'amplitudeEvent',
|
||||
'begin',
|
||||
'error',
|
||||
'flowEvent',
|
||||
|
@ -415,7 +416,9 @@ function mockMetricsContext (methods) {
|
|||
time: time,
|
||||
flow_id: this.payload.metricsContext.flowId,
|
||||
flow_time: time - this.payload.metricsContext.flowBeginTime,
|
||||
flowCompleteSignal: this.payload.metricsContext.flowCompleteSignal
|
||||
flowBeginTime: this.payload.metricsContext.flowBeginTime,
|
||||
flowCompleteSignal: this.payload.metricsContext.flowCompleteSignal,
|
||||
flowType: this.payload.metricsContext.flowType
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -484,7 +487,7 @@ function mockRequest (data) {
|
|||
received: data.received || Date.now() - 1
|
||||
},
|
||||
path: data.path,
|
||||
payload: data.payload,
|
||||
payload: data.payload || {},
|
||||
query: data.query || {},
|
||||
setMetricsFlowCompleteSignal: metricsContext.setFlowCompleteSignal,
|
||||
stashMetricsContext: metricsContext.stash,
|
||||
|
|
Загрузка…
Ссылка в новой задаче