fix(server): remove redundant metrics context fields

This commit is contained in:
Phil Booth 2016-12-01 19:48:43 +00:00
Родитель ed9ec79565
Коммит f027f0bda5
7 изменённых файлов: 37 добавлений и 463 удалений

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

@ -85,10 +85,7 @@ There are also further flow events
that do not correspond to an activity event.
In addition to the data that is present on activity events,
these events also have a metrics context
that includes the `utm_*` parameters and a `flowId`.
The flow events respect the `dnt` "Do not track"
header and do not send `utm_*` parameters
if that header is set to `1`.
that includes `flowId` and `flowBeginTime` properties.
#### Flow event structure
@ -100,15 +97,6 @@ containg the following fields:
* `flow_time`
* `time`
* `userAgent`
* `context` (optional)
* `entrypoint` (optional)
* `migration` (optional)
* `service` (optional)
* `utm_campaign` (optional)
* `utm_content` (optional)
* `utm_medium` (optional)
* `utm_source` (optional)
* `utm_term` (optional)
##### Example event
@ -118,11 +106,7 @@ containg the following fields:
"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:50.0) Gecko/20100101 Firefox/50.0",
"time": 1471258615891,
"flow_id": "1386176a76f4359d30aea806400ebbf8af1fb0040891ff32cd0d5cc5038f6d58",
"flow_time": 11948,
"uid": "d03249d2af7d4447be3debc674110978",
"context": "fx_desktop_v3",
"entrypoint": "menupanel",
"service": "sync"
"flow_time": 11948
}
```

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

@ -18,16 +18,7 @@ const FLOW_ID_LENGTH = 64
const SCHEMA = isA.object({
flowId: isA.string().length(64).regex(HEX).optional(),
flowBeginTime: isA.number().integer().positive().optional(),
context: isA.string().optional(),
entrypoint: isA.string().optional(),
migration: isA.string().optional(),
service: isA.string().optional(),
utmCampaign: isA.string().optional(),
utmContent: isA.string().optional(),
utmMedium: isA.string().optional(),
utmSource: isA.string().optional(),
utmTerm: isA.string().optional()
flowBeginTime: isA.number().integer().positive().optional()
}).and('flowId', 'flowBeginTime').optional()
const NOP = function () {
@ -40,14 +31,6 @@ const NULL_MEMCACHED = {
setAsync: NOP
}
const VALID_PROPERTIES = [
{ key: 'context', pattern: /^[0-9a-z_-]+$/ },
{ key: 'entrypoint', pattern: /^[\w\.-]+$/ },
{ key: 'flowId', pattern: /^[0-9a-f]{64}$/ },
{ key: 'migration', pattern: /^(sync11|amo)$/ },
{ key: 'service', pattern: /^(sync|content-server|[0-9a-f]{16})$/ }
]
module.exports = function (log, config) {
let _memcached
@ -122,7 +105,6 @@ module.exports = function (log, config) {
*/
function gather (data) {
const metadata = this.payload && this.payload.metricsContext
const doNotTrack = this.headers && this.headers.dnt === '1'
let token
return P.resolve()
@ -141,18 +123,6 @@ module.exports = function (log, config) {
data.flow_id = metadata.flowId
data.flow_time = calculateFlowTime(data.time, metadata.flowBeginTime)
data.flowCompleteSignal = metadata.flowCompleteSignal
data.context = metadata.context
data.entrypoint = metadata.entrypoint
data.migration = metadata.migration
data.service = metadata.service
if (! doNotTrack) {
data.utm_campaign = metadata.utmCampaign
data.utm_content = metadata.utmContent
data.utm_medium = metadata.utmMedium
data.utm_source = metadata.utmSource
data.utm_term = metadata.utmTerm
}
}
})
.catch(err => log.error({
@ -225,10 +195,6 @@ module.exports = function (log, config) {
return logInvalidContext(this, 'expired flowBeginTime')
}
if (! isValidData(metadata)) {
return logInvalidContext(this, 'invalid data')
}
// The first half of the id is random bytes, the second half is a HMAC of
// additional contextual information about the request. It's a simple way
// to check that the metrics came from the right place, without having to
@ -314,16 +280,5 @@ function calculateFlowTime (time, flowBeginTime) {
return time - flowBeginTime
}
function isValidData (data) {
return VALID_PROPERTIES.every(p => {
const property = data[p.key]
if (property) {
return p.pattern.test(property)
}
return true
})
}
module.exports.schema = SCHEMA

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

@ -102,13 +102,6 @@ module.exports = config => {
ClientApi.prototype.accountCreate = function (email, authPW, options) {
options = options || {}
// Default to desktop client context
if (!options.metricsContext) {
options.metricsContext = {
context: 'fx_desktop_v3'
}
}
var url = this.baseURL + '/account/create' + getQueryString(options)
return this.doRequest(
'POST',
@ -136,13 +129,6 @@ module.exports = config => {
opts = { keys: true }
}
// Default to desktop client context
if (!opts.metricsContext) {
opts.metricsContext = {
context: 'fx_desktop_v3'
}
}
return this.doRequest(
'POST',
this.baseURL + '/account/login' + getQueryString(opts),

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

@ -251,9 +251,7 @@ describe('/recovery_email/resend_code', () => {
payload: {
metricsContext: {
flowBeginTime: Date.now(),
flowId: 'F1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF103',
entrypoint: 'preferences',
utmContent: 'some-content-string'
flowId: 'F1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF103'
}
}
})
@ -279,9 +277,7 @@ describe('/recovery_email/resend_code', () => {
payload: {
metricsContext: {
flowBeginTime: Date.now(),
flowId: 'F1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF103',
entrypoint: 'preferences',
utmContent: 'some-content-string'
flowId: 'F1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF103'
}
}
})
@ -730,9 +726,7 @@ describe('/account/create', function () {
service: 'sync',
metricsContext: {
flowBeginTime: Date.now(),
flowId: 'F1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF103',
entrypoint: 'preferences',
utmContent: 'some-content-string'
flowId: 'F1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF103'
}
},
query: {
@ -880,11 +874,8 @@ describe('/account/login', function () {
service: 'sync',
reason: 'signin',
metricsContext: {
context: 'fx_desktop_v3',
flowBeginTime: Date.now(),
flowId: 'F1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF103',
entrypoint: 'preferences',
utmContent: 'some-content-string'
flowId: 'F1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF103'
}
},
query: {
@ -1774,11 +1765,8 @@ describe('/account/login/send_unblock_code', function () {
payload: {
email: TEST_EMAIL,
metricsContext: {
context: 'fx_desktop_v3',
flowBeginTime: Date.now(),
flowId: 'F1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF103',
entrypoint: 'preferences',
utmContent: 'some-content-string'
flowId: 'F1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF103'
}
}
})

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

@ -116,11 +116,8 @@ const mockRequest = mocks.mockRequest({
service: 'sync',
reason: 'signin',
metricsContext: {
context: 'fx_desktop_v3',
flowBeginTime: Date.now(),
flowId: 'F1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF103',
entrypoint: 'preferences',
utmContent: 'some-content-string'
flowId: 'F1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF1031DF103'
}
},
query: {

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

@ -205,21 +205,12 @@ describe('metricsConext', () => {
}, {}).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, 13, 'result has 13 properties')
assert.equal(Object.keys(result).length, 4, 'result has 4 properties')
assert.ok(result.time > time, 'result.time seems 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.flowCompleteSignal, 'mock flow complete signal', 'result.flowCompleteSignal is correct')
assert.equal(result.context, 'mock context', 'result.context is correct')
assert.equal(result.entrypoint, 'mock entry point', 'result.entrypoint is correct')
assert.equal(result.migration, 'mock migration', 'result.migration is correct')
assert.equal(result.service, 'mock service', 'result.service is correct')
assert.equal(result.utm_campaign, 'mock utm_campaign', 'result.utm_campaign is correct')
assert.equal(result.utm_content, 'mock utm_content', 'result.utm_content is correct')
assert.equal(result.utm_medium, 'mock utm_medium', 'result.utm_medium is correct')
assert.equal(result.utm_source, 'mock utm_source', 'result.utm_source is correct')
assert.equal(result.utm_term, 'mock utm_term', 'result.utm_term is correct')
assert.equal(Memcached.prototype.getAsync.callCount, 0, 'memcached.getAsync was not called')
assert.equal(log.error.callCount, 0, 'log.error was not called')
@ -250,45 +241,6 @@ describe('metricsConext', () => {
}
)
it(
'metricsContext.gather with DNT header',
() => {
var time = Date.now() - 1
metricsContext.gather.call({
headers: {
dnt: '1'
},
payload: {
metricsContext: {
flowId: 'mock flow id',
flowBeginTime: time,
flowCompleteSignal: 'mock flow complete signal',
context: 'mock context',
entrypoint: 'mock entry point',
migration: 'mock migration',
service: 'mock service',
utmCampaign: 'mock utm_campaign',
utmContent: 'mock utm_content',
utmMedium: 'mock utm_medium',
utmSource: 'mock utm_source',
utmTerm: 'mock utm_term',
ignore: 'mock ignorable property'
}
}
}, {}).then(function (result) {
assert.equal(Object.keys(result).length, 8, 'result has 8 properties')
assert.equal(result.utm_campaign, undefined, 'result.utm_campaign is undefined')
assert.equal(result.utm_content, undefined, 'result.utm_content is undefined')
assert.equal(result.utm_medium, undefined, 'result.utm_medium is undefined')
assert.equal(result.utm_source, undefined, 'result.utm_source is undefined')
assert.equal(result.utm_term, undefined, 'result.utm_term is undefined')
assert.equal(log.error.callCount, 0, 'log.error was not called')
})
}
)
it(
'metricsContext.gather with token',
() => {
@ -302,17 +254,7 @@ describe('metricsConext', () => {
return P.resolve({
flowId: 'flowId',
flowBeginTime: time,
flowCompleteSignal: 'flowCompleteSignal',
context: 'context',
entrypoint: 'entrypoint',
migration: 'migration',
service: 'service',
utmCampaign: 'utm_campaign',
utmContent: 'utm_content',
utmMedium: 'utm_medium',
utmSource: 'utm_source',
utmTerm: 'utm_term',
ignore: 'ignore me'
flowCompleteSignal: 'flowCompleteSignal'
})
})
metricsContext.gather.call({
@ -329,21 +271,12 @@ describe('metricsConext', () => {
assert.equal(typeof result, 'object', 'result is object')
assert.notEqual(result, null, 'result is not null')
assert.equal(Object.keys(result).length, 13, 'result has 13 properties')
assert.equal(Object.keys(result).length, 4, 'result has 4 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')
assert.ok(result.flow_time < time, 'result.flow_time is less than the current time')
assert.equal(result.flowCompleteSignal, 'flowCompleteSignal', 'result.flowCompleteSignal is correct')
assert.equal(result.context, 'context', 'result.context is correct')
assert.equal(result.entrypoint, 'entrypoint', 'result.entry point is correct')
assert.equal(result.migration, 'migration', 'result.migration is correct')
assert.equal(result.service, 'service', 'result.service is correct')
assert.equal(result.utm_campaign, 'utm_campaign', 'result.utm_campaign is correct')
assert.equal(result.utm_content, 'utm_content', 'result.utm_content is correct')
assert.equal(result.utm_medium, 'utm_medium', 'result.utm_medium is correct')
assert.equal(result.utm_source, 'utm_source', 'result.utm_source is correct')
assert.equal(result.utm_term, 'utm_term', 'result.utm_term is correct')
assert.equal(log.error.callCount, 0, 'log.error was not called')
@ -380,7 +313,7 @@ describe('metricsConext', () => {
assert.equal(typeof result, 'object', 'result is object')
assert.notEqual(result, null, 'result is not null')
assert.equal(Object.keys(result).length, 13, 'result has 13 properties')
assert.equal(Object.keys(result).length, 4, 'result has 4 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')
@ -730,12 +663,8 @@ describe('metricsConext', () => {
},
payload: {
metricsContext: {
context: 'fx_desktop_v3',
entrypoint: 'menupanel',
flowId,
flowBeginTime,
migration: 'sync11',
service: 'sync'
flowBeginTime
}
}
}
@ -809,11 +738,7 @@ describe('metricsConext', () => {
},
payload: {
metricsContext: {
context: 'fx_desktop_v3',
entrypoint: 'menupanel',
flowBeginTime: Date.now() - 1,
migration: 'sync11',
service: 'sync'
flowBeginTime: Date.now() - 1
}
}
}
@ -850,11 +775,7 @@ describe('metricsConext', () => {
},
payload: {
metricsContext: {
context: 'fx_desktop_v3',
entrypoint: 'menupanel',
flowId: 'f1031df1031df1031df1031df1031df1031df1031df1031df1031df1031df103',
migration: 'sync11',
service: 'sync'
flowId: 'f1031df1031df1031df1031df1031df1031df1031df1031df1031df1031df103'
}
}
}
@ -891,12 +812,8 @@ describe('metricsConext', () => {
},
payload: {
metricsContext: {
context: 'fx_desktop_v3',
entrypoint: 'menupanel',
flowId: 'f1031df1031df1031df1031df1031df1031df1031df1031df1031df1031df103',
flowBeginTime: Date.now() - mockConfig.metrics.flow_id_expiry - 1,
migration: 'sync11',
service: 'sync'
flowBeginTime: Date.now() - mockConfig.metrics.flow_id_expiry - 1
}
}
}
@ -917,245 +834,6 @@ describe('metricsConext', () => {
}
)
it(
'metricsContext.validate with invalid context',
() => {
const flowBeginTime = 1451566800000
const flowId = '1234567890abcdef1234567890abcdef6a7c0469a1e3d6dfa7d9bed7ae209672'
sinon.stub(Date, 'now', function() {
return flowBeginTime + 59999
})
const mockLog = mocks.spyLog()
const mockConfig = {
metrics: {
flow_id_expiry: 60000,
flow_id_key: 'S3CR37'
}
}
const mockRequest = {
headers: {
'user-agent': 'test-agent'
},
payload: {
metricsContext: {
context: '!',
entrypoint: 'menupanel',
flowId,
flowBeginTime,
migration: 'sync11',
service: 'sync'
}
}
}
const metricsContext = require('../../lib/metrics/context')(mockLog, mockConfig)
const result = metricsContext.validate.call(mockRequest)
assert.strictEqual(result, false, 'result was false')
assert.strictEqual(mockRequest.payload.metricsContext.flowId, undefined, 'invalid flow data was removed')
assert.equal(mockLog.info.callCount, 0, 'log.info was not called')
assert.equal(mockLog.warn.callCount, 1, 'log.warn was called once')
assert.equal(mockLog.warn.args[0].length, 1, 'log.warn was passed one argument')
assert.deepEqual(mockLog.warn.args[0][0], {
op: 'metrics.context.validate',
valid: false,
reason: 'invalid data',
agent: 'test-agent'
}, 'log.warn was passed correct argument')
Date.now.restore()
}
)
it(
'metricsContext.validate with invalid entrypoint',
() => {
const flowBeginTime = 1451566800000
const flowId = '1234567890abcdef1234567890abcdef6a7c0469a1e3d6dfa7d9bed7ae209672'
sinon.stub(Date, 'now', function() {
return flowBeginTime + 59999
})
const mockLog = mocks.spyLog()
const mockConfig = {
metrics: {
flow_id_expiry: 60000,
flow_id_key: 'S3CR37'
}
}
const mockRequest = {
headers: {
'user-agent': 'test-agent'
},
payload: {
metricsContext: {
context: 'fx_desktop_v3',
entrypoint: '!',
flowId,
flowBeginTime,
migration: 'sync11',
service: 'sync'
}
}
}
const metricsContext = require('../../lib/metrics/context')(mockLog, mockConfig)
const result = metricsContext.validate.call(mockRequest)
assert.strictEqual(result, false, 'result was false')
assert.strictEqual(mockRequest.payload.metricsContext.flowId, undefined, 'invalid flow data was removed')
assert.equal(mockLog.info.callCount, 0, 'log.info was not called')
assert.equal(mockLog.warn.callCount, 1, 'log.warn was called once')
assert.equal(mockLog.warn.args[0].length, 1, 'log.warn was passed one argument')
assert.deepEqual(mockLog.warn.args[0][0], {
op: 'metrics.context.validate',
valid: false,
reason: 'invalid data',
agent: 'test-agent'
}, 'log.warn was passed correct argument')
Date.now.restore()
}
)
it(
'metricsContext.validate with invalid migration',
() => {
const flowBeginTime = 1451566800000
const flowId = '1234567890abcdef1234567890abcdef6a7c0469a1e3d6dfa7d9bed7ae209672'
sinon.stub(Date, 'now', function() {
return flowBeginTime + 59999
})
const mockLog = mocks.spyLog()
const mockConfig = {
metrics: {
flow_id_expiry: 60000,
flow_id_key: 'S3CR37'
}
}
const mockRequest = {
headers: {
'user-agent': 'test-agent'
},
payload: {
metricsContext: {
context: 'fx_desktop_v3',
entrypoint: 'menupanel',
flowId,
flowBeginTime,
migration: 'sync111',
service: 'sync'
}
}
}
const metricsContext = require('../../lib/metrics/context')(mockLog, mockConfig)
const result = metricsContext.validate.call(mockRequest)
assert.strictEqual(result, false, 'result was false')
assert.strictEqual(mockRequest.payload.metricsContext.flowId, undefined, 'invalid flow data was removed')
assert.equal(mockLog.info.callCount, 0, 'log.info was not called')
assert.equal(mockLog.warn.callCount, 1, 'log.warn was called once')
assert.equal(mockLog.warn.args[0].length, 1, 'log.warn was passed one argument')
assert.deepEqual(mockLog.warn.args[0][0], {
op: 'metrics.context.validate',
valid: false,
reason: 'invalid data',
agent: 'test-agent'
}, 'log.warn was passed correct argument')
Date.now.restore()
}
)
it(
'metricsContext.validate with invalid service',
() => {
const flowBeginTime = 1451566800000
const flowId = '1234567890abcdef1234567890abcdef6a7c0469a1e3d6dfa7d9bed7ae209672'
sinon.stub(Date, 'now', function() {
return flowBeginTime + 59999
})
const mockLog = mocks.spyLog()
const mockConfig = {
metrics: {
flow_id_expiry: 60000,
flow_id_key: 'S3CR37'
}
}
const mockRequest = {
headers: {
'user-agent': 'test-agent'
},
payload: {
metricsContext: {
context: 'fx_desktop_v3',
entrypoint: 'menupanel',
flowId,
flowBeginTime,
migration: 'sync11',
service: 'foo'
}
}
}
const metricsContext = require('../../lib/metrics/context')(mockLog, mockConfig)
const result = metricsContext.validate.call(mockRequest)
assert.strictEqual(result, false, 'result was false')
assert.strictEqual(mockRequest.payload.metricsContext.flowId, undefined, 'invalid flow data was removed')
assert.equal(mockLog.info.callCount, 0, 'log.info was not called')
assert.equal(mockLog.warn.callCount, 1, 'log.warn was called once')
assert.equal(mockLog.warn.args[0].length, 1, 'log.warn was passed one argument')
assert.deepEqual(mockLog.warn.args[0][0], {
op: 'metrics.context.validate',
valid: false,
reason: 'invalid data',
agent: 'test-agent'
}, 'log.warn was passed correct argument')
Date.now.restore()
}
)
it(
'metricsContext.validate without optional data',
() => {
const flowBeginTime = 1451566800000
const flowId = '1234567890abcdef1234567890abcdef6a7c0469a1e3d6dfa7d9bed7ae209672'
sinon.stub(Date, 'now', function() {
return flowBeginTime + 59999
})
const mockLog = mocks.spyLog()
const mockConfig = {
metrics: {
flow_id_expiry: 60000,
flow_id_key: 'S3CR37'
}
}
const mockRequest = {
headers: {
'user-agent': 'test-agent'
},
payload: {
metricsContext: {
flowId,
flowBeginTime
}
}
}
const metricsContext = require('../../lib/metrics/context')(mockLog, mockConfig)
const result = metricsContext.validate.call(mockRequest)
assert.strictEqual(result, true, 'result was true')
assert.equal(mockRequest.payload.metricsContext.flowId, '1234567890abcdef1234567890abcdef6a7c0469a1e3d6dfa7d9bed7ae209672', 'valid flow data was not removed')
assert.equal(mockLog.warn.callCount, 0, 'log.warn was not called')
assert.equal(mockLog.info.callCount, 1, 'log.info was called')
Date.now.restore()
}
)
it(
'metricsContext.validate with an invalid flow signature',
() => {
@ -1172,12 +850,8 @@ describe('metricsConext', () => {
},
payload: {
metricsContext: {
context: 'fx_desktop_v3',
entrypoint: 'menupanel',
flowId: 'f1031df1031df1031df1031df1031df1031df1031df1031df1031df1031df103',
flowBeginTime: Date.now() - 1,
migration: 'sync11',
service: 'sync'
flowBeginTime: Date.now() - 1
}
}
}
@ -1217,12 +891,8 @@ describe('metricsConext', () => {
},
payload: {
metricsContext: {
context: 'fx_desktop_v3',
entrypoint: 'menupanel',
flowId: expectedSalt + expectedHmac,
flowBeginTime: expectedTime,
migration: 'sync11',
service: 'sync'
flowBeginTime: expectedTime
}
}
}
@ -1269,12 +939,8 @@ describe('metricsConext', () => {
},
payload: {
metricsContext: {
context: 'fx_desktop_v3',
entrypoint: 'menupanel',
flowId: expectedSalt + expectedHmac,
flowBeginTime: expectedTime - 1,
migration: 'sync11',
service: 'sync'
flowBeginTime: expectedTime - 1
}
}
}
@ -1321,12 +987,8 @@ describe('metricsConext', () => {
},
payload: {
metricsContext: {
context: 'fx_desktop_v3',
entrypoint: 'menupanel',
flowId: expectedSalt + expectedHmac,
flowBeginTime: expectedTime,
migration: 'sync11',
service: 'sync'
flowBeginTime: expectedTime
}
}
}

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

@ -412,16 +412,27 @@ describe('remote account create', function() {
)
it(
'account creation works with minimal metricsContext metadata',
'account creation fails with old metricsContext fields',
() => {
var email = server.uniqueEmail()
return Client.create(config.publicUrl, email, 'foo', {
metricsContext: {
flowId: 'deadbeefbaadf00ddeadbeefbaadf00ddeadbeefbaadf00ddeadbeefbaadf00d',
flowBeginTime: 1
flowBeginTime: 1,
context: 'foo',
entrypoint: 'bar',
migration: 'baz',
service: 'qux',
utmCampaign: 'wibble',
utmContent: 'blurgh',
utmMedium: 'blee',
utmSource: 'fnarr',
utmTerm: 'frang'
}
}).then(function (client) {
assert.ok(client, 'created account')
}).then(function () {
assert(false, 'account creation should have failed')
}, function (err) {
assert.ok(err, 'account creation failed')
})
}
)
@ -467,16 +478,7 @@ describe('remote account create', function() {
return Client.create(config.publicUrl, email, 'foo', {
metricsContext: {
flowId: 'deadbeefbaadf00ddeadbeefbaadf00ddeadbeefbaadf00ddeadbeefbaadf00d',
flowBeginTime: 1,
context: 'foo',
entrypoint: 'bar',
migration: 'baz',
service: 'qux',
utmCampaign: 'wibble',
utmContent: 'blurgh',
utmMedium: 'blee',
utmSource: 'fnarr',
utmTerm: 'frang'
flowBeginTime: 1
}
}).then(function (client) {
assert.ok(client, 'created account')