From 127432f2ec2a4bcb2017832f15382e4c2c05353c Mon Sep 17 00:00:00 2001 From: xquanluu <110280845+xquanluu@users.noreply.github.com> Date: Thu, 25 Aug 2022 15:09:48 +0700 Subject: [PATCH] feat: play verb url support single or array list url (#158) * feat: update time-series 0.11.12 * feat: support play verb url in plain text or array * fix: review comment Co-authored-by: Dave Horton --- lib/tasks/conference.js | 3 + lib/tasks/play.js | 8 +- lib/tasks/specs.json | 8 +- test/create-call-test.js | 19 ++- test/gather-tests.js | 2 +- test/index.js | 1 + test/play-tests.js | 158 ++++++++++++++++++++++++ test/say-tests.js | 2 +- test/scenarios/uac-success-send-bye.xml | 92 ++++++++++++++ test/sip-request-tests.js | 2 +- test/sipp.js | 2 +- test/utils.js | 11 +- test/webhook/app.js | 32 ++++- test/webhooks-tests.js | 3 +- 14 files changed, 323 insertions(+), 20 deletions(-) create mode 100644 test/play-tests.js create mode 100644 test/scenarios/uac-success-send-bye.xml diff --git a/lib/tasks/conference.js b/lib/tasks/conference.js index 655706fb..2132a63e 100644 --- a/lib/tasks/conference.js +++ b/lib/tasks/conference.js @@ -541,6 +541,9 @@ class Conference extends Task { } this.logger.debug(`Conference:_playHook: executing ${tasks.length} tasks`); + /* we might have been killed while off fetching waitHook */ + if (this.killed) return []; + if (tasks.length > 0) { this._playSession = new ConfirmCallSession({ logger: this.logger, diff --git a/lib/tasks/play.js b/lib/tasks/play.js index edcf173a..7ac98eae 100644 --- a/lib/tasks/play.js +++ b/lib/tasks/play.js @@ -24,7 +24,13 @@ class TaskPlay extends Task { while (!this.killed && (this.loop === 'forever' || this.loop--) && this.ep.connected) { if (cs.isInConference) { const {memberId, confName, confUuid} = cs; - await this.playToConfMember(this.ep, memberId, confName, confUuid, this.url); + if (Array.isArray(this.url)) { + for (const playUrl of this.url) { + await this.playToConfMember(this.ep, memberId, confName, confUuid, playUrl); + } + } else { + await this.playToConfMember(this.ep, memberId, confName, confUuid, this.url); + } } else await ep.play(this.url); } diff --git a/lib/tasks/specs.json b/lib/tasks/specs.json index 903e2c5a..0c0df844 100644 --- a/lib/tasks/specs.json +++ b/lib/tasks/specs.json @@ -84,7 +84,7 @@ }, "leave": { "properties": { - + } }, "hangup": { @@ -96,7 +96,7 @@ }, "play": { "properties": { - "url": "string", + "url": "string|array", "loop": "number|string", "earlyMedia": "boolean" }, @@ -514,7 +514,7 @@ "properties": { "enable": "boolean", "voiceMs": "number", - "mode": "number" + "mode": "number" }, "required": [ "enable" @@ -536,7 +536,7 @@ "noSpeechTimeoutMs": "number", "decisionTimeoutMs": "number", "toneTimeoutMs": "number", - "greetingCompletionTimeoutMs": "number" + "greetingCompletionTimeoutMs": "number" } } } diff --git a/test/create-call-test.js b/test/create-call-test.js index b64107d1..917f0ec4 100644 --- a/test/create-call-test.js +++ b/test/create-call-test.js @@ -2,9 +2,11 @@ const test = require('tape'); const { sippUac } = require('./sipp')('test_fs'); const bent = require('bent'); const clearModule = require('clear-module'); -const provisionCallHook = require('./utils') +const {provisionCallHook} = require('./utils') const getJSON = bent('json') +const waitFor = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); + process.on('unhandledRejection', (reason, p) => { console.log('Unhandled Rejection at: Promise', p, 'reason:', reason); }); @@ -23,6 +25,11 @@ test('test create-call timeout', async(t) => { try { await connect(srf); + + // give UAS app time to come up + const p = sippUac('uas-timeout-cancel.xml', '172.38.0.10'); + await waitFor(1000); + // GIVEN let account_sid = '622f62e4-303a-49f2-bbe0-eb1e1714e37a'; const post = bent('http://127.0.0.1:3000/', 'POST', 'json', 201); @@ -39,7 +46,7 @@ test('test create-call timeout', async(t) => { "number": "15583084809" }}); //THEN - await sippUac('uas-timeout-cancel.xml', '172.38.0.10'); + await p; disconnect(); } catch (err) { console.log(`error received: ${err}`); @@ -54,10 +61,16 @@ test('test create-call call-hook basic authentication', async(t) => { try { await connect(srf); + + // GIVEN let from = 'call_hook_basic_authentication'; let account_sid = '622f62e4-303a-49f2-bbe0-eb1e1714e37a'; + // Give UAS app time to come up + const p = sippUac('uas.xml', '172.38.0.10', from); + await waitFor(1000); + const post = bent('http://127.0.0.1:3000/', 'POST', 'json', 201); post('v1/createCall', { 'account_sid':account_sid, @@ -81,7 +94,7 @@ test('test create-call call-hook basic authentication', async(t) => { ]; provisionCallHook(from, verbs); //THEN - await sippUac('uas.xml', '172.38.0.10', from); + await p; let obj = await getJSON(`http:127.0.0.1:3100/lastRequest/${from}`) t.ok(obj.headers.Authorization = 'Basic dXNlcm5hbWU6cGFzc3dvcmQ=', diff --git a/test/gather-tests.js b/test/gather-tests.js index 5f67b5af..c9679769 100644 --- a/test/gather-tests.js +++ b/test/gather-tests.js @@ -3,7 +3,7 @@ const { sippUac } = require('./sipp')('test_fs'); const bent = require('bent'); const getJSON = bent('json') const clearModule = require('clear-module'); -const provisionCallHook = require('./utils') +const {provisionCallHook} = require('./utils') process.on('unhandledRejection', (reason, p) => { console.log('Unhandled Rejection at: Promise', p, 'reason:', reason); diff --git a/test/index.js b/test/index.js index b0dbcf88..5f0bfdf4 100644 --- a/test/index.js +++ b/test/index.js @@ -7,5 +7,6 @@ require('./say-tests'); require('./gather-tests'); require('./sip-request-tests'); require('./create-call-test'); +require('./play-tests'); require('./remove-test-db'); require('./docker_stop'); diff --git a/test/play-tests.js b/test/play-tests.js new file mode 100644 index 00000000..3968e8cd --- /dev/null +++ b/test/play-tests.js @@ -0,0 +1,158 @@ +const test = require('tape'); +const { sippUac } = require('./sipp')('test_fs'); +const clearModule = require('clear-module'); +const {provisionCallHook, provisionCustomHook} = require('./utils') +const bent = require('bent'); +const getJSON = bent('json') + +process.on('unhandledRejection', (reason, p) => { + console.log('Unhandled Rejection at: Promise', p, 'reason:', reason); +}); + +function connect(connectable) { + return new Promise((resolve, reject) => { + connectable.on('connect', () => { + return resolve(); + }); + }); +} + +test('\'play\' tests single link in plain text', async(t) => { + clearModule.all(); + const {srf, disconnect} = require('../app'); + + try { + await connect(srf); + + // GIVEN + const verbs = [ + { + verb: 'play', + url: 'https://example.com/example.mp3' + } + ]; + + const from = 'play_single_link'; + provisionCallHook(from, verbs) + + // THEN + await sippUac('uac-success-received-bye.xml', '172.38.0.10', from); + t.pass('play: succeeds when using single link'); + disconnect(); + } catch (err) { + console.log(`error received: ${err}`); + disconnect(); + t.error(err); + } +}); + +test('\'play\' tests multi links in array', async(t) => { + clearModule.all(); + const {srf, disconnect} = require('../app'); + + try { + await connect(srf); + + // GIVEN + const verbs = [ + { + verb: 'play', + url: ['https://example.com/example.mp3', 'https://example.com/example.mp3'] + } + ]; + + const from = 'play_multi_links_in_array'; + provisionCallHook(from, verbs) + + // THEN + await sippUac('uac-success-received-bye.xml', '172.38.0.10', from); + t.pass('play: succeeds when using links in array'); + disconnect(); + } catch (err) { + console.log(`error received: ${err}`); + disconnect(); + t.error(err); + } +}); + +test('\'play\' tests single link in conference', async(t) => { + clearModule.all(); + const {srf, disconnect} = require('../app'); + + try { + await connect(srf); + + // GIVEN + const from = 'play_single_link_in_conference'; + const waitHookVerbs = [ + { + verb: 'play', + url: 'https://example.com/example.mp3' + } + ]; + + const verbs = [ + { + verb: 'conference', + name: `${from}`, + beep: true, + "startConferenceOnEnter": false, + waitHook: `/customHook` + } + ]; + provisionCustomHook(from, waitHookVerbs) + provisionCallHook(from, verbs) + + // THEN + await sippUac('uac-success-send-bye.xml', '172.38.0.10', from); + t.pass('play: succeeds when using in conference as single link'); + // Make sure that waitHook is called and success + await getJSON(`http:127.0.0.1:3100/lastRequest/${from}_customHook`) + disconnect(); + } catch (err) { + console.log(`error received: ${err}`); + disconnect(); + t.error(err); + } +}); + +test('\'play\' tests multi links in array in conference', async(t) => { + clearModule.all(); + const {srf, disconnect} = require('../app'); + + try { + await connect(srf); + + // GIVEN + const from = 'play_multi_links_in_conference'; + const waitHookVerbs = [ + { + verb: 'play', + url: ['https://example.com/example.mp3', 'https://example.com/example.mp3'] + } + ]; + + const verbs = [ + { + verb: 'conference', + name: `${from}`, + beep: true, + "startConferenceOnEnter": false, + waitHook: `/customHook` + } + ]; + provisionCustomHook(from, waitHookVerbs) + provisionCallHook(from, verbs) + + // THEN + await sippUac('uac-success-send-bye.xml', '172.38.0.10', from); + t.pass('play: succeeds when using in conference with multi links'); + // Make sure that waitHook is called and success + await getJSON(`http:127.0.0.1:3100/lastRequest/${from}_customHook`) + disconnect(); + } catch (err) { + console.log(`error received: ${err}`); + disconnect(); + t.error(err); + } +}); diff --git a/test/say-tests.js b/test/say-tests.js index b7a2d414..a099c771 100644 --- a/test/say-tests.js +++ b/test/say-tests.js @@ -1,7 +1,7 @@ const test = require('tape'); const { sippUac } = require('./sipp')('test_fs'); const clearModule = require('clear-module'); -const provisionCallHook = require('./utils') +const {provisionCallHook} = require('./utils') process.on('unhandledRejection', (reason, p) => { console.log('Unhandled Rejection at: Promise', p, 'reason:', reason); diff --git a/test/scenarios/uac-success-send-bye.xml b/test/scenarios/uac-success-send-bye.xml new file mode 100644 index 00000000..163a0897 --- /dev/null +++ b/test/scenarios/uac-success-send-bye.xml @@ -0,0 +1,92 @@ + + + + + + + + + ;tag=[pid]SIPpTag00[call_number] + To: + Call-ID: [call_id] + CSeq: 1 INVITE + Contact: sip:[from]@[local_ip]:[local_port] + Max-Forwards: 70 + X-Account-Sid: bb845d4b-83a9-4cde-a6e9-50f3743bab3f + Subject: uac-say + Content-Type: application/sdp + Content-Length: [len] + + v=0 + o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip] + s=- + c=IN IP[media_ip_type] [media_ip] + t=0 0 + m=audio [media_port] RTP/AVP 0 + a=rtpmap:0 PCMU/8000 + + ]]> + + + + + + + + + + + + + + + + + + + + + ;tag=[pid]SIPpTag00[call_number] + To: [to] [peer_tag_param] + Call-ID: [call_id] + CSeq: 1 ACK + Contact: sip:[from]@[local_ip]:[local_port] + Max-Forwards: 70 + Subject: uac-say + Content-Length: 0 + + ]]> + + + + + + + ;tag=[pid]SIPpTag00[call_number] + To: [to] [peer_tag_param] + Call-ID: [call_id] + CSeq: 2 BYE + Max-Forwards: 70 + Content-Length: 0 + + ]]> + + + + + + + diff --git a/test/sip-request-tests.js b/test/sip-request-tests.js index 17b36132..2b2b34bf 100644 --- a/test/sip-request-tests.js +++ b/test/sip-request-tests.js @@ -3,7 +3,7 @@ const { sippUac } = require('./sipp')('test_fs'); const bent = require('bent'); const getJSON = bent('json') const clearModule = require('clear-module'); -const provisionCallHook = require('./utils'); +const {provisionCallHook} = require('./utils') process.on('unhandledRejection', (reason, p) => { console.log('Unhandled Rejection at: Promise', p, 'reason:', reason); diff --git a/test/sipp.js b/test/sipp.js index 43dad554..7a4bb38e 100644 --- a/test/sipp.js +++ b/test/sipp.js @@ -63,7 +63,7 @@ obj.sippUac = (file, bindAddress, from='sipp', to='16174000000') => { addOutput(data.toString()); }); child_process.stdout.on('data', (data) => { - //console.log(`stdout: ${data}`); + // console.log(`stdout: ${data}`); addOutput(data.toString()); }); }); diff --git a/test/utils.js b/test/utils.js index 62488897..0a0d7626 100644 --- a/test/utils.js +++ b/test/utils.js @@ -15,4 +15,13 @@ const provisionCallHook = (from, verbs) => { post('/appMapping', mapping); } -module.exports = provisionCallHook +const provisionCustomHook = (from, verbs) => { + const mapping = { + from, + data: JSON.stringify(verbs) + }; + const post = bent('http://127.0.0.1:3100', 'POST', 'string', 200); + post(`/customHookMapping`, mapping); +} + +module.exports = { provisionCallHook, provisionCustomHook} diff --git a/test/webhook/app.js b/test/webhook/app.js index e7295b12..2db82d7f 100644 --- a/test/webhook/app.js +++ b/test/webhook/app.js @@ -20,11 +20,7 @@ app.use(express.json()); app.all('/', (req, res) => { console.log(req.body, 'POST /'); const key = req.body.from - if (!json_mapping.has(key)) return res.sendStatus(404); - const retData = JSON.parse(json_mapping.get(key)); - console.log(retData, `${req.method} /`); - addRequestToMap(key, req, hook_mapping); - return res.json(retData); + return getJsonFromMap(key, req, res); }); app.post('/appMapping', (req, res) => { @@ -52,6 +48,24 @@ app.post('/actionHook', (req, res) => { return res.sendStatus(200); }); +/* +* customHook +* For the hook to return + */ + +app.all('/customHook', (req, res) => { + let key = `${req.body.from}_customHook`;; + console.log(req.body, `POST /customHook`); + return getJsonFromMap(key, req, res); +}); + +app.post('/customHookMapping', (req, res) => { + let key = `${req.body.from}_customHook`; + console.log(req.body, `POST /customHookMapping`); + json_mapping.set(key, req.body.data); + return res.sendStatus(200); +}); + // Fetch Requests app.get('/requests/:key', (req, res) => { let key = req.params.key; @@ -77,6 +91,14 @@ app.get('/lastRequest/:key', (req, res) => { * private function */ +function getJsonFromMap(key, req, res) { + if (!json_mapping.has(key)) return res.sendStatus(404); + const retData = JSON.parse(json_mapping.get(key)); + console.log(retData, ` Response to ${req.method} ${req.url}`); + addRequestToMap(key, req, hook_mapping); + return res.json(retData); +} + function addRequestToMap(key, req, map) { let headers = new Map() for(let i = 0; i < req.rawHeaders.length; i++) { diff --git a/test/webhooks-tests.js b/test/webhooks-tests.js index 82074a32..cfc9cac3 100644 --- a/test/webhooks-tests.js +++ b/test/webhooks-tests.js @@ -1,7 +1,7 @@ const test = require('tape'); const { sippUac } = require('./sipp')('test_fs'); const clearModule = require('clear-module'); -const provisionCallHook = require('./utils'); +const {provisionCallHook} = require('./utils') const opts = { timestamp: () => {return `, "time": "${new Date().toISOString()}"`;}, level: process.env.JAMBONES_LOGLEVEL || 'info' @@ -26,7 +26,6 @@ function connect(connectable) { test('basic webhook tests', async(t) => { clearModule.all(); const {srf, disconnect} = require('../app'); - const provisionCallHook = require('./utils') try { await connect(srf);