feat(api): add an endpoint for sending SMS messages
https://github.com/mozilla/fxa-auth-server/pull/1648 r=vbudhram
This commit is contained in:
Родитель
22734a13bd
Коммит
d35d4420ce
|
@ -64,7 +64,7 @@ function main() {
|
|||
|
||||
var Server = require('../lib/server')
|
||||
var server = null
|
||||
var mailer = null
|
||||
var senders = null
|
||||
var statsInterval = null
|
||||
var database = null
|
||||
var customs = null
|
||||
|
@ -74,10 +74,10 @@ function main() {
|
|||
log.stat(Password.stat())
|
||||
}
|
||||
|
||||
require('../lib/mailer')(config, log)
|
||||
require('../lib/senders')(config, log)
|
||||
.done(
|
||||
function(m) {
|
||||
mailer = m
|
||||
function(result) {
|
||||
senders = result
|
||||
|
||||
var DB = require('../lib/db')(
|
||||
config,
|
||||
|
@ -102,7 +102,8 @@ function main() {
|
|||
serverPublicKeys,
|
||||
signer,
|
||||
db,
|
||||
mailer,
|
||||
senders.email,
|
||||
senders.sms,
|
||||
Password,
|
||||
config,
|
||||
customs
|
||||
|
@ -148,7 +149,7 @@ function main() {
|
|||
function () {
|
||||
customs.close()
|
||||
try {
|
||||
mailer.stop()
|
||||
senders.email.stop()
|
||||
} catch (e) {
|
||||
// XXX: simplesmtp module may quit early and set socket to `false`, stopping it may fail
|
||||
log.warn({ op: 'shutdown', message: 'Mailer client already disconnected' })
|
||||
|
|
|
@ -567,6 +567,32 @@ var conf = convict({
|
|||
format: Array,
|
||||
env: 'HPKP_PIN_SHA256'
|
||||
}
|
||||
},
|
||||
sms: {
|
||||
enabled: {
|
||||
doc: 'Indicates whether POST /sms is enabled',
|
||||
default: true,
|
||||
format: Boolean,
|
||||
env: 'SMS_ENABLED'
|
||||
},
|
||||
apiKey: {
|
||||
doc: 'API key for the SMS service',
|
||||
default: 'YOU MUST CHANGE ME',
|
||||
format: String,
|
||||
env: 'SMS_API_KEY'
|
||||
},
|
||||
apiSecret: {
|
||||
doc: 'API secret for the SMS service',
|
||||
default: 'YOU MUST CHANGE ME',
|
||||
format: String,
|
||||
env: 'SMS_API_SECRET'
|
||||
},
|
||||
installFirefoxLink: {
|
||||
doc: 'Link for the installFirefox SMS template',
|
||||
format: 'url',
|
||||
default: 'https://mzl.la/1HOd4ec',
|
||||
env: 'SMS_INSTALL_FIREFOX_LINK'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
[
|
||||
[ "CA", "16474909977" ],
|
||||
[ "GB", "Firefox" ],
|
||||
[ "US", "15036789977" ]
|
||||
]
|
|
@ -56,6 +56,7 @@ in a sign-in or sign-up flow:
|
|||
|`email.verification.resent`|A sign-up verification email has been re-sent to a user.|
|
||||
|`email.verify_code.clicked`|A user has clicked on the link in a confirmation/verification email.|
|
||||
|`email.${templateName}.delivered`|An email was delivered to a user.|
|
||||
|`sms.${templateName}.sent`|An SMS message has been sent to a user's phone.|
|
||||
|`account.confirmed`|Sign-in to an existing account has been confirmed via email.|
|
||||
|`account.reminder`|A new account has been verified via a reminder email.|
|
||||
|`account.verified`|A new account has been verified via email.|
|
||||
|
|
49
lib/error.js
49
lib/error.js
|
@ -32,6 +32,10 @@ var ERRNO = {
|
|||
ACCOUNT_RESET: 126,
|
||||
INVALID_UNBLOCK_CODE: 127,
|
||||
// MISSING_TOKEN: 128,
|
||||
INVALID_PHONE_NUMBER: 129,
|
||||
INVALID_REGION: 130,
|
||||
INVALID_MESSAGE_ID: 131,
|
||||
MESSAGE_REJECTED: 132,
|
||||
SERVER_BUSY: 201,
|
||||
FEATURE_NOT_ENABLED: 202,
|
||||
UNEXPECTED_ERROR: 999
|
||||
|
@ -476,5 +480,50 @@ AppError.invalidUnblockCode = function () {
|
|||
})
|
||||
}
|
||||
|
||||
AppError.invalidPhoneNumber = () => {
|
||||
return new AppError({
|
||||
code: 400,
|
||||
error: 'Bad Request',
|
||||
errno: ERRNO.INVALID_PHONE_NUMBER,
|
||||
message: 'Invalid phone number'
|
||||
})
|
||||
}
|
||||
|
||||
AppError.invalidRegion = region => {
|
||||
return new AppError({
|
||||
code: 400,
|
||||
error: 'Bad Request',
|
||||
errno: ERRNO.INVALID_REGION,
|
||||
message: 'Invalid region'
|
||||
}, {
|
||||
region
|
||||
})
|
||||
}
|
||||
|
||||
AppError.invalidMessageId = () => {
|
||||
return new AppError({
|
||||
code: 400,
|
||||
error: 'Bad Request',
|
||||
errno: ERRNO.INVALID_MESSAGE_ID,
|
||||
message: 'Invalid message id'
|
||||
})
|
||||
}
|
||||
|
||||
AppError.messageRejected = (reason, reasonCode) => {
|
||||
return new AppError({
|
||||
code: 500,
|
||||
error: 'Bad Request',
|
||||
errno: ERRNO.MESSAGE_REJECTED,
|
||||
message: 'Message rejected'
|
||||
}, {
|
||||
reason,
|
||||
reasonCode
|
||||
})
|
||||
}
|
||||
|
||||
AppError.unexpectedError = () => {
|
||||
return new AppError({})
|
||||
}
|
||||
|
||||
module.exports = AppError
|
||||
module.exports.ERRNO = ERRNO
|
||||
|
|
|
@ -54,7 +54,8 @@ const FLOW_EVENT_ROUTES = new Set([
|
|||
'/account/login/send_unblock_code',
|
||||
'/account/reset',
|
||||
'/recovery_email/resend_code',
|
||||
'/recovery_email/verify_code'
|
||||
'/recovery_email/verify_code',
|
||||
'/sms'
|
||||
])
|
||||
|
||||
const PATH_PREFIX = /^\/v1/
|
||||
|
|
|
@ -17,6 +17,7 @@ module.exports = function (
|
|||
signer,
|
||||
db,
|
||||
mailer,
|
||||
smsImpl,
|
||||
Password,
|
||||
config,
|
||||
customs
|
||||
|
@ -59,6 +60,7 @@ module.exports = function (
|
|||
)
|
||||
const session = require('./session')(log, isA, error, db)
|
||||
const sign = require('./sign')(log, P, isA, error, signer, db, config.domain, devices)
|
||||
const smsRoute = require('./sms')(log, isA, error, config, customs, smsImpl)
|
||||
const util = require('./util')(
|
||||
log,
|
||||
random,
|
||||
|
@ -75,6 +77,7 @@ module.exports = function (
|
|||
password,
|
||||
session,
|
||||
sign,
|
||||
smsRoute,
|
||||
util
|
||||
)
|
||||
v1Routes.forEach(r => { r.path = basePath + '/v1' + r.path })
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
/* 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/. */
|
||||
|
||||
'use strict'
|
||||
|
||||
const PhoneNumberUtil = require('google-libphonenumber').PhoneNumberUtil
|
||||
const validators = require('./validators')
|
||||
|
||||
const METRICS_CONTEXT_SCHEMA = require('../metrics/context').schema
|
||||
const SENDER_IDS = new Map(require('../../config/sms-sender-ids.json'))
|
||||
|
||||
module.exports = (log, isA, error, config, customs, sms) => {
|
||||
if (! config.sms.enabled) {
|
||||
return []
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
method: 'POST',
|
||||
path: '/sms',
|
||||
config: {
|
||||
auth: {
|
||||
strategy: 'sessionToken'
|
||||
},
|
||||
validate: {
|
||||
payload: {
|
||||
phoneNumber: isA.string().regex(validators.E164_NUMBER).required(),
|
||||
messageId: isA.number().positive().required(),
|
||||
metricsContext: METRICS_CONTEXT_SCHEMA
|
||||
}
|
||||
}
|
||||
},
|
||||
handler (request, reply) {
|
||||
log.begin('sms.send', request)
|
||||
request.validateMetricsContext()
|
||||
|
||||
const sessionToken = request.auth.credentials
|
||||
const phoneNumber = request.payload.phoneNumber
|
||||
const messageId = request.payload.messageId
|
||||
const acceptLanguage = request.app.acceptLanguage
|
||||
|
||||
let phoneNumberUtil, parsedPhoneNumber
|
||||
|
||||
customs.check(request, sessionToken.email, 'connectDeviceSms')
|
||||
.then(parsePhoneNumber)
|
||||
.then(validatePhoneNumber)
|
||||
.then(getRegionSpecificSenderId)
|
||||
.then(sendMessage)
|
||||
.then(logSuccess)
|
||||
.then(createResponse)
|
||||
.then(reply, reply)
|
||||
|
||||
function parsePhoneNumber () {
|
||||
phoneNumberUtil = PhoneNumberUtil.getInstance()
|
||||
parsedPhoneNumber = phoneNumberUtil.parse(phoneNumber)
|
||||
}
|
||||
|
||||
function validatePhoneNumber () {
|
||||
if (! phoneNumberUtil.isValidNumber(parsedPhoneNumber)) {
|
||||
throw error.invalidPhoneNumber()
|
||||
}
|
||||
}
|
||||
|
||||
function getRegionSpecificSenderId () {
|
||||
const region = phoneNumberUtil.getRegionCodeForNumber(parsedPhoneNumber)
|
||||
const senderId = SENDER_IDS.get(region)
|
||||
|
||||
if (! senderId) {
|
||||
throw error.invalidRegion(region)
|
||||
}
|
||||
|
||||
return senderId
|
||||
}
|
||||
|
||||
function sendMessage (senderId) {
|
||||
return sms.send(phoneNumber, senderId, messageId, acceptLanguage)
|
||||
.catch(err => {
|
||||
if (err.status === 500) {
|
||||
throw error.messageRejected(err.reason, err.reasonCode)
|
||||
}
|
||||
|
||||
if (err.status === 400) {
|
||||
throw error.invalidMessageId()
|
||||
}
|
||||
|
||||
throw new error.unexpectedError()
|
||||
})
|
||||
}
|
||||
|
||||
function logSuccess () {
|
||||
return request.emitMetricsEvent(`sms.${messageId}.sent`)
|
||||
}
|
||||
|
||||
function createResponse () {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -16,6 +16,9 @@ module.exports.URLSAFEBASE64 = /^[a-zA-Z0-9-_]*$/
|
|||
|
||||
module.exports.BASE_36 = /^[a-zA-Z0-9]*$/
|
||||
|
||||
// Crude phone number validation. The handler code does it more thoroughly.
|
||||
exports.E164_NUMBER = /^\+[1-9]\d{1,14}$/
|
||||
|
||||
// Match display-safe unicode characters.
|
||||
// We're pretty liberal with what's allowed in a unicode string,
|
||||
// but we exclude the following classes of characters:
|
||||
|
|
|
@ -2,22 +2,26 @@
|
|||
* 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/. */
|
||||
|
||||
var P = require('./promise')
|
||||
var createMailer = require('fxa-auth-mailer')
|
||||
'use strict'
|
||||
|
||||
module.exports = function (config, log) {
|
||||
var defaultLanguage = config.i18n.defaultLanguage
|
||||
const P = require('./promise')
|
||||
const createSenders = require('fxa-auth-mailer')
|
||||
|
||||
return createMailer(
|
||||
module.exports = (config, log) => {
|
||||
const defaultLanguage = config.i18n.defaultLanguage
|
||||
|
||||
return createSenders(
|
||||
log,
|
||||
{
|
||||
locales: config.i18n.supportedLanguages,
|
||||
defaultLanguage: defaultLanguage,
|
||||
mail: config.smtp
|
||||
mail: config.smtp,
|
||||
sms: config.sms
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (mailer) {
|
||||
senders => {
|
||||
const mailer = senders.email
|
||||
mailer.sendVerifyCode = function (account, code, opts) {
|
||||
return P.resolve(mailer.verifyEmail(
|
||||
{
|
||||
|
@ -151,7 +155,7 @@ module.exports = function (config, log) {
|
|||
}
|
||||
))
|
||||
}
|
||||
return mailer
|
||||
return senders
|
||||
}
|
||||
)
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -45,6 +45,7 @@
|
|||
"fxa-geodb": "0.0.7",
|
||||
"fxa-jwtool": "0.7.1",
|
||||
"fxa-shared": "1.0.3",
|
||||
"google-libphonenumber": "2.0.10",
|
||||
"hapi": "14.2.0",
|
||||
"hapi-auth-hawk": "3.0.1",
|
||||
"hapi-fxa-oauth": "2.2.0",
|
||||
|
|
|
@ -248,7 +248,7 @@ function createMailer () {
|
|||
locales: config.i18n.supportedLanguages,
|
||||
defaultLanguage: defaultLanguage,
|
||||
mail: config.smtp
|
||||
}, sender)
|
||||
}, sender).email
|
||||
}
|
||||
|
||||
function checkRequiredOption(optionName) {
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
/* 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/. */
|
||||
|
||||
'use strict'
|
||||
|
||||
const config = require('../config').getProperties()
|
||||
const NOT_SET = 'YOU MUST CHANGE ME'
|
||||
|
||||
if (config.sms.apiKey === NOT_SET || config.sms.apiSecret === NOT_SET) {
|
||||
fail('Come back and try again when you\'ve set SMS_API_KEY and SMS_API_SECRET.')
|
||||
}
|
||||
|
||||
const args = parseArgs()
|
||||
const log = require('../lib/log')(config.log.level, 'send-sms')
|
||||
|
||||
require('../lib/senders')(config, log)
|
||||
.then(senders => {
|
||||
return senders.sms.send.apply(null, args)
|
||||
})
|
||||
.then(() => {
|
||||
console.log('SENT!')
|
||||
})
|
||||
.catch(error => {
|
||||
let message = error.message
|
||||
if (error.reason && error.reasonCode) {
|
||||
message = `${message}: ${error.reasonCode} ${error.reason}`
|
||||
}
|
||||
fail(message)
|
||||
})
|
||||
|
||||
function fail (message) {
|
||||
console.error(message)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
function parseArgs () {
|
||||
let acceptLanguage, messageId, senderId, phoneNumber
|
||||
|
||||
switch (process.argv.length) {
|
||||
/* eslint-disable indent, no-fallthrough */
|
||||
case 6:
|
||||
acceptLanguage = process.argv[5]
|
||||
case 5:
|
||||
messageId = process.argv[4]
|
||||
case 4:
|
||||
senderId = process.argv[3]
|
||||
case 3:
|
||||
phoneNumber = process.argv[2]
|
||||
break
|
||||
default:
|
||||
fail(`Usage: ${process.argv[1]} phoneNumber [senderId] [messageId] [acceptLanguage]`)
|
||||
/* eslint-enable indent, no-fallthrough */
|
||||
}
|
||||
|
||||
return [
|
||||
phoneNumber,
|
||||
senderId || 'Firefox',
|
||||
messageId || 1,
|
||||
acceptLanguage || 'en'
|
||||
]
|
||||
}
|
||||
|
|
@ -12,9 +12,9 @@ describe('mailer locales', () => {
|
|||
|
||||
let mailer
|
||||
before(() => {
|
||||
return require('../../../lib/mailer')(config, log)
|
||||
.then(m => {
|
||||
mailer = m
|
||||
return require('../../../lib/senders')(config, log)
|
||||
.then(result => {
|
||||
mailer = result.email
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
@ -0,0 +1,326 @@
|
|||
/* 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/. */
|
||||
|
||||
'use strict'
|
||||
|
||||
const AppError = require('../../../lib/error')
|
||||
const assert = require('insist')
|
||||
const getRoute = require('../../routes_helpers').getRoute
|
||||
const isA = require('joi')
|
||||
const mocks = require('../../mocks')
|
||||
const P = require('../../../lib/promise')
|
||||
const sinon = require('sinon')
|
||||
|
||||
const sms = {}
|
||||
|
||||
function makeRoutes (options) {
|
||||
options = options || {}
|
||||
const log = options.log || mocks.mockLog()
|
||||
return require('../../../lib/routes/sms')(log, isA, AppError, options.config, mocks.mockCustoms(), sms)
|
||||
}
|
||||
|
||||
function runTest (route, request) {
|
||||
return new P((resolve, reject) => {
|
||||
route.handler(request, response => {
|
||||
if (response instanceof Error) {
|
||||
reject(response)
|
||||
} else {
|
||||
resolve(response)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
describe('/sms', () => {
|
||||
let log, config, routes, route, request
|
||||
|
||||
beforeEach(() => {
|
||||
log = mocks.spyLog()
|
||||
config = {
|
||||
sms: {
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
routes = makeRoutes({ log, config })
|
||||
route = getRoute(routes, '/sms')
|
||||
request = mocks.mockRequest({
|
||||
credentials: {
|
||||
email: 'foo@example.org'
|
||||
},
|
||||
log: log,
|
||||
payload: {
|
||||
messageId: 42,
|
||||
metricsContext: {
|
||||
flowBeginTime: Date.now(),
|
||||
flowId: '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('sms.send succeeds', () => {
|
||||
beforeEach(() => {
|
||||
sms.send = sinon.spy(() => P.resolve())
|
||||
})
|
||||
|
||||
describe('USA phone number', () => {
|
||||
beforeEach(() => {
|
||||
request.payload.phoneNumber = '+18885083401'
|
||||
return runTest(route, request)
|
||||
})
|
||||
|
||||
it('called log.begin correctly', () => {
|
||||
assert.equal(log.begin.callCount, 1)
|
||||
const args = log.begin.args[0]
|
||||
assert.equal(args.length, 2)
|
||||
assert.equal(args[0], 'sms.send')
|
||||
assert.equal(args[1], request)
|
||||
})
|
||||
|
||||
it('called request.validateMetricsContext correctly', () => {
|
||||
assert.equal(request.validateMetricsContext.callCount, 1)
|
||||
const args = request.validateMetricsContext.args[0]
|
||||
assert.equal(args.length, 0)
|
||||
})
|
||||
|
||||
it('called sms.send correctly', () => {
|
||||
assert.equal(sms.send.callCount, 1)
|
||||
const args = sms.send.args[0]
|
||||
assert.equal(args.length, 4)
|
||||
assert.equal(args[0], '+18885083401')
|
||||
assert.equal(args[1], '15036789977')
|
||||
assert.equal(args[2], 42)
|
||||
assert.equal(args[3], 'en-US')
|
||||
})
|
||||
|
||||
it('called log.flowEvent correctly', () => {
|
||||
assert.equal(log.flowEvent.callCount, 1)
|
||||
|
||||
const args = log.flowEvent.args[0]
|
||||
assert.equal(args.length, 1)
|
||||
assert.equal(args[0].event, 'sms.42.sent')
|
||||
assert.equal(args[0].flow_id, request.payload.metricsContext.flowId)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Canada phone number', () => {
|
||||
beforeEach(() => {
|
||||
request.payload.phoneNumber = '+14168483114'
|
||||
return runTest(route, request)
|
||||
})
|
||||
|
||||
it('called log.begin once', () => {
|
||||
assert.equal(log.begin.callCount, 1)
|
||||
})
|
||||
|
||||
it('called request.validateMetricsContext once', () => {
|
||||
assert.equal(request.validateMetricsContext.callCount, 1)
|
||||
})
|
||||
|
||||
it('called sms.send correctly', () => {
|
||||
assert.equal(sms.send.callCount, 1)
|
||||
const args = sms.send.args[0]
|
||||
assert.equal(args[0], '+14168483114')
|
||||
assert.equal(args[1], '16474909977')
|
||||
})
|
||||
|
||||
it('called log.flowEvent once', () => {
|
||||
assert.equal(log.flowEvent.callCount, 1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('UK phone number', () => {
|
||||
beforeEach(() => {
|
||||
request.payload.phoneNumber = '+442078553000'
|
||||
return runTest(route, request)
|
||||
})
|
||||
|
||||
it('called log.begin once', () => {
|
||||
assert.equal(log.begin.callCount, 1)
|
||||
})
|
||||
|
||||
it('called request.validateMetricsContext once', () => {
|
||||
assert.equal(request.validateMetricsContext.callCount, 1)
|
||||
})
|
||||
|
||||
it('called sms.send correctly', () => {
|
||||
assert.equal(sms.send.callCount, 1)
|
||||
const args = sms.send.args[0]
|
||||
assert.equal(args[0], '+442078553000')
|
||||
assert.equal(args[1], 'Firefox')
|
||||
})
|
||||
|
||||
it('called log.flowEvent once', () => {
|
||||
assert.equal(log.flowEvent.callCount, 1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('invalid phone number', () => {
|
||||
let err
|
||||
|
||||
beforeEach(() => {
|
||||
request.payload.phoneNumber = '+15551234567'
|
||||
return runTest(route, request)
|
||||
.catch(e => {
|
||||
err = e
|
||||
})
|
||||
})
|
||||
|
||||
it('called log.begin once', () => {
|
||||
assert.equal(log.begin.callCount, 1)
|
||||
})
|
||||
|
||||
it('called request.validateMetricsContext once', () => {
|
||||
assert.equal(request.validateMetricsContext.callCount, 1)
|
||||
})
|
||||
|
||||
it('did not call sms.send', () => {
|
||||
assert.equal(sms.send.callCount, 0)
|
||||
})
|
||||
|
||||
it('did not call log.flowEvent', () => {
|
||||
assert.equal(log.flowEvent.callCount, 0)
|
||||
})
|
||||
|
||||
it('threw the correct error data', () => {
|
||||
assert.ok(err instanceof AppError)
|
||||
assert.equal(err.errno, AppError.ERRNO.INVALID_PHONE_NUMBER)
|
||||
assert.equal(err.message, 'Invalid phone number')
|
||||
})
|
||||
})
|
||||
|
||||
describe('invalid region', () => {
|
||||
let err
|
||||
|
||||
beforeEach(() => {
|
||||
request.payload.phoneNumber = '+886287861100'
|
||||
return runTest(route, request)
|
||||
.catch(e => {
|
||||
err = e
|
||||
})
|
||||
})
|
||||
|
||||
it('called log.begin once', () => {
|
||||
assert.equal(log.begin.callCount, 1)
|
||||
})
|
||||
|
||||
it('called request.validateMetricsContext once', () => {
|
||||
assert.equal(request.validateMetricsContext.callCount, 1)
|
||||
})
|
||||
|
||||
it('did not call sms.send', () => {
|
||||
assert.equal(sms.send.callCount, 0)
|
||||
})
|
||||
|
||||
it('did not call log.flowEvent', () => {
|
||||
assert.equal(log.flowEvent.callCount, 0)
|
||||
})
|
||||
|
||||
it('threw the correct error data', () => {
|
||||
assert.ok(err instanceof AppError)
|
||||
assert.equal(err.errno, AppError.ERRNO.INVALID_REGION)
|
||||
assert.equal(err.message, 'Invalid region')
|
||||
assert.equal(err.output.payload.region, 'TW')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('sms.send fails with 500 error', () => {
|
||||
let err
|
||||
|
||||
beforeEach(() => {
|
||||
sms.send = sinon.spy(() => P.reject({
|
||||
status: 500,
|
||||
reason: 'wibble',
|
||||
reasonCode: 7
|
||||
}))
|
||||
request.payload.phoneNumber = '+18885083401'
|
||||
return runTest(route, request)
|
||||
.catch(e => {
|
||||
err = e
|
||||
})
|
||||
})
|
||||
|
||||
it('called log.begin once', () => {
|
||||
assert.equal(log.begin.callCount, 1)
|
||||
})
|
||||
|
||||
it('called request.validateMetricsContext once', () => {
|
||||
assert.equal(request.validateMetricsContext.callCount, 1)
|
||||
})
|
||||
|
||||
it('called sms.send once', () => {
|
||||
assert.equal(sms.send.callCount, 1)
|
||||
})
|
||||
|
||||
it('did not call log.flowEvent', () => {
|
||||
assert.equal(log.flowEvent.callCount, 0)
|
||||
})
|
||||
|
||||
it('threw the correct error data', () => {
|
||||
assert.ok(err instanceof AppError)
|
||||
assert.equal(err.errno, AppError.ERRNO.MESSAGE_REJECTED)
|
||||
assert.equal(err.message, 'Message rejected')
|
||||
assert.equal(err.output.payload.reason, 'wibble')
|
||||
assert.equal(err.output.payload.reasonCode, 7)
|
||||
})
|
||||
})
|
||||
|
||||
describe('sms.send fails with 400 error', () => {
|
||||
let err
|
||||
|
||||
beforeEach(() => {
|
||||
sms.send = sinon.spy(() => P.reject({
|
||||
status: 400
|
||||
}))
|
||||
request.payload.phoneNumber = '+18885083401'
|
||||
return runTest(route, request)
|
||||
.catch(e => {
|
||||
err = e
|
||||
})
|
||||
})
|
||||
|
||||
it('called log.begin once', () => {
|
||||
assert.equal(log.begin.callCount, 1)
|
||||
})
|
||||
|
||||
it('called request.validateMetricsContext once', () => {
|
||||
assert.equal(request.validateMetricsContext.callCount, 1)
|
||||
})
|
||||
|
||||
it('called sms.send once', () => {
|
||||
assert.equal(sms.send.callCount, 1)
|
||||
})
|
||||
|
||||
it('did not call log.flowEvent', () => {
|
||||
assert.equal(log.flowEvent.callCount, 0)
|
||||
})
|
||||
|
||||
it('threw the correct error data', () => {
|
||||
assert.ok(err instanceof AppError)
|
||||
assert.equal(err.errno, AppError.ERRNO.INVALID_MESSAGE_ID)
|
||||
assert.equal(err.message, 'Invalid message id')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('/sms disabled', () => {
|
||||
let log, config, routes
|
||||
|
||||
beforeEach(() => {
|
||||
log = mocks.spyLog()
|
||||
config = {
|
||||
sms: {
|
||||
enabled: false
|
||||
}
|
||||
}
|
||||
routes = makeRoutes({ log, config })
|
||||
})
|
||||
|
||||
it('routes was empty array', () => {
|
||||
assert.ok(Array.isArray(routes))
|
||||
assert.equal(routes.length, 0)
|
||||
})
|
||||
})
|
||||
|
Загрузка…
Ссылка в новой задаче