Skip to content

Commit

Permalink
feat(Calls): EN-4357: Added call creation and ending support (#57)
Browse files Browse the repository at this point in the history
  • Loading branch information
Daniel James authored May 21, 2019
1 parent 09e728e commit 99841a0
Show file tree
Hide file tree
Showing 7 changed files with 306 additions and 0 deletions.
10 changes: 10 additions & 0 deletions src/Client.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,16 @@ class Client {
return this.request('PUT', ...args);
}

/**
* Convenience method of request() to make an HTTP PATCH request
* @param {Array} args Any other arguments for request()
* @returns {Promise} Promise from request()
* @see request
*/
patch(...args) {
return this.request('PATCH', ...args);
}

/**
* Convenience method of request() to make an HTTP DELETE request
* @param {Array} args Any other arguments for request()
Expand Down
65 changes: 65 additions & 0 deletions src/examples/create_and_end_calls.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
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';

chai.should();
chai.use(chaiAsPromised);

describe('When retrieving a call by ID', () => {
const api = new Track({ autoRenew: false });

beforeEach(() => charlie.setUpSuccessfulMock(api.client));
beforeEach(() => mockCalls.setUpSuccessfulMock(api.client));
beforeEach(() => fetchMock.catch(503));
afterEach(fetchMock.restore);

it('should get a call', () => {
api.logIn({ username: '[email protected]', password: 'securepassword' });

const callPromise = api.customer('SYNC').call(3)
.fetch()
.then(call => call); // Do things with call

return callPromise;
});
});

describe('When creating a call', () => {
const api = new Track({ autoRenew: false });

beforeEach(() => charlie.setUpSuccessfulMock(api.client));
beforeEach(() => mockCalls.setUpSuccessfulMock(api.client));
beforeEach(() => fetchMock.catch(503));
afterEach(fetchMock.restore);

it('should create a call', () => {
api.logIn({ username: '[email protected]', password: 'securepassword' });

const callPromise = api.customer('SYNC').call({ initiating_user: '/1/users/1' })
.create()
.then(call => call); // Do things with call

return callPromise;
});
});

describe('When ending a call', () => {
const api = new Track({ autoRenew: false });

beforeEach(() => charlie.setUpSuccessfulMock(api.client));
beforeEach(() => mockCalls.setUpSuccessfulMock(api.client));
beforeEach(() => fetchMock.catch(503));
afterEach(fetchMock.restore);

it('should end a call', () => {
api.logIn({ username: '[email protected]', password: 'securepassword' });

const callPromise = api.customer('SYNC').call(3)
.fetch()
.then(call => call.end());

return callPromise;
});
});
54 changes: 54 additions & 0 deletions src/mocks/calls.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// eslint-disable-next-line import/no-extraneous-dependencies
import fetchMock from 'fetch-mock';
import Client from '../Client';

const calls = {
setUpSuccessfulMock: (client) => {
const singleResponse = () => new Response(Client.toBlob(calls.getById(3)));
const postResponse = () => new Response(undefined, {
headers: {
Location: '/1/SYNC/calls/3',
},
});
const patchResponse = () => new Response(undefined, {
headers: {},
});

fetchMock
.get(client.resolve('/1/SYNC/calls/3'), singleResponse)
.post(client.resolve('/1/SYNC/calls'), postResponse)
.patch(client.resolve('/1/SYNC/calls/3'), patchResponse);
},
getById: id => calls.list.find(v => v.id === id),
list: [{
href: '/1/SYNC/calls/3',
id: 3,
started: new Date().toISOString(),
ended: undefined,
initiating_user: '/1/users/1',
participants: [
{
href: '/1/SYNC/calls/3/participants/1',
id: 1,
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/3/participants/2',
id: 2,
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 calls;
1 change: 1 addition & 0 deletions src/mocks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Client from '../Client';
export { default as agencies } from './agencies';
export { default as areas } from './areas';
export { default as blocks } from './blocks';
export { default as calls } from './calls';
export { default as dispatchMessages } from './dispatchMessages';
export { default as dispatchMessageBatches } from './dispatchMessageBatches';
export { default as drivers } from './drivers';
Expand Down
82 changes: 82 additions & 0 deletions src/resources/Call.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import Resource from './Resource';

/**
* Call resource
*/
class Call extends Resource {
/**
* Creates a new call
*
* 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 in assigning 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 and ID
* @param {string} customerCode Customer code
* @param {Number} id Call ID
* @returns {string} URI to instance of call
*/
static makeHref(customerCode, id) {
return {
href: `/1/${customerCode}/calls/${id}`,
code: customerCode,
};
}

/**
* Fetches the data for this call via the client
* @returns {Promise} If successful, a hydrated instance of this call
*/
fetch() {
return this.client.get(this.href)
.then(response => response.json())
.then(call => new Call(this.client, { ...this, ...call }));
}

/**
* Saves data for a call via the client
* @returns {Promise} if successful returns a call with the id included
*/
create() {
// eslint-disable-next-line no-unused-vars
const { client, hydrated, customerCode, ...body } = this;
return this.client.post(`/1/${this.customerCode}/calls`, { body })
.then(response => response.headers.get('location'))
.then((href) => {
const match = /\/\d+\/\S+\/calls\/(\d+)/.exec(href);
return new Call(this.client, { ...this, href, id: parseFloat(match[1]) });
});
}

/**
* Updates data for a call via the client
* @returns {Promise} if successful returns instance of this call
*/
end() {
const { href } = Call.makeHref(this.customerCode, this.id);
this.ended = new Date().toISOString();
return this.client.patch(href, {
body: [
{
op: 'replace',
path: '/ended',
value: this.ended,
},
],
});
}

}

export default Call;
81 changes: 81 additions & 0 deletions src/resources/Call.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import fetchMock from 'fetch-mock';
import Client from '../Client';
import Call from './Call';
import { calls as mockCalls } from '../mocks';

chai.should();
chai.use(chaiAsPromised);

describe('When instantiating a call based on customer and ID', () => {
const client = new Client();
const call = new Call(client, Call.makeHref('SYNC', 3));

it('should set the href', () => call.href.should.equal('/1/SYNC/calls/3'));
it('should not be hydrated', () => call.hydrated.should.equal(false));
});

describe('When instantiating a call based on an object', () => {
const client = new Client();
const call = new Call(client, mockCalls.getById(3));

it('should set the ID', () => call.id.should.equal(3));
it('should set the href', () => call.href.should.equal('/1/SYNC/calls/3'));
it('should be hydrated', () => call.hydrated.should.equal(true));
});

describe('When fetching a call based on customer and ID', () => {
const client = new Client();

beforeEach(() => mockCalls.setUpSuccessfulMock(client));
beforeEach(() => fetchMock.catch(503));
afterEach(fetchMock.restore);

let promise;
beforeEach(() => {
promise = new Call(client, Call.makeHref('SYNC', 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/3'));
it('should be hydrated', () => promise.then(v => v.hydrated).should.eventually.equal(true));
});

describe('When creating a call', () => {
const client = new Client();

beforeEach(() => mockCalls.setUpSuccessfulMock(client));
beforeEach(() => fetchMock.catch(503));
afterEach(fetchMock.restore);

let promise;
beforeEach(() => {
promise = new Call(client, { code: 'SYNC', initiating_user: '/1/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/3'));
it('should set the ID', () => promise.then(v => v.id).should.eventually.equal(3));
});

describe('When updating a call', () => {
const client = new Client();

beforeEach(() => mockCalls.setUpSuccessfulMock(client));
beforeEach(() => fetchMock.catch(503));
afterEach(fetchMock.restore);

let promise;
beforeEach(() => {
promise = new Call(client, { code: 'SYNC', initiating_user: '/1/users/1' })
.create()
.then(call => call.end()
.then(() => call));
});

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/3'));
it('should set ended to a date', () => promise.then(v => v.ended).should.eventually.be.a('string'));
});
13 changes: 13 additions & 0 deletions src/resources/Customer.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Area from './Area';
import AreasContext from './AreasContext';
import Assignment from './Assignment';
import Block from './Block';
import Call from './Call';
import DispatchMessage from './DispatchMessage';
import DispatchMessagesContext from './DispatchMessagesContext';
import DispatchMessageBatch from './DispatchMessageBatch';
Expand Down Expand Up @@ -109,6 +110,18 @@ class Customer extends Resource {
return this.resource(Block, Block.makeHref(this.code, id));
}

/**
* Gets a call resource by id
* @param {Object} payload Identity of the call or object representing a new call
* @returns {Message} Call resource
*/
call(payload) {
if (!isNaN(parseFloat(payload)) && isFinite(payload)) {
return this.resource(Call, Call.makeHref(this.code, payload));
}
return this.resource(Call, { code: this.code, ...payload });
}

/**
* Gets a context for querying this customer's dispatch messages
* @returns {DispatchMessagesContext} Context for querying this customer's dispatch messages
Expand Down

0 comments on commit 99841a0

Please sign in to comment.