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:
Phil Booth 2017-08-28 10:53:25 +01:00 коммит произвёл GitHub
Родитель f136268dd9
Коммит 5800418902
21 изменённых файлов: 1707 добавлений и 146 удалений

Просмотреть файл

@ -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

209
lib/metrics/amplitude.js Normal file
Просмотреть файл

@ -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)
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,