Commit f43dbc16a329b2ffedd2103dc9ddbcb1343ef668

Authored by Adhidarma Hadiwinoto
1 parent 4051d4be68
Exists in master

New modem selector

Showing 7 changed files with 167 additions and 104 deletions Inline Diff

lib/apiserver/index.js
1 "use strict"; 1 "use strict";
2 2
3 /** 3 /**
4 * Modul untuk menerima callback dari modem handler jika ada SMS masuk. 4 * Modul untuk menerima callback dari modem handler jika ada SMS masuk.
5 */ 5 */
6 6
7 7
8 const express = require('express'); 8 const express = require('express');
9 const moment = require('moment'); 9 const moment = require('moment');
10 10
11 const messagingService = require('komodo-center-messaging-client-lib'); 11 const messagingService = require('komodo-center-messaging-client-lib');
12 12
13 const config = require('komodo-sdk/config'); 13 const config = require('komodo-sdk/config');
14 const logger = require('komodo-sdk/logger'); 14 const logger = require('komodo-sdk/logger');
15 15
16 const transport = require('../transport'); 16 const transport = require('../transport');
17 const partnerLastSeen = require('../partner-last-seen'); 17 const partnerLastSeen = require('../partner-last-seen');
18 const history = require('../history'); 18 const history = require('../history');
19 const modems = require('../modems2');
19 20
20 const app = express(); 21 const app = express();
21 messagingService.setTransport(transport); 22 messagingService.setTransport(transport);
22 23
23 function apikeyChecker(req, res, next) { 24 function apikeyChecker(req, res, next) {
24 res.locals.has_valid_apikey = req.params.apikey === config.handler_callback_server.apikey; 25 res.locals.has_valid_apikey = req.params.apikey === config.handler_callback_server.apikey;
25 if (res.locals.has_valid_apikey) { 26 if (res.locals.has_valid_apikey) {
26 next(); 27 next();
27 } 28 }
28 else { 29 else {
29 res.end('APISERVER: Invalid apikey'); 30 res.end('APISERVER: Invalid apikey');
30 } 31 }
31 } 32 }
32 33
33 function onIncomingSms(req, res) { 34 function onIncomingSms(req, res) {
34 res.end('OK'); 35 res.end('OK');
35 36
36 if (!req.query.number) return; 37 if (!req.query.number) return;
37 if (req.query.number.indexOf('+') !== 0) return; 38 if (req.query.number.indexOf('+') !== 0) return;
38 39
39 const numberWithSuffix = req.query.number.replace(/^\+/, '') + (config.number_suffix || ''); 40 const numberWithSuffix = req.query.number.replace(/^\+/, '') + (config.number_suffix || '');
40 41
41 partnerLastSeen.set(req.query.number, req.query.modem); 42 partnerLastSeen.set(req.query.number, req.query.modem);
42 43
43 history.push({ 44 history.push({
44 ts: req.query.ts || moment().format('YYYY-MM-DD HH:mm:ss'), 45 ts: req.query.ts || moment().format('YYYY-MM-DD HH:mm:ss'),
45 modem: { 46 modem: {
46 name: req.query.modem, 47 name: req.query.modem,
47 imsi: req.query.modem_imsi, 48 imsi: req.query.modem_imsi,
48 msisdn: req.query.modem_msisdn, 49 msisdn: req.query.modem_msisdn,
49 }, 50 },
50 direction: 'INCOMING', 51 direction: 'INCOMING',
51 partner: req.query.number, 52 partner: req.query.number,
52 message: req.query.msg, 53 message: req.query.msg,
53 }); 54 });
54 55
56 modems.set({
57 name: req.query.modem,
58 imsi: req.query.modem_imsi,
59 msisdn: req.query.modem_msisdn,
60 reportIp: req.query.report_ip || req.ip,
61 reportPort: req.query.report_port,
62 reportApikey: req.query.report_apikey,
63 });
64
55 logger.info('APISERVER: Incoming SMS', { modem: req.query.modem, from: req.query.number, from_with_suffix: numberWithSuffix, msg: req.query.msg }); 65 logger.info('APISERVER: Incoming SMS', { modem: req.query.modem, from: req.query.number, from_with_suffix: numberWithSuffix, msg: req.query.msg });
56 messagingService.onIncomingMessage({ 66 messagingService.onIncomingMessage({
57 me: req.query.modem, 67 me: req.query.modem,
58 partner: numberWithSuffix, 68 partner: numberWithSuffix,
59 msg: req.query.msg 69 msg: req.query.msg
60 }) 70 })
61 } 71 }
62 72
63 async function pageHistory(req, res) { 73 async function pageHistory(req, res) {
64 res.json(await history.dump()); 74 res.json(await history.dump());
65 } 75 }
66 76
67 app.use(function(req, res, next) { 77 app.use(function(req, res, next) {
68 logger.verbose('APISERVER: Incoming http request', { ip: req.ip, url: req.url }); 78 logger.verbose('APISERVER: Incoming http request', { ip: req.ip, url: req.url });
69 next(); 79 next();
70 }) 80 })
71 81
72 app.use('/apikey/:apikey', apikeyChecker); 82 app.use('/apikey/:apikey', apikeyChecker);
73 app.get('/apikey/:apikey/on-sms', onIncomingSms); 83 app.get('/apikey/:apikey/on-sms', onIncomingSms);
74 app.get('/apikey/:apikey/inbox', onIncomingSms); 84 app.get('/apikey/:apikey/inbox', onIncomingSms);
75 app.get('/apikey/:apikey/on-sms/inbox', onIncomingSms); 85 app.get('/apikey/:apikey/on-sms/inbox', onIncomingSms);
76 app.get('/apikey/:apikey/history', pageHistory); 86 app.get('/apikey/:apikey/history', pageHistory);
77 87
78 const listenPort = config && config.handler_callback_server ? config.handler_callback_server.listen_port : null; 88 const listenPort = config && config.handler_callback_server ? config.handler_callback_server.listen_port : null;
79 listenPort && app.listen(listenPort, () => { 89 listenPort && app.listen(listenPort, () => {
80 logger.info('HTTP Handler Callback server listening on port ' + listenPort); 90 logger.info('HTTP Handler Callback server listening on port ' + listenPort);
81 }) 91 })
File was created 1 "use strict";
2
3 function getModemConfig(modemName, modemsConfig) {
4 if (!modemsConfig) return;
5 if (!modemName) return;
6 if (typeof modemName === 'string' && !modemName.trim()) return;
7
8 return modemsConfig[modemName.trim()];
9 }
10
11 function getModemUrl(modemName, modemsConfig) {
12 const modemConfig = getModemConfig(modemName, modemsConfig);
13 return modemConfig ? modemConfig.url : null;
14 }
15
16 function getModemApikey(modemName, modemsConfig) {
17 const modemConfig = getModemConfig(modemName, modemsConfig);
18 return modemConfig ? modemConfig.apikey : null;
19 }
20
21 function removeSuffixFromNumber(number, config) {
22 if (!config) {
23 config = {};
24 }
25
26 const suffix = config && config.number_suffix ? config.number_suffix : '@.*';
27 const re = new RegExp(suffix + '$');
28 return number.replace(re, '');
29 }
30
31 exports.getModemConfig = getModemConfig;
32 exports.getModemUrl = getModemUrl;
33 exports.getModemApikey = getModemApikey;
34 exports.removeSuffixFromNumber = removeSuffixFromNumber;
lib/modems.js
1 "use strict"; File was deleted
2
3 function getModemConfig(modemName, modemsConfig) {
4 if (!modemsConfig) return;
5 if (!modemName) return;
6 if (typeof modemName === 'string' && !modemName.trim()) return;
7
8 return modemsConfig[modemName.trim()];
9 }
10
11 function getModemUrl(modemName, modemsConfig) {
12 const modemConfig = getModemConfig(modemName, modemsConfig);
13 return modemConfig ? modemConfig.url : null;
14 }
15
16 function getModemApikey(modemName, modemsConfig) {
17 const modemConfig = getModemConfig(modemName, modemsConfig);
18 return modemConfig ? modemConfig.apikey : null;
19 }
20
21 function removeSuffixFromNumber(number, config) {
22 if (!config) {
23 config = {};
24 }
25
26 const suffix = config && config.number_suffix ? config.number_suffix : '@.*';
27 const re = new RegExp(suffix + '$');
28 return number.replace(re, '');
29 }
30
31 exports.getModemConfig = getModemConfig;
32 exports.getModemUrl = getModemUrl;
33 exports.getModemApikey = getModemApikey;
34 exports.removeSuffixFromNumber = removeSuffixFromNumber;
File was created 1 'use strict';
2
3 const modemList = {
4 by_name: {},
5 by_imsi: {},
6 by_msisdn: {},
7 };
8
9 /**
10 * Objek data sebuah modem.
11 *
12 * @typedef {Object} ModemData
13 * @property {string} name - nama modem
14 * @property {string} imsi - IMSI modem
15 * @property {string} msisdn - MSISDN modem
16 * @property {string} reportIp - IP modem
17 * @property {number} reportPort - TCP port modem
18 * @property {string} reportApikey - APIKEY modem
19 */
20
21 /**
22 * Update data sebuah modem berdasarkan nama modem.
23 *
24 * @param {ModemData} val - objek data modem
25 */
26 function touchByName(val) {
27 if (!val || !val.name) return;
28 if (typeof val.name !== 'string') return;
29 if (!val.name.trim()) return;
30
31 modemList.by_name[val.name] = val;
32 }
33 /**
34 * Update data sebuah modem berdasarkan IMSI.
35 *
36 * @param {ModemData} val - objek data modem
37 */
38 function touchByIMSI(val) {
39 if (!val || !val.imsi) return;
40 if (typeof val.imsi !== 'string') return;
41 if (!val.imsi.trim()) return;
42
43 modemList.by_imsi[val.imsi] = val;
44 }
45
46 /**
47 * Update data sebuah modem berdasarkan MSISDN.
48 *
49 * @param {ModemData} val - objek data modem
50 */
51 function touchByMSISDN(val) {
52 if (!val || !val.msisdn) return;
53 if (typeof val.msisdn !== 'string') return;
54 if (!val.msisdn.trim()) return;
55
56 modemList.by_msisdn[val.msisdn] = val;
57 }
58
59 /**
60 * Update data sebuah modem.
61 *
62 * @param {ModemData} val - objek data modem
63 * @see ModemData
64 */
65 function touch(val) {
66 if (!val) return;
67
68 if (!val.reportIp) {
69 val.reportIp = '127.0.0.1';
70 }
71
72 touchByName(val);
73 touchByIMSI(val);
74 touchByMSISDN(val);
75 }
76 /**
77 * Ambil data sebuah modem.
78 *
79 * @param {string} selector - selector pencarian, valid jika bernilai salah satu dari: name, imsi, msisdn
80 * @param {string} keyword - kata kunci modem yang ingin diambil
81 * @returns {ModemData} data modem terkait
82 */
83 function get(selector, keyword) {
84 if (!selector || !keyword) return null;
85
86 return modemList[`by_${selector}`] ? modemList[`by_${selector}`][keyword] : null;
87 }
88
89 exports.touch = touch;
90 exports.set = touch;
91 exports.get = get;
lib/partner-last-seen.js
1 "use strict"; 1 "use strict";
2 2
3 const REDIS_TTL_SECS = 3600 * 24 * 7; 3 const REDIS_TTL_SECS = 3600 * 24 * 31;
4 4
5 const config = require('komodo-sdk/config'); 5 const config = require('komodo-sdk/config');
6 // const logger = require('komodo-sdk/logger'); 6 // const logger = require('komodo-sdk/logger');
7 7
8 const redis = require('redis'); 8 const redis = require('redis');
9 const redisClient = redis.createClient(config.redis || { host: '127.0.0.1' }); 9 const redisClient = redis.createClient(config.redis || { host: '127.0.0.1' });
10 10
11 const _caches = {}; 11 const _caches = {};
12 12
13 13
14 function _composeKeyword(partner) { 14 function _composeKeyword(partner) {
15 return `POCHINKI_PARTNER_LAST_SEEN_${ partner }`; 15 return `POCHINKI_PARTNER_LAST_SEEN_${ partner }`;
16 } 16 }
17 17
18 function get(partnerNumber) { 18 function get(partnerNumber) {
19 return new Promise(function(resolve) { 19 return new Promise(function(resolve) {
20 20
21 partnerNumber = (partnerNumber || '').replace(/^\+/, ''); 21 partnerNumber = (partnerNumber || '').replace(/^\+/, '');
22 22
23 if (!partnerNumber) { 23 if (!partnerNumber) {
24 resolve(null); 24 resolve(null);
25 } 25 }
26 else if (_caches[partnerNumber]) { 26 else if (_caches[partnerNumber]) {
27 resolve(_caches[partnerNumber]); 27 resolve(_caches[partnerNumber]);
28 } 28 }
29 else { 29 else {
30 const keyword = _composeKeyword(partnerNumber); 30 const keyword = _composeKeyword(partnerNumber);
31 // logger.verbose(`REDIS-DEBUG: GET`, {keyword}); 31 // logger.verbose(`REDIS-DEBUG: GET`, {keyword});
32 32
33 redisClient.get(keyword, function(err, reply) { 33 redisClient.get(keyword, function(err, reply) {
34 if (err) { 34 if (err) {
35 resolve(null); 35 resolve(null);
36 } 36 }
37 else if (reply) { 37 else if (reply) {
38 resolve(Number(reply)); 38 resolve(Number(reply));
39 _caches[partnerNumber] = Number(reply); 39 _caches[partnerNumber] = Number(reply);
40 } 40 }
41 else { 41 else {
42 resolve(null); 42 resolve(null);
43 } 43 }
44 }) 44 })
45 } 45 }
46 }) 46 })
47 } 47 }
48 48
49 function set(partnerNumber, modemName) { 49 function set(partnerNumber, modemName) {
50 partnerNumber = (partnerNumber || '').replace(/^\+/, ''); 50 partnerNumber = (partnerNumber || '').replace(/^\+/, '');
51 51
52 if (!partnerNumber || !modemName) { 52 if (!partnerNumber || !modemName) {
53 return; 53 return;
54 } 54 }
55 55
56 _caches[partnerNumber] = modemName; 56 _caches[partnerNumber] = modemName;
57 57
58 const keyword = _composeKeyword(partnerNumber); 58 const keyword = _composeKeyword(partnerNumber);
59 // logger.verbose('REDIS-DEBUG: SET', { keyword }) 59 // logger.verbose('REDIS-DEBUG: SET', { keyword })
60 redisClient.set(keyword, modemName, 'EX', REDIS_TTL_SECS); 60 redisClient.set(keyword, modemName, 'EX', REDIS_TTL_SECS);
61 } 61 }
62 62
63 exports.get = get; 63 exports.get = get;
64 exports.set = set; 64 exports.set = set;
65 65
1 "use strict"; 1 "use strict";
2 2
3 const url = require('url');
3 const request = require('request'); 4 const request = require('request');
4 const uuidv4 = require('uuid/v4'); 5 const uuidv4 = require('uuid/v4');
5 const moment = require('moment'); 6 const moment = require('moment');
6 7
7 const config = require('komodo-sdk/config'); 8 const config = require('komodo-sdk/config');
8 const logger = require('komodo-sdk/logger'); 9 const logger = require('komodo-sdk/logger');
9 10
10 const modems = require('./modems'); 11 const modemSelect = require('./modemSelect');
12 const modems = require('./modems2');
11 const partnerLastSeen = require('./partner-last-seen'); 13 const partnerLastSeen = require('./partner-last-seen');
12 const history = require('./history'); 14 const history = require('./history');
13 15
14 async function _getApproriateHandlerByLastSeen(partnerNumber) {
15 logger.verbose('Looking for last seen on for partner number ' + partnerNumber);
16 const lastSeenFrom = await partnerLastSeen.get(partnerNumber);
17 return lastSeenFrom;
18 }
19
20 function _getApproriateHandlerByForced() {
21 if (!config.sending_handler || !config.sending_handler.length) return;
22
23 const sendingHandlerCount = config.sending_handler.length;
24 const idx = Math.floor(Math.random() * sendingHandlerCount);
25 return config.sending_handler[idx];
26 }
27
28 async function _getApproriateHandler(partnerNumber, origin) {
29 let handlerToUse;
30
31 if (config.handler_chooser_algorithm === 'FORCED') {
32 handlerToUse = _getApproriateHandlerByForced();
33 logger.verbose('Config file mentioned to using FORCED handler chooser algorithm', { handler_to_use: handlerToUse});
34 }
35 else {
36 handlerToUse = await _getApproriateHandlerByLastSeen(partnerNumber);
37 logger.verbose('Config file mentioned to using LAST-SEEN handler chooser algorithm', { handler_to_use: handlerToUse});
38 }
39
40 if (!modems.getModemConfig(handlerToUse, config.modems)) {
41 const handlerWithSameOrigin = modems.getModemConfig(origin, config.modems);
42 if (handlerWithSameOrigin) {
43 logger.verbose('Invalid approriate handler, using handler from the same ORIGIN request by CORE to send sms')
44 handlerToUse = origin;
45 }
46 else {
47 logger.verbose('Invalid approriate handler, using default handler to send sms')
48 handlerToUse = config.default_modem;
49 }
50 }
51
52 return handlerToUse;
53 }
54
55 function _send(destinationNumber, msg, handlerName) { 16 function _send(destinationNumber, msg, handlerName) {
56 17
57 /*
58 if (msg.length > 160 && !config.do_not_trim_long_sms) {
59 logger.verbose('Message trim to 160 chars');
60 msg = msg.slice(0, 156) + ' ...';
61 }
62 */
63
64 if (msg.length > 160) { 18 if (msg.length > 160) {
65 const newMsg = msg.slice(0, 160); 19 const newMsg = msg.slice(0, 160);
66 const remainingMsg = msg.slice(160); 20 const remainingMsg = msg.slice(160);
67 21
68 _send(destinationNumber, newMsg, handlerName); 22 _send(destinationNumber, newMsg, handlerName);
69 setTimeout(() => { 23 setTimeout(() => {
70 _send(destinationNumber, remainingMsg, handlerName); 24 _send(destinationNumber, remainingMsg, handlerName);
71 }, 2000); 25 }, 2000);
72 26
73 return; 27 return;
74 } 28 }
75 29
76 const modem = modems.getModemConfig(handlerName, config.modems); 30 const modem = modems.get('name', handlerName);
77 if (!modem) { 31 if (!modem) {
78 logger.warn('Not knowing modem to use. Ignoring message', { destination_number: destinationNumber, msg: msg, handler_name: handlerName }); 32 logger.warn('Not knowing modem to use. Ignoring message', { destination_number: destinationNumber, msg: msg, handler_name: handlerName });
79 return; 33 return;
80 } 34 }
81 35
82 if (!modem.url || !modem.apikey) { 36 if (!modem.reportIp || !modem.reportPort || !modem.reportApikey) {
83 logger.warn('Invalid modem configuration', { config: modem, handler_name: handlerName }); 37 logger.warn('Invalid modem configuration', { modem });
84 return; 38 return;
85 } 39 }
86 40
87 const reqId = uuidv4(); 41 const reqId = uuidv4();
88 42
89 history.push({ 43 history.push({
90 ts: moment().format('YYYY-MM-DD HH:mm:ss'), 44 ts: moment().format('YYYY-MM-DD HH:mm:ss'),
91 modem: { 45 modem: {
92 name: handlerName, 46 name: handlerName,
93 }, 47 },
94 direction: 'OUTGOING', 48 direction: 'OUTGOING',
95 partner: destinationNumber, 49 partner: destinationNumber,
96 message: msg, 50 message: msg,
97 }); 51 });
98 52
99
100 const requestOptions = { 53 const requestOptions = {
101 url: modem.url, 54 url: url.format({
55 protocol: 'http',
56 hostname: modem.reportIp,
57 port: modem.reportPort,
58 }),
102 qs: { 59 qs: {
103 msg: msg, 60 msg: msg,
104 number: destinationNumber, 61 number: destinationNumber,
105 reqid: reqId, 62 reqid: reqId,
106 apikey: modem.apikey 63 apikey: modem.reportApikey
107 } 64 }
108 } 65 }
109 66
110 logger.info('Sending message to modem handler', { req_id: reqId, destination_number: destinationNumber, msg: msg, msg_length: msg.length, handler_name: handlerName }); 67 logger.info('Sending message to modem handler', { req_id: reqId, destination_number: destinationNumber, msg: msg, msg_length: msg.length, handler_name: handlerName });
111 request(requestOptions, function(err, res, body) { 68 request(requestOptions, function(err, res, body) {
112 if (err) { 69 if (err) {
113 logger.warn('Error requesting to modem handler. ' + err.toString(), { req_id: reqId, handler_name: handlerName }); 70 logger.warn('Error requesting to modem handler. ' + err.toString(), { req_id: reqId, handler_name: handlerName });
114 71
115 } 72 }
116 else if (res.statusCode != 200) { 73 else if (res.statusCode != 200) {
117 logger.warn('Modem handler not responding with HTTP status code 200.', { http_status_code: res.statusCode, req_id: reqId, handler_name: handlerName }); 74 logger.warn('Modem handler not responding with HTTP status code 200.', { http_status_code: res.statusCode, req_id: reqId, handler_name: handlerName });
118 } 75 }
119 else { 76 else {
120 logger.verbose('Message sent to handler', { req_id: reqId, handler_name: handlerName, response_body: body }); 77 logger.verbose('Message sent to handler', { req_id: reqId, handler_name: handlerName, response_body: body });
121 } 78 }
122 }) 79 })
123 80
124 } 81 }
125 82
126 async function send(partner, msg, origin) { 83 async function send(partner, msg, origin) {
127 if (!partner) return; 84 if (!partner) return;
128 85
129 if (typeof msg !== 'string') { 86 if (typeof msg !== 'string') {
130 logger.warn('Message to send is not a string, ignoring message'); 87 logger.warn('Message to send is not a string, ignoring message');
131 return; 88 return;
132 } 89 }
133 90
134 msg = msg.trim(); 91 msg = msg.trim();
135 if (!msg) return; 92 if (!msg) return;
136 93
137 const destinationNumber = modems.removeSuffixFromNumber(partner, config); 94 const destinationNumber = modemSelect.removeSuffixFromNumber(partner, config);
95
96 // logger.verbose('Choosing handler name', { partner, destinationNumber, msg, origin });
1 "use strict"; 1 "use strict";
2 2
3 /* global describe it */ 3 /* global describe it */
4 4
5 const should = require('should'); 5 const should = require('should');
6 6
7 const modems = require('../lib/modems'); 7 const modemSelect = require('../lib/modemSelect');
8 8
9 describe('#modems', function() { 9 describe('#modemSelect', function() {
10 10
11 describe('#getModemUrl', function() { 11 describe('#getModemUrl', function() {
12 12
13 const modemsConfig = { 13 const modemsConfig = {
14 SMS0: { 14 SMS0: {
15 url: "http://localhost:8888/" 15 url: "http://localhost:8888/"
16 } 16 }
17 } 17 }
18 18
19 it('should return correct url', function() { 19 it('should return correct url', function() {
20 modems.getModemUrl('SMS0', modemsConfig).should.equal('http://localhost:8888/'); 20 modemSelect.getModemUrl('SMS0', modemsConfig).should.equal('http://localhost:8888/');
21 }) 21 })
22 22
23 it ('should handle missing modems', function() { 23 it ('should handle missing modems', function() {
24 should.not.exists(modems.getModemUrl('SMS0', null)); 24 should.not.exists(modemSelect.getModemUrl('SMS0', null));
25 should.not.exists(modems.getModemUrl('SMS0', {})); 25 should.not.exists(modemSelect.getModemUrl('SMS0', {}));
26 should.not.exists(modems.getModemUrl('SMS1', modemsConfig)); 26 should.not.exists(modemSelect.getModemUrl('SMS1', modemsConfig));
27 }) 27 })
28 }) 28 })
29 29
30 describe('#removeSuffixFromNumber', function() { 30 describe('#removeSuffixFromNumber', function() {
31 const config = { 31 const config = {
32 number_suffix: '@phonenumber' 32 number_suffix: '@phonenumber'
33 } 33 }
34 34
35 it('should return correct number', function() { 35 it('should return correct number', function() {
36 modems.removeSuffixFromNumber('08181234@phonenumber', config).should.equal('08181234'); 36 modemSelect.removeSuffixFromNumber('08181234@phonenumber', config).should.equal('08181234');
37 }) 37 })
38 38
39 it ('should return correct number without suffix in the number', function() { 39 it ('should return correct number without suffix in the number', function() {
40 modems.removeSuffixFromNumber('08181234', config).should.equal('08181234'); 40 modemSelect.removeSuffixFromNumber('08181234', config).should.equal('08181234');
41 }) 41 })
42 42
43 it ('should return correct number without suffix in config', function() { 43 it ('should return correct number without suffix in config', function() {
44 modems.removeSuffixFromNumber('08181234', null).should.equal('08181234'); 44 modemSelect.removeSuffixFromNumber('08181234', null).should.equal('08181234');
45 modems.removeSuffixFromNumber('08181234', {}).should.equal('08181234'); 45 modemSelect.removeSuffixFromNumber('08181234', {}).should.equal('08181234');
46 modems.removeSuffixFromNumber('08181234@phonenumber', {}).should.equal('08181234'); 46 modemSelect.removeSuffixFromNumber('08181234@phonenumber', {}).should.equal('08181234');
47 }) 47 })
48 }) 48 })
49 49
50 }) 50 })