Commit 76a4bcfcce7f3195e230f4dc9344cb709e2ca43e

Authored by Adhidarma Hadiwinoto
1 parent 1c0e584d58
Exists in master

Adaptasi modem baru

Showing 7 changed files with 147 additions and 54 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 // const modems = require('../modems2');
20 20
21 const routerConfigSenders = require('./router-config-senders'); 21 const routerConfigSenders = require('./router-config-senders');
22 const routerModems = require('./router-modems'); 22 const routerModems = require('./router-modems');
23 23
24 if (config.handler_callback_server) { 24 if (config.handler_callback_server) {
25 logger.warn('Deprecated config.handler_callback_server. Please migrate it to config.apiserver!'); 25 logger.warn('Deprecated config.handler_callback_server. Please migrate it to config.apiserver!');
26 } 26 }
27 27
28 const app = express(); 28 const app = express();
29 messagingService.setTransport(transport); 29 messagingService.setTransport(transport);
30 30
31 function apikeyChecker(req, res, next) { 31 function apikeyChecker(req, res, next) {
32 res.locals.has_valid_apikey = req.params.apikey === ((config.apiserver && config.apiserver.apikey ? config.apiserver.apikey : null) || config.handler_callback_server.apikey); 32 res.locals.has_valid_apikey = req.params.apikey === ((config.apiserver && config.apiserver.apikey ? config.apiserver.apikey : null) || config.handler_callback_server.apikey);
33 if (res.locals.has_valid_apikey) { 33 if (res.locals.has_valid_apikey) {
34 next(); 34 next();
35 } 35 }
36 else { 36 else {
37 logger.warn('Invalid apikey', { ip: req.ip }); 37 logger.warn('Invalid apikey', { ip: req.ip });
38 res.end('APISERVER: Invalid apikey'); 38 res.end('APISERVER: Invalid apikey');
39 } 39 }
40 } 40 }
41 41
42 function onIncomingSms(req, res) { 42 function onIncomingSms(req, res) {
43 res.end('OK'); 43 res.end('OK');
44 44
45 if (!req.query.number) return; 45 if (!req.query.number) return;
46 const numberWithSuffix = req.query.number.replace(/^\+/, '') + (config.number_suffix || ''); 46 const numberWithSuffix = req.query.number.replace(/^\+/, '') + (config.number_suffix || '');
47 47
48 partnerLastSeen.set(req.query.number, req.query.modem_imsi); 48 partnerLastSeen.set(req.query.number, req.query.modem_imsi);
49 49
50 history.push({ 50 history.push({
51 ts: req.query.ts || moment().format('YYYY-MM-DD HH:mm:ss'), 51 ts: req.query.ts || moment().format('YYYY-MM-DD HH:mm:ss'),
52 modem: { 52 modem: {
53 name: req.query.modem, 53 name: req.query.modem,
54 imsi: req.query.modem_imsi, 54 imsi: req.query.modem_imsi,
55 msisdn: req.query.modem_msisdn, 55 msisdn: req.query.modem_msisdn,
56 }, 56 },
57 direction: 'INCOMING', 57 direction: 'INCOMING',
58 partner: req.query.number, 58 partner: req.query.number,
59 message: req.query.msg, 59 message: req.query.msg,
60 }); 60 });
61 61
62 /* 62 /*
63 modems.set({ 63 modems.set({
64 name: req.query.modem, 64 name: req.query.modem,
65 device: req.query.modem_device, 65 device: req.query.modem_device,
66 imsi: req.query.modem_imsi, 66 imsi: req.query.modem_imsi,
67 msisdn: req.query.modem_msisdn, 67 msisdn: req.query.modem_msisdn,
68 networkId: req.query.modem.network_id, 68 networkId: req.query.modem.network_id,
69 networkName: req.query.modem_network_name, 69 networkName: req.query.modem_network_name,
70 signalStrength: req.query.modem_signal_strength, 70 signalStrength: req.query.modem_signal_strength,
71 uptime: req.query.uptime, 71 uptime: req.query.uptime,
72 reportIp: req.query.report_ip || req.ip, 72 reportIp: req.query.report_ip || req.ip,
73 reportPort: req.query.report_port, 73 reportPort: req.query.report_port,
74 reportApikey: req.query.report_apikey, 74 reportApikey: req.query.report_apikey,
75 reportPathSms: req.query.report_path_sms || '/sms', 75 reportPathSms: req.query.report_path_sms || '/sms',
76 }); 76 });
77 */ 77 */
78 78
79 logger.info('APISERVER: Incoming SMS', { modem: req.query.modem, from: req.query.number, from_with_suffix: numberWithSuffix, msg: req.query.msg }); 79 const doNotForwardToCore = (req.query.number.search(/(\+)*62/) !== 0) || (req.query.number.length <= 8);
80 logger.info('APISERVER: Incoming SMS', { modem: req.query.modem, from: req.query.number, from_with_suffix: numberWithSuffix, msg: req.query.msg, doNotForwardToCore });
81
80 messagingService.onIncomingMessage({ 82 messagingService.onIncomingMessage({
81 me: req.query.modem, 83 me: req.query.modem,
82 partner: numberWithSuffix, 84 partner: numberWithSuffix,
83 partner_raw: req.query.number, 85 partner_raw: req.query.number,
84 msg: req.query.msg, 86 msg: req.query.msg,
85 origin_label: req.query.modem_imsi || 'UNKNOWN', 87 origin_label: req.query.modem_imsi || 'UNKNOWN',
86 origin_transport: 'SMS', 88 origin_transport: 'SMS',
87 origin_partner: req.query.number, 89 origin_partner: req.query.number,
88 do_not_forward_to_core: (req.query.number.indexOf('+62') !== 0) || (req.query.number.length <= 8), 90 do_not_forward_to_core: doNotForwardToCore,
89 }); 91 });
90 } 92 }
91 93
92 async function pageHistory(req, res) { 94 async function pageHistory(req, res) {
93 res.json(await history.dump()); 95 res.json(await history.dump());
94 } 96 }
95 97
96 app.use(function(req, res, next) { 98 app.use(function(req, res, next) {
97 if ( 99 if (
98 req && req.path && typeof req.path === 'string' 100 req && req.path && typeof req.path === 'string'
99 && ( 101 && (
100 req.path.search(/\/modems$/) >= 0 102 req.path.search(/\/modems$/) >= 0
101 || req.path.search(/\/modems\/set$/) >= 0 103 || req.path.search(/\/modems\/set$/) >= 0
102 ) 104 )
103 ) { 105 ) {
104 next(); 106 next();
105 return; 107 return;
106 } 108 }
107 109
108 logger.verbose('APISERVER: Incoming http request', { ip: req.ip, path: req.path, url: req.url }); 110 logger.verbose('APISERVER: Incoming http request', { ip: req.ip, path: req.path, url: req.url });
109 next(); 111 next();
110 }) 112 })
111 113
112 app.use('/apikey/:apikey', apikeyChecker); 114 app.use('/apikey/:apikey', apikeyChecker);
113 app.get('/apikey/:apikey/on-sms', onIncomingSms); 115 app.get('/apikey/:apikey/on-sms', onIncomingSms);
114 app.get('/apikey/:apikey/inbox', onIncomingSms); 116 app.get('/apikey/:apikey/inbox', onIncomingSms);
115 app.get('/apikey/:apikey/on-sms/inbox', onIncomingSms); 117 app.get('/apikey/:apikey/on-sms/inbox', onIncomingSms);
116 app.get('/apikey/:apikey/history', pageHistory); 118 app.get('/apikey/:apikey/history', pageHistory);
117 app.use('/apikey/:apikey/modems', routerModems); 119 app.use('/apikey/:apikey/modems', routerModems);
118 app.use('/apikey/:apikey/config/senders', routerConfigSenders); 120 app.use('/apikey/:apikey/config/senders', routerConfigSenders);
119 121
120 const listenPort = (config && config.apiserver && config.apiserver.listen_port ? config.apiserver.listen_port : null) 122 const listenPort = (config && config.apiserver && config.apiserver.listen_port ? config.apiserver.listen_port : null)
121 || (config && config.handler_callback_server ? config.handler_callback_server.listen_port : null); 123 || (config && config.handler_callback_server ? config.handler_callback_server.listen_port : null);
122 124
123 if (listenPort) { 125 if (listenPort) {
124 app.listen(listenPort, () => { 126 app.listen(listenPort, () => {
125 logger.info('HTTP Handler Callback server listening on port ' + listenPort); 127 logger.info('HTTP Handler Callback server listening on port ' + listenPort);
126 }); 128 });
127 } else { 129 } else {
128 logger.warn('Undefined config.apiserver.listen_port for APISERVER. Not listening for command.'); 130 logger.warn('Undefined config.apiserver.listen_port for APISERVER. Not listening for command.');
129 } 131 }
130 132
1 "use strict"; 1 "use strict";
2 2
3 const MAX_SMS_LENGTH = 160; 3 const MAX_SMS_LENGTH = 140;
4 4
5 const url = require('url'); 5 const url = require('url');
6 const request = require('request'); 6 const request = require('request');
7 const uuidv4 = require('uuid/v4'); 7 const uuidv4 = require('uuid/v4');
8 const moment = require('moment'); 8 const moment = require('moment');
9 9
10 const config = require('komodo-sdk/config'); 10 const config = require('komodo-sdk/config');
11 const logger = require('komodo-sdk/logger'); 11 const logger = require('komodo-sdk/logger');
12 12
13 const messagingService = require('komodo-center-messaging-client-lib'); 13 const messagingService = require('komodo-center-messaging-client-lib');
14 14
15 const common = require('./common'); 15 const common = require('./common');
16 const modems = require('./modems2'); 16 const modems = require('./modems2');
17 const modemChooser = require('./modem-chooser'); 17 const modemChooser = require('./modem-chooser');
18 // const partnerLastSeen = require('./partner-last-seen'); 18 // const partnerLastSeen = require('./partner-last-seen');
19 const history = require('./history'); 19 const history = require('./history');
20 const prefixes = require('./prefixes'); 20 const prefixes = require('./prefixes');
21 const truncate = require('./truncate-paragraph');
21 22
22 function _send(destinationNumber, msg, handlerIMSI) { 23 function _send(destinationNumber, msg, handlerIMSI) {
23 24
24 if (msg.length > 160) { 25 if (msg.length > MAX_SMS_LENGTH) {
25 logger.info('Splitting message'); 26 logger.info('Splitting message');
26 27
28 /*
27 const newMsg = msg.slice(0, MAX_SMS_LENGTH); 29 const newMsg = msg.slice(0, MAX_SMS_LENGTH);
28 const remainingMsg = msg.slice(MAX_SMS_LENGTH); 30 const remainingMsg = msg.slice(MAX_SMS_LENGTH);
31 */
32
33 const [newMsg, remainingMsg] = truncate(msg, MAX_SMS_LENGTH);
34 logger.verbose('TRANSPORT: Truncate long message', {maxLength: MAX_SMS_LENGTH, original: msg, head: newMsg, tail: remainingMsg});
29 35
30 _send(destinationNumber, newMsg, handlerIMSI); 36 _send(destinationNumber, newMsg, handlerIMSI);
31 setTimeout(() => { 37 setTimeout(() => {
32 _send(destinationNumber, remainingMsg, handlerIMSI); 38 _send(destinationNumber, remainingMsg, handlerIMSI);
33 }, 1000); 39 }, 1000);
34 40
35 return; 41 return;
36 } 42 }
37 43
38 const modem = modems.get('imsi', handlerIMSI); 44 const modem = modems.get('imsi', handlerIMSI);
39 if (!modem) { 45 if (!modem) {
40 logger.warn('Not knowing modem to use. Ignoring message', { destination_number: destinationNumber, msg: msg, modem_imsi: handlerIMSI }); 46 logger.warn('Not knowing modem to use. Ignoring message', { destination_number: destinationNumber, msg: msg, modem_imsi: handlerIMSI });
41 return; 47 return;
42 } 48 }
43 49
44 if (!modem.reportIp || !modem.reportPort || !modem.reportApikey) { 50 if (!modem.reportIp || !modem.reportPort || !modem.reportApikey) {
45 logger.warn('Invalid modem configuration', { modem }); 51 logger.warn('Invalid modem configuration', { modem });
46 return; 52 return;
47 } 53 }
48 54
49 const reqId = uuidv4(); 55 const reqId = uuidv4();
50 56
51 history.push({ 57 history.push({
52 ts: moment().format('YYYY-MM-DD HH:mm:ss'), 58 ts: moment().format('YYYY-MM-DD HH:mm:ss'),
53 modem: { 59 modem: {
54 name: modem.name, 60 name: modem.name,
55 imsi: modem.imsi, 61 imsi: modem.imsi,
56 msisdn: modem.msisdn, 62 msisdn: modem.msisdn,
57 }, 63 },
58 direction: 'OUTGOING', 64 direction: 'OUTGOING',
59 partner: destinationNumber, 65 partner: destinationNumber,
60 message: msg, 66 message: msg,
61 }); 67 });
62 68
63 logger.verbose('TRANSPORT: saving outgoing message'); 69 logger.verbose('TRANSPORT: saving outgoing message');
64 messagingService.onIncomingMessage({ 70 messagingService.onIncomingMessage({
65 me: modem.name, 71 me: modem.name,
66 partner: destinationNumber, 72 partner: destinationNumber,
67 partner_raw: `+${destinationNumber}`.replace(/^\++/, '+'), 73 partner_raw: `+${destinationNumber}`.replace(/^\++/, '+'),
68 msg: msg, 74 msg: msg,
69 origin_label: modem.imsi || 'UNKNOWN', 75 origin_label: modem.imsi || 'UNKNOWN',
70 origin_transport: 'SMS', 76 origin_transport: 'SMS',
71 origin_partner: destinationNumber, 77 origin_partner: destinationNumber,
72 do_not_forward_to_core: true, 78 do_not_forward_to_core: true,
73 is_outgoing: true, 79 is_outgoing: true,
74 }); 80 });
75 81
76 const requestOptions = { 82 const requestOptions = {
77 url: url.format({ 83 url: url.format({
78 protocol: 'http', 84 protocol: 'http',
79 hostname: modem.reportIp, 85 hostname: modem.reportIp,
80 port: modem.reportPort, 86 port: modem.reportPort,
81 pathname: modem.reportPathSms || '/sms', 87 pathname: modem.reportPathSms || '/sms',
82 }), 88 }),
83 qs: { 89 qs: {
84 msg: msg, 90 msg: msg,
85 number: destinationNumber, 91 number: destinationNumber,
86 reqid: reqId, 92 reqid: reqId,
87 apikey: modem.reportApikey, 93 apikey: modem.reportApikey,
88 } 94 }
89 } 95 }
90 96
91 logger.info('Sending message to modem handler', { req_id: reqId, destination_number: destinationNumber, msg: msg, msg_length: msg.length, modem_name: modem.name, modem_imsi: modem.imsi }); 97 logger.info('Sending message to modem handler', { req_id: reqId, destination_number: destinationNumber, msg: msg, msg_length: msg.length, modem_name: modem.name, modem_imsi: modem.imsi });
92 request(requestOptions, function(err, res, body) { 98 request(requestOptions, function(err, res, body) {
93 if (err) { 99 if (err) {
94 logger.warn('Error requesting to modem handler. ' + err.toString(), { req_id: reqId, modem_name: modem.name, modem_imsi: modem.imsi }); 100 logger.warn('Error requesting to modem handler. ' + err.toString(), { req_id: reqId, modem_name: modem.name, modem_imsi: modem.imsi });
95 101
96 } 102 }
97 else if (res.statusCode != 200) { 103 else if (res.statusCode != 200) {
98 logger.warn('Modem handler not responding with HTTP status code 200.', { http_status_code: res.statusCode, req_id: reqId, modem_name: modem.name, modem_imsi: modem.imsi }); 104 logger.warn('Modem handler not responding with HTTP status code 200.', { http_status_code: res.statusCode, req_id: reqId, modem_name: modem.name, modem_imsi: modem.imsi });
99 } 105 }
100 else { 106 else {
101 logger.verbose('Message sent to handler', { req_id: reqId, modem_name: modem.name, modem_imsi: modem.imsi, response_body: body }); 107 logger.verbose('Message sent to handler', { req_id: reqId, modem_name: modem.name, modem_imsi: modem.imsi, response_body: body });
102 } 108 }
103 }) 109 })
104 110
105 } 111 }
106 112
107 async function send(partner, msg) { 113 async function send(partner, msg) {
108 if (!partner) return; 114 if (!partner) return;
109 115
110 if (typeof msg !== 'string') { 116 if (typeof msg !== 'string') {
111 logger.warn('Message to send is not a string, ignoring message'); 117 logger.warn('Message to send is not a string, ignoring message');
112 return; 118 return;
113 } 119 }
114 120
115 msg = msg.trim(); 121 msg = msg.trim();
116 if (!msg) return; 122 if (!msg) return;
117 123
118 const destinationNumber = common.removeSuffixFromNumber(partner, config.number_suffix); 124 const destinationNumber = common.removeSuffixFromNumber(partner, config.number_suffix);
119 const prefixName = await prefixes.lookup(destinationNumber); 125 const prefixName = await prefixes.lookup(destinationNumber);
120 logger.verbose('Destination number prefix lookup', {partner: destinationNumber, prefix: prefixName}); 126 logger.verbose('Destination number prefix lookup', {partner: destinationNumber, prefix: prefixName});
121 127
122 // logger.verbose('Choosing handler name', { partner, destinationNumber, msg, origin }); 128 // logger.verbose('Choosing handler name', { partner, destinationNumber, msg, origin });
123 // const handlerIMSI = await partnerLastSeen.get(destinationNumber) ; 129 // const handlerIMSI = await partnerLastSeen.get(destinationNumber) ;
124 const handlerIMSI = await modemChooser.chooser(destinationNumber, config); 130 const handlerIMSI = await modemChooser.chooser(destinationNumber, config);
125 131
126 if (!handlerIMSI) { 132 if (!handlerIMSI) {
127 logger.warn(`Unknown handler for sending message to partner`, { partner, destinationNumber }); 133 logger.warn(`Unknown handler for sending message to partner`, { partner, destinationNumber });
128 return; 134 return;
129 } 135 }
130 136
131 _send(destinationNumber, msg, handlerIMSI); 137 _send(destinationNumber, msg, handlerIMSI);
132 } 138 }
133 139
134 exports.send = send; 140 exports.send = send;
lib/truncate-paragraph/index.js
File was created 1 const DEBUG = process.env['DEBUG'];
2
3 const validator = require('./validator');
4
5 function reverseString(str) {
6 return str.split('').reverse().join('');
7 }
8
9 module.exports = (src, maxLength) => {
10 const head = src.slice(0, maxLength);
11 const tail = src.slice(maxLength);
12
13 if (validator(head, tail)) {
14 if (DEBUG) console.log(`TRUNCATE-PARAGRAPH: straight result src='${src}' maxLength=${maxLength}`);
15 return [head.trim(), tail.trim()];
16 }
17
18 const reverseI = reverseString(head).search(/[\n., ;-]/);
19 if (reverseI === -1) {
20 // if (DEBUG) console.log(`TRUNCATE-PARAGRAPH: out of boundary src='${src}' maxLength=${maxLength}`);
21 return [head.trim(), tail.trim()];
22 }
23
24 const i = src.slice(0, maxLength - reverseI).trim().length;
25 // if (DEBUG) console.log(`TRUNCATE-PARAGRAPH: calculated src='${src}' maxLength=${maxLength} i=${i}`);
26 return [
27 src.slice(0, i).trim(),
28 src.slice(i).trim(),
29 ]
30 }
31
lib/truncate-paragraph/validator.js
File was created 1 function isSeparator(char) {
2 return char.search(/[\n., ;]/) === 0;
3 }
4
5 function isSeparatorHead(char) {
6 return char.search(/-/) === 0 || isSeparator(char)
7 }
8
9 module.exports = (head, tail) => {
10 return isSeparatorHead(head[head.length - 1]) || isSeparator(tail[0]);
11 };
12
test/modems.js
1 "use strict"; File was deleted
2
3 /* global describe it */
4
5 const should = require('should');
6
7 const modemSelect = require('../lib/modemSelect');
8
9 describe('#modemSelect', function() {
10
11 describe('#getModemUrl', function() {
12
13 const modemsConfig = {
14 SMS0: {
15 url: "http://localhost:8888/"
16 }
17 }
18
19 it('should return correct url', function() {
20 modemSelect.getModemUrl('SMS0', modemsConfig).should.equal('http://localhost:8888/');
21 })
22
23 it ('should handle missing modems', function() {
24 should.not.exists(modemSelect.getModemUrl('SMS0', null));
25 should.not.exists(modemSelect.getModemUrl('SMS0', {}));
26 should.not.exists(modemSelect.getModemUrl('SMS1', modemsConfig));
27 })
28 })
29
30 describe('#removeSuffixFromNumber', function() {
31 const config = {
32 number_suffix: '@phonenumber'
33 }
34
35 it('should return correct number', function() {
36 modemSelect.removeSuffixFromNumber('08181234@phonenumber', config).should.equal('08181234');
37 })
38
39 it ('should return correct number without suffix in the number', function() {
40 modemSelect.removeSuffixFromNumber('08181234', config).should.equal('08181234');
41 })
42
43 it ('should return correct number without suffix in config', function() {
44 modemSelect.removeSuffixFromNumber('08181234', null).should.equal('08181234');
45 modemSelect.removeSuffixFromNumber('08181234', {}).should.equal('08181234');
46 modemSelect.removeSuffixFromNumber('08181234@phonenumber', {}).should.equal('08181234');
47 })
48 })
49
50 })
File was created 1 "use strict";
2
3 /* global describe it */
4
5 const should = require('should');
6
7 const modemSelect = require('../lib/modemSelect');
8
9 describe('#modemSelect', function() {
10
11 describe('#getModemUrl', function() {
12
13 const modemsConfig = {
14 SMS0: {
15 url: "http://localhost:8888/"
16 }
17 }
18
19 it('should return correct url', function() {
20 modemSelect.getModemUrl('SMS0', modemsConfig).should.equal('http://localhost:8888/');
21 })
22
23 it ('should handle missing modems', function() {
24 should.not.exists(modemSelect.getModemUrl('SMS0', null));
25 should.not.exists(modemSelect.getModemUrl('SMS0', {}));
26 should.not.exists(modemSelect.getModemUrl('SMS1', modemsConfig));
27 })
28 })
29
30 describe('#removeSuffixFromNumber', function() {
31 const config = {
32 number_suffix: '@phonenumber'
33 }
34
35 it('should return correct number', function() {
36 modemSelect.removeSuffixFromNumber('08181234@phonenumber', config).should.equal('08181234');
37 })
38
39 it ('should return correct number without suffix in the number', function() {
40 modemSelect.removeSuffixFromNumber('08181234', config).should.equal('08181234');
41 })
42
43 it ('should return correct number without suffix in config', function() {
44 modemSelect.removeSuffixFromNumber('08181234', null).should.equal('08181234');
45 modemSelect.removeSuffixFromNumber('08181234', {}).should.equal('08181234');
46 modemSelect.removeSuffixFromNumber('08181234@phonenumber', {}).should.equal('08181234');
47 })
48 })
49
50 })
test/truncate-paragraph.js
File was created 1 /* global describe it */
2
3 require('should');
4 const validator = require('../lib/truncate-paragraph/validator');
5 const truncate = require('../lib/truncate-paragraph');
6
7
8 describe('#truncate-paragraph', () => {
9
10 describe('#validator', () => {
11 it('should return correctly', () => {
12 validator('ada', 'aja').should.equal(false);
13 validator('ada', ' aja').should.equal(true);
14 validator('ada-', 'aja').should.equal(true);
15 });
16 })
17
18 it('should return correctly - 1', () => {
19 const [head, tail] = truncate('12345 12', 5);
20
21 // console.log([head, tail]);
22
23 head.should.be.ok();
24 tail.should.be.ok();
25 });
26
27 it('should return correctly - 2', () => {
28 truncate('12345 123', 5)[0].should.equal('12345');
29 truncate('12345 123', 5)[1].should.equal('123');
30
31 truncate('12345 789', 7)[0].should.equal('12345', 'head 12345 789, 7');
32 truncate('12345 789', 7)[0].length.should.be.belowOrEqual(7, 'head length 12345 789, 7');
33 truncate('12345 789', 7)[1].should.equal('789', 'tail 12345 789, 7');
34
35 truncate('12345 789 12345678', 10)[0].should.equal('12345 789', 'head 12345 789 12345678, 10');
36 truncate('12345 789 12345678', 10)[0].length.should.be.belowOrEqual(10, 'head length 12345 789 12345678, 10');
37 truncate('12345 789 12345678', 10)[1].should.equal('12345678', 'tail 12345 789 12345678, 10');
38
39 truncate('12345-6789', 7)[0].should.equal('12345-');
40 truncate('12345-6789', 7)[1].should.equal('6789');
41
42 // truncate('Downline anda:\n#28 ABC CELL. Saldo: Rp. 4.500.000\n#35 BAYARKILAT. Saldo: Rp. 1.926.500\n#47 COBA1907011835. Saldo: Rp. 0\n#58 COBADARIMESSAGING. Saldo: Rp. 0\n#59 COBADARIMESSAGING1. Saldo: Rp. 0\n#60 COBADARIMESSAGING2. Saldo: Rp. 0\n#61 COBADARIMESSAGING3. Saldo: Rp. 0\n#62 COBADARIMESSAGING4. Saldo: Rp. 0\n#63 COBADARIMESSAGING5. Saldo: Rp. 0\n#48 COBADEFAULTMARKUP. Saldo: Rp. 0\n#49 COBADEFMARKUP2. Saldo: Rp. 0\n#50 COBADEFMARKUP3. Saldo: Rp. 0\n#51 COBADEFMARKUP4. Saldo: Rp. 0\n#52 COBADEFMARKUP5. Saldo: Rp. 0\n#53 COBADEFMARKUP6. Saldo: Rp. 0\n#54 COBADEFMARKUP7. Saldo: Rp. 0\n#55 COBADEFMARKUP8. Saldo: Rp. 0\n#56 COBADEFMARKUP9. Saldo: Rp. 0\n#57 COBADEFMARKUP99. Saldo: Rp. 0\n#36 DEMO. Saldo: Rp. 0\n#38 DUMMY. Saldo: Rp. 4.994.500\n#46 DUMMY13410. Saldo: Rp. 0\n#45 EVO. Saldo: Rp. 170.000\n#40 GIRASTEKDEV. Saldo: Rp. 1.000.000\n#39 HIKE. Saldo: Rp. 8.100.025\n#37 HOKISTORE. Saldo: Rp. 1.542.000\n#41 JPUDEV. Saldo: Rp. 1.000.000\n#30 KISELDEV. Saldo: Rp. 4.789.500\n#44 MELON SANDBOX. Saldo: Rp. 0\n#34 PASARSELON. Saldo: Rp. 0\n#23 PM0. Saldo: Rp. 664.700\n#27 RELOAD97. Saldo: Rp. 136.683.515\n#21 STORE2. Saldo: Rp. 664.700\n#2 TEST. Saldo: Rp. 664.700\n#3 TEST1. Saldo: Rp. 664.700\n#17 TEST10. Saldo: Rp. 664.700\n#8 TEST4. Saldo: Rp. 664.700\n#10 TEST5. Saldo: Rp. 664.700\n#12 TEST6. Saldo: Rp. 80.664.700\n#13 TEST7. Saldo: Rp. 664.700\n#14 TEST8. Saldo: Rp. 664.700\n#19 TOKOBAGUS. Saldo: Rp. 664.700\n#20 TOKOCUMI. Saldo: Rp. 664.700\n#43 VERSA. Saldo: Rp. 16.780.480\n#42 VERSA DEV. Saldo: Rp. 445.000\n#22 ZSTORE1. Saldo: Rp. 664.700\n#66 coba0717. Saldo: Rp. 0\n#67 coba0826. Saldo: Rp. 0\n#85 coba08261000. Saldo: Rp. 0\n#75 coba08261305. Saldo: Rp. 0\n#76 coba08261401. Saldo: Rp. 0\n#78 coba08261448. Saldo: Rp. 0\n#79 coba08261449m25. Saldo: Rp. 0\n#87 coba08261518. Saldo: Rp. 0\n#88 coba08261519. Saldo: Rp. 0\n#89 coba08261520. Saldo: Rp. 0\n#90 coba08261924. Saldo: Rp. 0\n#92 coba08261925. Saldo: Rp. 0\n#95 coba08261933. Saldo: Rp. 0\n#96 coba08261937. Saldo: Rp. 0\n#97 coba08261939. Saldo: Rp. 0\n#98 coba08261944. Saldo: Rp. 0\n#99 coba08261945. Saldo: Rp. 0\n#100 coba08261947. Saldo: Rp. 0\n#101 coba08262003. Saldo: Rp. 0\n#102 coba082620031. Saldo: Rp. 0\n#80 coba0826cekdefm. Saldo: Rp. 0\n#77 coba0826markup25. Saldo: Rp. 0\n#82 coba0826ms. Saldo: Rp. 0\n#81 coba0826overm. Saldo: Rp. 0\n#86 coba0826tele. Saldo: Rp. 0\n#64 cumi678. Saldo: Rp. 518.215","head":"Downline anda:\n#28 ABC CELL. Saldo: Rp. 4.500.000\n#35 BAYARKILAT. Saldo: Rp. 1.926.500\n#47 COBA1907011835. Saldo', 140)[0].should.equal('ad')
43 })
44 });
45