Skip to content

Commit

Permalink
Merge pull request #6 from Bandwidth/SWI-3836
Browse files Browse the repository at this point in the history
SWI-3836 Update BXML Library and Add Integration Tests
  • Loading branch information
ckoegel authored Nov 28, 2023
2 parents e541462 + 0ce9300 commit 0c45900
Show file tree
Hide file tree
Showing 5 changed files with 329 additions and 1 deletion.
2 changes: 1 addition & 1 deletion custom_templates/package.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"scripts": {
"build": "tsc {{#supportsES6}}&& tsc -p tsconfig.esm.json{{/supportsES6}}",
"prepare": "npm run build",
"test": "jest --coverage --detectOpenHandles"
"test": "jest --detectOpenHandles"
},
"dependencies": {
"axios": "^0.27.2",
Expand Down
3 changes: 3 additions & 0 deletions models/bxml/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import * as Bxml from './verbs';

export { Bxml };
30 changes: 30 additions & 0 deletions models/bxml/verbs/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
export * from './Bridge';
export * from './Conference';
export * from './CustomParam';
export * from './Forward';
export * from './Gather';
export * from './Hangup';
export * from './Pause';
export * from './PauseRecording';
export * from './PhoneNumber';
export * from './PlayAudio';
export * from './Record';
export * from './Redirect';
export * from './ResumeRecording';
export * from './Ring';
export * from './SendDtmf';
export * from './SipUri';
export * from './SpeakSentence';
export * from './StartGather';
export * from './StartRecording';
export * from './StartStream';
export * from './StartTranscription';
export * from './StopGather';
export * from './StopRecording';
export * from './StopStream';
export * from './StopTranscription';
export * from './StreamParam';
export * from './Tag';
export * from './Transfer';
export * from '../Bxml';
export * from '../Response';
1 change: 1 addition & 0 deletions models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,4 @@ export * from './verify-code-request';
export * from './verify-code-response';
export * from './voice-api-error';
export * from './voice-code-response';
export * from './bxml';
294 changes: 294 additions & 0 deletions tests/integration/bxml.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,294 @@
const { CallsApi } = require('../../api/calls-api');
const { Configuration } = require('../../configuration');
const { Bxml, CallStateEnum } = require('../../models');
const { createMantecaCall, sleep, cleanupCalls } = require('../callUtils');

describe('BXML Integration Tests', () => {
jest.setTimeout(20000);

const config = new Configuration({username: BW_USERNAME, password: BW_PASSWORD});
const callsApi = new CallsApi(config);
let activeCalls = [];

afterAll(async () => {
await cleanupCalls(activeCalls, callsApi);
});

test('test one-off verbs', async () => {
const updateCallId = await createMantecaCall(callsApi);
const bridgeCallId = await createMantecaCall(callsApi);
activeCalls.push(updateCallId, bridgeCallId);
await sleep(SLEEP_TIME_S);

const conferenceAttributes = {
mute: true,
hold: true,
callIdsToCoach: updateCallId,
conferenceEventUrl: 'https://initial.com',
conferenceEventMethod: 'POST',
conferenceEventFallbackUrl: 'https://initial.com',
conferenceEventFallbackMethod: 'POST',
username: 'initialUsername',
password: 'initialPassword',
fallbackUsername: 'initialFallbackUsername',
fallbackPassword: 'initialFallbackPassword',
tag: 'initialTag',
callbackTimeout: 1.1
};

const gatherAttributes = {
gatherUrl: 'https://initial.com',
gatherMethod: 'POST',
gatherFallbackUrl: 'https://initial.com',
gatherFallbackMethod: 'POST',
username: 'initialUsername',
password: 'initialPassword',
fallbackUsername: 'initialFallbackUsername',
fallbackPassword: 'initialFallbackPassword',
tag: 'initialTag',
terminatingDigits: '5',
maxDigits: 1,
interDigitTimeout: 1.1,
firstDigitTimeout: 1.1,
repeatCount: 1
};

const speakSentenceAttributes = {
voice: 'julie',
gender: 'female',
locale: 'en_US'
};

const recordAttributes = {
recordCompleteUrl: 'https://initial.com',
recordCompleteMethod: 'POST',
recordCompleteFallbackUrl: 'https://initial.com',
recordCompleteFallbackMethod: 'POST',
recordingAvailableUrl: 'https://initial.com',
recordingAvailableMethod: 'POST',
transcribe: true,
transcriptionAvailableUrl: 'https://initial.com',
transcriptionAvailableMethod: 'POST',
username: 'initialUsername',
password: 'initialPassword',
fallbackUsername: 'initialFallbackUsername',
fallbackPassword: 'initialFallbackPassword',
tag: 'initialTag',
terminatingDigits: '#',
maxDuration: 2,
silenceTimeout: 2,
fileFormat: 'wav'
};

const sendDtmfAttributes = {
toneDuration: 50,
toneInterval: 50
};

const ringAttributes = {
duration: 5.1,
answerCall: true
};

const bridgeAttributes = {
bridgeCompleteUrl: 'https://initial.com',
bridgeCompleteMethod: 'POST',
bridgeCompleteFallbackUrl: 'https://initial.com',
bridgeCompleteFallbackMethod: 'POST',
bridgeTargetCompleteUrl: 'https://initial.com',
bridgeTargetCompleteMethod: 'POST',
bridgeTargetCompleteFallbackUrl: 'https://initial.com',
bridgeTargetCompleteFallbackMethod: 'POST',
username: 'initialUsername',
password: 'initialPassword',
fallbackUsername: 'initialFallbackUsername',
fallbackPassword: 'initialFallbackPassword',
tag: 'initialTag'
};

const transferAttributes = {
transferCallerId: '+19195551234',
callTimeout: 5,
transferCompleteUrl: 'https://initial.com',
transferCompleteMethod: 'POST',
transferCompleteFallbackUrl: 'https://initial.com',
transferCompleteFallbackMethod: 'POST',
username: 'initialUsername',
password: 'initialPassword',
fallbackUsername: 'initialFallbackUsername',
fallbackPassword: 'initialFallbackPassword',
tag: 'initialTag',
diversionTreatment: 'propagate',
diversionReason: 'user-busy'
};

const phoneNumber = new Bxml.PhoneNumber('+19195551234');
const sipUri = new Bxml.SipUri('sip:[email protected]');

const conference = new Bxml.Conference('my-conference', conferenceAttributes);
const gather = new Bxml.Gather(gatherAttributes);
const speakSentence = new Bxml.SpeakSentence('<lang xml:lang="es-MX">Hola</lang>nodejs speak sentence <emphasis>SSML test</emphasis>', speakSentenceAttributes);
const nestedGather = new Bxml.Gather(gatherAttributes, [speakSentence]);
const pause = new Bxml.Pause({ duration: 2 });
const playAudio = new Bxml.PlayAudio('https://www.example.com/file.wav', { username: 'initialUsername', password: 'initialPassword' });
const record = new Bxml.Record(recordAttributes);
const sendDtmf = new Bxml.SendDtmf('1234', sendDtmfAttributes);
const tag = new Bxml.Tag('testTag');
const ring = new Bxml.Ring(ringAttributes);
const hangup = new Bxml.Hangup();
const bridge = new Bxml.Bridge(bridgeCallId, bridgeAttributes);
const transfer = new Bxml.Transfer(transferAttributes, [phoneNumber, sipUri]);

const bxml = new Bxml.Response([tag, conference, speakSentence, gather, sendDtmf, nestedGather, pause, record, playAudio, sendDtmf, ring, bridge, transfer, hangup]);

const { status: updateStatus } =
await callsApi.updateCallBxml(BW_ACCOUNT_ID, updateCallId, bxml.toBxml());
expect(updateStatus).toEqual(204);
await sleep(SLEEP_TIME_S);

const { status: completeStatus } =
await callsApi.updateCall(BW_ACCOUNT_ID, updateCallId, { state: CallStateEnum.Completed });
expect(completeStatus).toEqual(200);
});

test('test start and stop verbs', async () => {
const updateCallId = await createMantecaCall(callsApi);
activeCalls.push(updateCallId);
await sleep(SLEEP_TIME_S);

const startGatherAttributes = {
dtmfUrl: 'https://initial.com',
dtmfMethod: 'POST',
username: 'initialUsername',
password: 'initialPassword',
tag: 'initialTag'
};

const startRecordingAttributes = {
recordingAvailableUrl: 'https://initial.com',
recordingAvailableMethod: 'POST',
transcribe: true,
transcriptionAvailableUrl: 'https://initial.com',
transcriptionAvailableMethod: 'POST',
username: 'initialUsername',
password: 'initialPassword',
tag: 'initialTag',
fileFormat: 'wav',
multiChannel: true
};

const startStreamAttributes = {
name: 'initialName',
tracks: 'inbound',
destination: 'wss://initial.com',
streamEventUrl: 'https://initial.com',
streamEventMethod: 'POST',
username: 'initialUsername',
password: 'initialPassword'
};

const startTranscriptionAttributes = {
name: 'initialName',
tracks: 'inbound',
transcriptionEventUrl: 'https://initial.com',
transcriptionEventMethod: 'POST',
username: 'initialUsername',
password: 'initialPassword',
destination: 'wss://initial.com',
stabilized: true
};

const stopStreamAttributes = {
name: 'initialName'
};

const stopTranscriptionAttributes = {
name: 'initialName'
};

const customParam1 = new Bxml.CustomParam({ name: 'customParamName1', value: 'customParamValue1' });
const streamParam1 = new Bxml.StreamParam({ name: 'streamParamName1', value: 'streamParamValue1' });

const startGather = new Bxml.StartGather(startGatherAttributes);
const startRecording = new Bxml.StartRecording(startRecordingAttributes);
const pauseRecording = new Bxml.PauseRecording();
const resumeRecording = new Bxml.ResumeRecording();
const startStream = new Bxml.StartStream(startStreamAttributes, [streamParam1]);
const startTranscription = new Bxml.StartTranscription(startTranscriptionAttributes, [customParam1]);
const stopGather = new Bxml.StopGather();
const stopRecording = new Bxml.StopRecording();
const stopStream = new Bxml.StopStream(stopStreamAttributes);
const stopTranscription = new Bxml.StopTranscription(stopTranscriptionAttributes);

const bxml = new Bxml.Bxml([startGather, stopGather, startRecording, pauseRecording, resumeRecording, stopRecording, startStream, stopStream, startTranscription, stopTranscription]);

const { status: updateStatus } =
await callsApi.updateCallBxml(BW_ACCOUNT_ID, updateCallId, bxml.toBxml());
expect(updateStatus).toEqual(204);
await sleep(SLEEP_TIME_S);

const { status: completeStatus } =
await callsApi.updateCall(BW_ACCOUNT_ID, updateCallId, { state: CallStateEnum.Completed });
expect(completeStatus).toEqual(200);
});

test('test forward', async () => {
const updateCallId = await createMantecaCall(callsApi);
activeCalls.push(updateCallId);
await sleep(SLEEP_TIME_S);

const forwardAttributes = {
to: '+19195551234',
from: '+19195554321',
callTimeout: 5,
diversionTreatment: 'propagate',
diversionReason: 'user-busy',
uui: '93d6f3c0be5845960b744fa28015d8ede84bd1a4;encoding=base64,asdf;encoding=jwt'
};

const forward = new Bxml.Forward(forwardAttributes);

const bxml = new Bxml.Response([forward]);

const { status: updateStatus } =
await callsApi.updateCallBxml(BW_ACCOUNT_ID, updateCallId, bxml.toBxml());
expect(updateStatus).toEqual(204);
await sleep(SLEEP_TIME_S);

const { status: completeStatus } =
await callsApi.updateCall(BW_ACCOUNT_ID, updateCallId, { state: CallStateEnum.Completed });
expect(completeStatus).toEqual(200);

});

test('test redirect', async () => {
const updateCallId = await createMantecaCall(callsApi);
activeCalls.push(updateCallId);
await sleep(SLEEP_TIME_S);

const redirectAttributes = {
redirectUrl: 'https://initial.com',
redirectMethod: 'POST',
redirectFallbackUrl: 'https://initial.com',
redirectFallbackMethod: 'POST',
username: 'initialUsername',
password: 'initialPassword',
fallbackUsername: 'initialFallbackUsername',
fallbackPassword: 'initialFallbackPassword',
tag: 'initialTag'
};

const redirect = new Bxml.Redirect(redirectAttributes);

const bxml = new Bxml.Response([redirect]);

const { status: updateStatus } =
await callsApi.updateCallBxml(BW_ACCOUNT_ID, updateCallId, bxml.toBxml());
expect(updateStatus).toEqual(204);
await sleep(SLEEP_TIME_S);

const { status: completeStatus } =
await callsApi.updateCall(BW_ACCOUNT_ID, updateCallId, { state: CallStateEnum.Completed });
expect(completeStatus).toEqual(200);
});
});

0 comments on commit 0c45900

Please sign in to comment.