-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(voip): Add call participant management endpoints. (#61)
Allows API consumers to add participants to and remove participants from existing VOIP calls. Resolves EN-4256. Signed-off-by: Jeff Cuevas-Koch <[email protected]>
- Loading branch information
1 parent
0bfceb1
commit d1b9d96
Showing
7 changed files
with
283 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,11 @@ import chai from 'chai'; | |
import chaiAsPromised from 'chai-as-promised'; | ||
import fetchMock from 'fetch-mock'; | ||
import Track from '../index'; | ||
import { charlie, calls as mockCalls } from '../mocks'; | ||
import { | ||
charlie, | ||
calls as mockCalls, | ||
callParticipants as mockParticipants, | ||
} from '../mocks'; | ||
|
||
chai.should(); | ||
chai.use(chaiAsPromised); | ||
|
@@ -63,3 +67,45 @@ describe('When ending a call', () => { | |
return callPromise; | ||
}); | ||
}); | ||
|
||
describe('When adding a participant to a call', () => { | ||
const api = new Track({ autoRenew: false }); | ||
|
||
beforeEach(() => charlie.setUpSuccessfulMock(api.client)); | ||
beforeEach(() => mockParticipants.setUpSuccessfulMock(api.client)); | ||
beforeEach(() => fetchMock.catch(503)); | ||
afterEach(fetchMock.restore); | ||
|
||
it('should add the participant', () => { | ||
api.logIn({ username: '[email protected]', password: 'securepassword' }); | ||
|
||
const callId = 2; | ||
const newParticipant = { type: 'user', user: '/1/SYNC/users/1' }; | ||
const participantPromise = api.customer('SYNC').callParticipant(callId, newParticipant) | ||
.create() | ||
.then(participant => participant); | ||
|
||
return participantPromise; | ||
}); | ||
}); | ||
|
||
describe('When removing a participant from a call', () => { | ||
const api = new Track({ autoRenew: false }); | ||
|
||
beforeEach(() => charlie.setUpSuccessfulMock(api.client)); | ||
beforeEach(() => mockParticipants.setUpSuccessfulMock(api.client)); | ||
beforeEach(() => fetchMock.catch(503)); | ||
afterEach(fetchMock.restore); | ||
|
||
it('should remove the participant', () => { | ||
api.logIn({ username: '[email protected]', password: 'securepassword' }); | ||
|
||
const callId = 2; | ||
const participantId = 3; | ||
const participantPromise = api.customer('SYNC').callParticipant(callId, participantId) | ||
.fetch() | ||
.then(participant => participant.end()); | ||
|
||
return participantPromise; | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
// eslint-disable-next-line import/no-extraneous-dependencies | ||
import fetchMock from 'fetch-mock'; | ||
import Client from '../Client'; | ||
|
||
const callParticipants = { | ||
setUpSuccessfulMock: (client) => { | ||
const singleResponse = () => new Response(Client.toBlob(callParticipants.getById(2, 3))); | ||
const postResponse = () => new Response(undefined, { | ||
headers: { | ||
Location: '/1/SYNC/calls/2/participants/3', | ||
}, | ||
}); | ||
const patchResponse = () => new Response(undefined, { | ||
headers: {}, | ||
}); | ||
|
||
fetchMock | ||
.get(client.resolve('/1/SYNC/calls/2/participants/3'), singleResponse) | ||
.post(client.resolve('/1/SYNC/calls/2/participants'), postResponse) | ||
.patch(client.resolve('/1/SYNC/calls/2/participants/3'), patchResponse); | ||
}, | ||
getById: (callId, id) => callParticipants.list.find(v => | ||
v.call.href === `/1/SYNC/calls/${callId}` && v.id === id), | ||
list: [ | ||
{ | ||
href: '/1/SYNC/calls/2/participants/3', | ||
call: { href: '/1/SYNC/calls/2' }, | ||
id: 3, | ||
type: 'user', | ||
external_session_id: '82576c1c-9351-4da3-8df1-e3063ae285e7', | ||
connection_requested: new Date().toISOString(), | ||
connection_established: undefined, | ||
connection_terminated: undefined, | ||
user: '/1/SYNC/users/1', | ||
}, | ||
{ | ||
href: '/1/SYNC/calls/2/participants/4', | ||
call: { href: '/1/SYNC/calls/2' }, | ||
id: 4, | ||
type: 'vehicle', | ||
external_session_id: '537fafaf-9eea-4b1a-b65c-096ee4ed462f', | ||
connection_requested: new Date().toISOString(), | ||
connection_established: undefined, | ||
connection_terminated: undefined, | ||
vehicle: '/1/SYNC/vehicles/1', | ||
}, | ||
], | ||
}; | ||
export default callParticipants; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import Resource from './Resource'; | ||
|
||
/** | ||
* CallParticipant resource | ||
*/ | ||
|
||
class CallParticipant extends Resource { | ||
/** | ||
* Creates a new call participant | ||
* | ||
* Will populate itself with the values given to it after the client parameter | ||
* @param {Client} client Instance of pre-configured client | ||
* @param {Object} rest The object to use ina ssigning values to this instance | ||
*/ | ||
constructor(client, rest) { | ||
super(client); | ||
const { code, ...newProperties } = rest; | ||
this.customerCode = code; | ||
const hydrated = !Object.keys(newProperties).every(k => k === 'href' || k === 'customerCode'); | ||
Object.assign(this, newProperties, { hydrated }); | ||
} | ||
|
||
/** | ||
* Makes a href for a given customer code, call ID, and ID | ||
* @param {string} customerCode Alphanumeric code of the customer | ||
* @param {Number} callId ID of the call | ||
* @param {Number} id Call Participant ID | ||
* @returns {string} URI to instance of call | ||
*/ | ||
static makeHref(customerCode, callId, id) { | ||
return { | ||
href: `/1/${customerCode}/calls/${callId}/participants/${id}`, | ||
code: customerCode, | ||
}; | ||
} | ||
|
||
/** | ||
* Fetches the data for this call participant via the client | ||
* @returns {Promise} If successful, a hydrated instance of this call participant | ||
*/ | ||
fetch() { | ||
return this.client.get(this.href) | ||
.then(response => response.json()) | ||
.then(callParticipant => new CallParticipant(this.client, { ...this, ...callParticipant })); | ||
} | ||
|
||
/** | ||
* Adds a participant to a call via the client | ||
* @returns {Promise} If successful, returns the call participant with IDs set | ||
*/ | ||
create() { | ||
const { client, hydrated, customerCode, callId, ...body } = this; | ||
return this.client.post(`/1/${customerCode}/calls/${callId}/participants`, { body }) | ||
.then(response => response.headers.get('location')) | ||
.then((href) => { | ||
const match = /\/\d+\/\S+\/calls\/(\d+)\/participants\/(\d+)/.exec(href); | ||
return new CallParticipant(this.client, { | ||
...this, | ||
href, | ||
callId: parseFloat(match[1]), | ||
id: parseFloat(match[2]), | ||
}); | ||
}); | ||
} | ||
|
||
/** | ||
* Ends the call for this participant via the client | ||
* @returns {Promise} If successful, returns instance of this call participant | ||
*/ | ||
end() { | ||
const { href } = CallParticipant.makeHref(this.customerCode, this.callId, this.id); | ||
this.connection_terminated = new Date().toISOString(); | ||
return this.client.patch(href, { | ||
body: [ | ||
{ | ||
op: 'replace', | ||
path: '/connection_terminated', | ||
value: this.connection_terminated, | ||
}, | ||
], | ||
}); | ||
} | ||
} | ||
|
||
export default CallParticipant; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
import chai from 'chai'; | ||
import chaiAsPromised from 'chai-as-promised'; | ||
import fetchMock from 'fetch-mock'; | ||
import Client from '../Client'; | ||
import CallParticipant from './CallParticipant'; | ||
import { callParticipants as mockParticipants } from '../mocks'; | ||
|
||
chai.should(); | ||
chai.use(chaiAsPromised); | ||
|
||
describe('When instantiating a call participant based on customer and IDs', () => { | ||
const client = new Client(); | ||
const callParticipant = new CallParticipant(client, CallParticipant.makeHref('SYNC', 2, 3)); | ||
|
||
it('should set the href', () => callParticipant.href.should.equal('/1/SYNC/calls/2/participants/3')); | ||
it('should not be hydrated', () => callParticipant.hydrated.should.equal(false)); | ||
}); | ||
|
||
describe('When instantiating a call participant based on an object', () => { | ||
const client = new Client(); | ||
const callParticipant = new CallParticipant(client, mockParticipants.getById(2, 3)); | ||
|
||
it('should set the ID', () => callParticipant.id.should.equal(3)); | ||
it('should set the href', () => callParticipant.href.should.equal('/1/SYNC/calls/2/participants/3')); | ||
it('should be hydrated', () => callParticipant.hydrated.should.equal(true)); | ||
}); | ||
|
||
describe('When fetching a call participant based on customer and IDs', () => { | ||
const client = new Client(); | ||
|
||
beforeEach(() => mockParticipants.setUpSuccessfulMock(client)); | ||
beforeEach(() => fetchMock.catch(503)); | ||
afterEach(fetchMock.restore); | ||
|
||
let promise; | ||
beforeEach(() => { | ||
promise = new CallParticipant(client, CallParticipant.makeHref('SYNC', 2, 3)).fetch(); | ||
}); | ||
|
||
it('should resolve the promise', () => promise.should.be.fulfilled); | ||
it('should set the ID', () => promise.then(v => v.id).should.eventually.equal(3)); | ||
it('should set the href', () => promise.then(v => v.href).should.eventually.equal('/1/SYNC/calls/2/participants/3')); | ||
it('should be hydrated', () => promise.then(v => v.hydrated).should.eventually.equal(true)); | ||
}); | ||
|
||
describe('When adding a call participant', () => { | ||
const client = new Client(); | ||
|
||
beforeEach(() => mockParticipants.setUpSuccessfulMock(client)); | ||
beforeEach(() => fetchMock.catch(503)); | ||
afterEach(fetchMock.restore); | ||
|
||
let promise; | ||
beforeEach(() => { | ||
promise = new CallParticipant(client, { code: 'SYNC', callId: 2, user: '/1/SYNC/users/1' }).create(); | ||
}); | ||
|
||
it('should resolve the promise', () => promise.should.be.fulfilled); | ||
it('should set the href', () => promise.then(v => v.href).should.eventually.equal('/1/SYNC/calls/2/participants/3')); | ||
it('should set the ID', () => promise.then(v => v.id).should.eventually.equal(3)); | ||
}); | ||
|
||
describe('When updating a call participant', () => { | ||
const client = new Client(); | ||
|
||
beforeEach(() => mockParticipants.setUpSuccessfulMock(client)); | ||
beforeEach(() => fetchMock.catch(503)); | ||
afterEach(fetchMock.restore); | ||
|
||
let promise; | ||
beforeEach(() => { | ||
promise = new CallParticipant(client, { code: 'SYNC', callId: 2, user: '/1/SYNC/users/1' }) | ||
.create() | ||
.then(participant => participant.end() | ||
.then(() => participant)); | ||
}); | ||
|
||
it('should resolve the promise', () => promise.should.be.fulfilled); | ||
it('should set the href', () => promise.then(v => v.href).should.eventually.equal('/1/SYNC/calls/2/participants/3')); | ||
it('should set connection_terminated to a date', () => promise.then(v => v.connection_terminated).should.eventually.be.a('string')); | ||
}); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters