From 2a4f8e3ff9ff5c28d4a14354f0210941e6d961e1 Mon Sep 17 00:00:00 2001 From: Dave Horton Date: Wed, 10 Aug 2022 14:13:25 +0200 Subject: [PATCH] Simplify test (#145) * feat: add create-call timeout test * feat: single webhook-test-scaffold and basic auth callhook testcase * cleanup Co-authored-by: Quan Luu Co-authored-by: xquanluu <110280845+xquanluu@users.noreply.github.com> --- test/create-call-test.js | 61 +++++++++++-- test/docker-compose-testbed.yaml | 54 +---------- test/gather-tests.js | 23 ++++- test/say-tests.js | 17 +++- test/scenarios/uac-expect-603.xml | 5 +- .../uac-gather-account-creds-success.xml | 16 ++-- .../scenarios/uac-send-info-during-dialog.xml | 16 ++-- ...ccess.xml => uac-success-received-bye.xml} | 16 ++-- test/sip-request-tests.js | 26 +++++- test/sipp.js | 6 +- test/utils.js | 18 ++++ test/webhook/app.js | 90 ++++++++++++++----- test/webhooks-tests.js | 17 +++- 13 files changed, 245 insertions(+), 120 deletions(-) rename test/scenarios/{uac-say-account-creds-success.xml => uac-success-received-bye.xml} (78%) create mode 100644 test/utils.js diff --git a/test/create-call-test.js b/test/create-call-test.js index 8325acfb..b64107d1 100644 --- a/test/create-call-test.js +++ b/test/create-call-test.js @@ -1,10 +1,9 @@ const test = require('tape'); const { sippUac } = require('./sipp')('test_fs'); const bent = require('bent'); -const getJSON = bent('json') const clearModule = require('clear-module'); -const HttpRequestor = require('../lib/utils/http-requestor'); - +const provisionCallHook = require('./utils') +const getJSON = bent('json') process.on('unhandledRejection', (reason, p) => { console.log('Unhandled Rejection at: Promise', p, 'reason:', reason); @@ -24,8 +23,9 @@ test('test create-call timeout', async(t) => { try { await connect(srf); - let account_sid = '622f62e4-303a-49f2-bbe0-eb1e1714e37a' - const post = bent('http://127.0.0.1:3000/', 'POST', 'json', 201) + // GIVEN + let account_sid = '622f62e4-303a-49f2-bbe0-eb1e1714e37a'; + const post = bent('http://127.0.0.1:3000/', 'POST', 'json', 201); post('v1/createCall', { 'account_sid':account_sid, 'timeout': 1, @@ -37,7 +37,8 @@ test('test create-call timeout', async(t) => { "to": { "type": "phone", "number": "15583084809" - }}) + }}); + //THEN await sippUac('uas-timeout-cancel.xml', '172.38.0.10'); disconnect(); } catch (err) { @@ -47,6 +48,48 @@ test('test create-call timeout', async(t) => { } }); -function sleep(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); -} +test('test create-call call-hook basic authentication', async(t) => { + clearModule.all(); + const {srf, disconnect} = require('../app'); + + try { + await connect(srf); + // GIVEN + let from = 'call_hook_basic_authentication'; + let account_sid = '622f62e4-303a-49f2-bbe0-eb1e1714e37a'; + + const post = bent('http://127.0.0.1:3000/', 'POST', 'json', 201); + post('v1/createCall', { + 'account_sid':account_sid, + "call_hook": { + "url": "http://127.0.0.1:3100/", + "method": "POST", + "username": "username", + "password": "password" + }, + "from": from, + "to": { + "type": "phone", + "number": "15583084809" + }}); + + let verbs = [ + { + "verb": "say", + "text": "hello" + } + ]; + provisionCallHook(from, verbs); + //THEN + await sippUac('uas.xml', '172.38.0.10', from); + + let obj = await getJSON(`http:127.0.0.1:3100/lastRequest/${from}`) + t.ok(obj.headers.Authorization = 'Basic dXNlcm5hbWU6cGFzc3dvcmQ=', + 'create-call: call-hook contains basic authentication header'); + disconnect(); + } catch (err) { + console.log(`error received: ${err}`); + disconnect(); + t.error(err); + } +}); diff --git a/test/docker-compose-testbed.yaml b/test/docker-compose-testbed.yaml index 5e46cb5f..608dd93d 100644 --- a/test/docker-compose-testbed.yaml +++ b/test/docker-compose-testbed.yaml @@ -12,7 +12,7 @@ services: platform: linux/x86_64 ports: - "3360:3306" - environment: + environment: MYSQL_ALLOW_EMPTY_PASSWORD: "yes" healthcheck: test: ["CMD", "mysqladmin" ,"ping", "-h", "127.0.0.1", "--protocol", "tcp"] @@ -75,10 +75,8 @@ services: fs: ipv4_address: 172.38.0.51 - webhook-decline: + webhook-scaffold: image: jambonz/webhook-test-scaffold:latest - environment: - APP_PATH: /tmp/decline.json ports: - "3100:3000/tcp" volumes: @@ -87,54 +85,6 @@ services: fs: ipv4_address: 172.38.0.60 - webhook-say: - image: jambonz/webhook-test-scaffold:latest - environment: - APP_PATH: /tmp/say.json - ports: - - "3101:3000/tcp" - volumes: - - ./test-apps:/tmp - networks: - fs: - ipv4_address: 172.38.0.61 - - webhook-gather: - image: jambonz/webhook-test-scaffold:latest - environment: - APP_PATH: /tmp/gather.json - ports: - - "3102:3000/tcp" - volumes: - - ./test-apps:/tmp - networks: - fs: - ipv4_address: 172.38.0.62 - - webhook-transcribe: - image: jambonz/webhook-test-scaffold:latest - environment: - APP_PATH: /tmp/transcribe.json - ports: - - "3103:3000/tcp" - volumes: - - ./test-apps:/tmp - networks: - fs: - ipv4_address: 172.38.0.63 - - webhook-sip-info: - image: jambonz/webhook-test-scaffold:latest - environment: - APP_PATH: /tmp/info.json - ports: - - "3104:3000/tcp" - volumes: - - ./test-apps:/tmp - networks: - fs: - ipv4_address: 172.38.0.64 - influxdb: image: influxdb:1.8 ports: diff --git a/test/gather-tests.js b/test/gather-tests.js index db61d2c4..5f67b5af 100644 --- a/test/gather-tests.js +++ b/test/gather-tests.js @@ -3,6 +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') process.on('unhandledRejection', (reason, p) => { console.log('Unhandled Rejection at: Promise', p, 'reason:', reason); @@ -22,9 +23,25 @@ test('\'gather\' and \'transcribe\' tests', async(t) => { try { await connect(srf); - await sippUac('uac-gather-account-creds-success.xml', '172.38.0.10'); - let obj = await getJSON('http://127.0.0.1:3102/actionHook'); - t.ok(obj.speech.alternatives[0].transcript = 'I\'d like to speak to customer support', + // GIVEN + let verbs = [ + { + "verb": "gather", + "input": ["speech"], + "recognizer": { + "vendor": "google", + "hints": ["customer support", "sales", "human resources", "HR"] + }, + "timeout": 10, + "actionHook": "/actionHook" + } + ]; + let from = "gather_success"; + provisionCallHook(from, verbs); + // THEN + await sippUac('uac-gather-account-creds-success.xml', '172.38.0.10', from); + let obj = await getJSON(`http://127.0.0.1:3100/lastRequest/${from}_actionHook`); + t.ok(obj.body.speech.alternatives[0].transcript = 'I\'d like to speak to customer support', 'gather: succeeds when using account credentials'); disconnect(); diff --git a/test/say-tests.js b/test/say-tests.js index 359f3bbb..b7a2d414 100644 --- a/test/say-tests.js +++ b/test/say-tests.js @@ -1,6 +1,7 @@ const test = require('tape'); const { sippUac } = require('./sipp')('test_fs'); const clearModule = require('clear-module'); +const provisionCallHook = require('./utils') process.on('unhandledRejection', (reason, p) => { console.log('Unhandled Rejection at: Promise', p, 'reason:', reason); @@ -20,9 +21,21 @@ test('\'say\' tests', async(t) => { try { await connect(srf); - await sippUac('uac-say-account-creds-success.xml', '172.38.0.10'); + + // GIVEN + const verbs = [ + { + verb: 'say', + text: 'hello' + } + ]; + + const from = 'say_test_success'; + provisionCallHook(from, verbs) + + // THEN + await sippUac('uac-success-received-bye.xml', '172.38.0.10', from); t.pass('say: succeeds when using using account credentials'); - disconnect(); } catch (err) { console.log(`error received: ${err}`); diff --git a/test/scenarios/uac-expect-603.xml b/test/scenarios/uac-expect-603.xml index 5660a6ae..75364cce 100644 --- a/test/scenarios/uac-expect-603.xml +++ b/test/scenarios/uac-expect-603.xml @@ -7,7 +7,7 @@ INVITE sip:16174000000@[remote_ip]:[remote_port] SIP/2.0 Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch] - From: sipp ;tag=[pid]SIPpTag00[call_number] + From: [from] ;tag=[pid]SIPpTag00[call_number] To: Call-ID: [call_id] CSeq: 1 INVITE @@ -41,7 +41,7 @@ ACK sip:16174000000@[remote_ip]:[remote_port] SIP/2.0 [last_Via] - From: sipp ;tag=[pid]SIPpTag00[call_number] + From: [from] ;tag=[pid]SIPpTag00[call_number] To: [peer_tag_param] Call-ID: [call_id] CSeq: 1 ACK @@ -53,4 +53,3 @@ - diff --git a/test/scenarios/uac-gather-account-creds-success.xml b/test/scenarios/uac-gather-account-creds-success.xml index 126373fa..4e937c7e 100644 --- a/test/scenarios/uac-gather-account-creds-success.xml +++ b/test/scenarios/uac-gather-account-creds-success.xml @@ -8,13 +8,13 @@ ;tag=[pid]SIPpTag00[call_number] - To: + From: [from] ;tag=[pid]SIPpTag00[call_number] + To: Call-ID: [call_id] CSeq: 1 INVITE - Contact: sip:sipp@[local_ip]:[local_port] + Contact: sip:[from]@[local_ip]:[local_port] Max-Forwards: 70 X-Account-Sid: bb845d4b-83a9-4cde-a6e9-50f3743bab3f Subject: uac-gather-account-creds-success @@ -53,13 +53,13 @@ ;tag=[pid]SIPpTag00[call_number] - To: 16174000003 [peer_tag_param] + From: [from] ;tag=[pid]SIPpTag00[call_number] + To: [to] [peer_tag_param] Call-ID: [call_id] CSeq: 1 ACK - Contact: sip:sipp@[local_ip]:[local_port] + Contact: sip:[from]@[local_ip]:[local_port] Max-Forwards: 70 Subject: uac-gather-account-creds-success Content-Length: 0 diff --git a/test/scenarios/uac-send-info-during-dialog.xml b/test/scenarios/uac-send-info-during-dialog.xml index 04e933d8..64c0cb9d 100644 --- a/test/scenarios/uac-send-info-during-dialog.xml +++ b/test/scenarios/uac-send-info-during-dialog.xml @@ -8,13 +8,13 @@ ;tag=[pid]SIPpTag00[call_number] - To: + From: [from] ;tag=[pid]SIPpTag00[call_number] + To: Call-ID: [call_id] CSeq: 1 INVITE - Contact: sip:sipp@[local_ip]:[local_port] + Contact: sip:[from]@[local_ip]:[local_port] Max-Forwards: 70 X-Account-Sid: bb845d4b-83a9-4cde-a6e9-50f3743bab3f Subject: uac-gather-account-creds-success @@ -53,13 +53,13 @@ ;tag=[pid]SIPpTag00[call_number] - To: 16174000006 [peer_tag_param] + From: [from] ;tag=[pid]SIPpTag00[call_number] + To: [to] [peer_tag_param] Call-ID: [call_id] CSeq: 1 ACK - Contact: sip:sipp@[local_ip]:[local_port] + Contact: sip:[from]@[local_ip]:[local_port] Max-Forwards: 70 Subject: uac-gather-account-creds-success Content-Length: 0 diff --git a/test/scenarios/uac-say-account-creds-success.xml b/test/scenarios/uac-success-received-bye.xml similarity index 78% rename from test/scenarios/uac-say-account-creds-success.xml rename to test/scenarios/uac-success-received-bye.xml index af362b76..91e76971 100644 --- a/test/scenarios/uac-say-account-creds-success.xml +++ b/test/scenarios/uac-success-received-bye.xml @@ -8,13 +8,13 @@ ;tag=[pid]SIPpTag00[call_number] - To: + From: [from] ;tag=[pid]SIPpTag00[call_number] + To: Call-ID: [call_id] CSeq: 1 INVITE - Contact: sip:sipp@[local_ip]:[local_port] + Contact: sip:[from]@[local_ip]:[local_port] Max-Forwards: 70 X-Account-Sid: bb845d4b-83a9-4cde-a6e9-50f3743bab3f Subject: uac-say @@ -53,13 +53,13 @@ ;tag=[pid]SIPpTag00[call_number] - To: 16174000001 [peer_tag_param] + From: [from] ;tag=[pid]SIPpTag00[call_number] + To: [to] [peer_tag_param] Call-ID: [call_id] CSeq: 1 ACK - Contact: sip:sipp@[local_ip]:[local_port] + Contact: sip:[from]@[local_ip]:[local_port] Max-Forwards: 70 Subject: uac-say Content-Length: 0 diff --git a/test/sip-request-tests.js b/test/sip-request-tests.js index b9266eb2..17b36132 100644 --- a/test/sip-request-tests.js +++ b/test/sip-request-tests.js @@ -3,6 +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'); process.on('unhandledRejection', (reason, p) => { console.log('Unhandled Rejection at: Promise', p, 'reason:', reason); @@ -22,9 +23,28 @@ test('sending SIP in-dialog requests tests', async(t) => { try { await connect(srf); - await sippUac('uac-send-info-during-dialog.xml', '172.38.0.10'); - const obj = await getJSON('http://127.0.0.1:3104/actionHook'); - t.ok(obj.result === 'success' && obj.sip_status === 200, 'successfully sent SIP INFO'); + //GIVEN + let verbs = [ + { + "verb": "say", + "text": "hello" + }, + { + "verb": "sip:request", + "method": "info", + "headers": { + "Content-Type": "application/text" + }, + "body": "here I am ", + "actionHook": "/actionHook" + } + ]; + let from = "sip_indialog_test"; + provisionCallHook(from, verbs); + // THEN + await sippUac('uac-send-info-during-dialog.xml', '172.38.0.10', from); + const obj = await getJSON(`http://127.0.0.1:3100/lastRequest/${from}_actionHook`); + t.ok(obj.body.sip_status === 200, 'successfully sent SIP INFO'); disconnect(); } catch (err) { diff --git a/test/sipp.js b/test/sipp.js index 5c44926b..43dad554 100644 --- a/test/sipp.js +++ b/test/sipp.js @@ -24,7 +24,7 @@ obj.output = () => { return output; }; -obj.sippUac = (file, bindAddress) => { +obj.sippUac = (file, bindAddress, from='sipp', to='16174000000') => { const cmd = 'docker'; const args = [ 'run', '-t', '--rm', '--net', `${network}`, @@ -34,7 +34,9 @@ obj.sippUac = (file, bindAddress) => { '-sleep', '250ms', '-nostdin', '-cid_str', `%u-%p@%s-${idx++}`, - '172.38.0.50' + '172.38.0.50', + '-key','from', from, + '-key','to', to, '-trace_msg' ]; if (bindAddress) args.splice(5, 0, '--ip', bindAddress); diff --git a/test/utils.js b/test/utils.js new file mode 100644 index 00000000..62488897 --- /dev/null +++ b/test/utils.js @@ -0,0 +1,18 @@ +const bent = require('bent'); + +/* +* phoneNumber: 16174000000 +* Hook endpoints http://127.0.0.1:3100/ +* The function help testcase to register desired jambonz json response for an application call +* When a call has From number match the registered hook event, the desired jambonz json will be responded. +*/ +const provisionCallHook = (from, verbs) => { + const mapping = { + from, + data: JSON.stringify(verbs) + }; + const post = bent('http://127.0.0.1:3100', 'POST', 'string', 200); + post('/appMapping', mapping); +} + +module.exports = provisionCallHook diff --git a/test/webhook/app.js b/test/webhook/app.js index 4069b89a..e7295b12 100644 --- a/test/webhook/app.js +++ b/test/webhook/app.js @@ -3,47 +3,95 @@ const fs = require('fs'); const express = require('express'); const app = express(); const listenPort = process.env.HTTP_PORT || 3000; -let lastAction, lastEvent; - -assert.ok(process.env.APP_PATH, 'env var APP_PATH is required'); +let json_mapping = new Map(); +let hook_mapping = new Map(); app.listen(listenPort, () => { console.log(`sample jambones app server listening on ${listenPort}`); }); -const applicationData = JSON.parse(fs.readFileSync(process.env.APP_PATH)); app.use(express.urlencoded({ extended: true })); app.use(express.json()); +/* + * Markup language + */ app.all('/', (req, res) => { - console.log(applicationData, `${req.method} /`); - return res.json(applicationData); + 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); }); +app.post('/appMapping', (req, res) => { + console.log(req.body, 'POST /appMapping'); + json_mapping.set(req.body.from, req.body.data); + return res.sendStatus(200); +}); + +/* + * Status Callback + */ app.post('/callStatus', (req, res) => { console.log({payload: req.body}, 'POST /callStatus'); + let key = req.body.from + "_callStatus" + addRequestToMap(key, req, hook_mapping); return res.sendStatus(200); }); - +/* + * action Hook + */ app.post('/actionHook', (req, res) => { console.log({payload: req.body}, 'POST /actionHook'); - lastAction = req.body; + let key = req.body.from + "_actionHook" + addRequestToMap(key, req, hook_mapping); return res.sendStatus(200); }); -app.get('/actionHook', (req, res) => { - console.log({payload: lastAction}, 'GET /actionHook'); - return res.json(lastAction); -}); +// Fetch Requests +app.get('/requests/:key', (req, res) => { + let key = req.params.key; + if (hook_mapping.has(key)) { + return res.json(hook_mapping.get(key)); + } else { + return res.sendStatus(404); + } -app.post('/eventHook', (req, res) => { - console.log({payload: req.body}, 'POST /eventHook'); - lastEvent = req.body; - return res.sendStatus(200); -}); +}) -app.get('/eventHook', (req, res) => { - console.log({payload: lastEvent}, 'GET /eventHook'); - return res.json(lastEvent); -}); +app.get('/lastRequest/:key', (req, res) => { + let key = req.params.key; + if (hook_mapping.has(key)) { + let requests = hook_mapping.get(key); + return res.json(requests[requests.length - 1]); + } else { + return res.sendStatus(404); + } +}) + +/* + * private function + */ + +function addRequestToMap(key, req, map) { + let headers = new Map() + for(let i = 0; i < req.rawHeaders.length; i++) { + if (i % 2 === 0) { + headers.set(req.rawHeaders[i], req.rawHeaders[i + 1]) + } + } + let request = { + 'url': req.url, + 'headers': Object.fromEntries(headers), + 'body': req.body + } + if (map.has(key)) { + map.get(key).push(request); + } else { + map.set(key, [request]); + } +} diff --git a/test/webhooks-tests.js b/test/webhooks-tests.js index dbb456e0..638f546f 100644 --- a/test/webhooks-tests.js +++ b/test/webhooks-tests.js @@ -17,10 +17,25 @@ function connect(connectable) { test('basic webhook tests', async(t) => { clearModule.all(); const {srf, disconnect} = require('../app'); + const provisionCallHook = require('./utils') try { await connect(srf); - await sippUac('uac-expect-603.xml', '172.38.0.10'); + const verbs = [ + { + verb: 'sip:decline', + status: 603, + reason: 'Gone Fishin', + headers: { + 'Retry-After': 300 + } + } + ]; + + const from = 'sip_decline_test_success'; + provisionCallHook(from, verbs) + + await sippUac('uac-expect-603.xml', '172.38.0.10', from); t.pass('webhook successfully declines call'); disconnect();