Skip to content

Commit a49aa91

Browse files
cuevaskochJeff Cuevas-Koch
andauthored
feat(assignable_entities): Get assignable entities (#98)
Allows users to retrieve assignable routes and assignable stops from Track API. Supports EN-7151. Signed-off-by: Jeff Cuevas-Koch <jcuevas-koch@gmvsync.com> Co-authored-by: Jeff Cuevas-Koch <jcuevas-koch@gmvsync.com>
1 parent f4336d1 commit a49aa91

15 files changed

Lines changed: 535 additions & 0 deletions
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import chai from 'chai';
2+
import chaiAsPromised from 'chai-as-promised';
3+
import fetchMock from 'fetch-mock';
4+
import Track from '../index';
5+
import { charlie, assignableRoutes as mockAssignableRoutes } from '../mocks';
6+
7+
chai.should();
8+
chai.use(chaiAsPromised);
9+
10+
describe('When getting assignable routes', () => {
11+
const api = new Track({ autoRenew: false });
12+
13+
beforeEach(() => charlie.setUpSuccessfulMock(api.client));
14+
beforeEach(() => mockAssignableRoutes.setUpSuccessfulMock(api.client));
15+
beforeEach(() => fetchMock.catch(503));
16+
afterEach(fetchMock.restore);
17+
18+
it('should get a list of assignable routes', () => {
19+
api.logIn({ username: 'charlie@example.com', password: 'securepassword' });
20+
21+
const assignableRoutesPromise = api.customer('SYNC').assignableRoutes()
22+
.getPage()
23+
.then(page => page.list)
24+
.then(assignableRoutes => assignableRoutes); // Do things with list of assignableRoutes
25+
26+
return assignableRoutesPromise;
27+
});
28+
});
29+
30+
describe('When retrieving an assignable route by ID', () => {
31+
const api = new Track({ autoRenew: false });
32+
33+
beforeEach(() => charlie.setUpSuccessfulMock(api.client));
34+
beforeEach(() => mockAssignableRoutes.setUpSuccessfulMock(api.client));
35+
beforeEach(() => fetchMock.catch(503));
36+
afterEach(fetchMock.restore);
37+
38+
it('should get an assignable route', () => {
39+
api.logIn({ username: 'charlie@example.com', password: 'securepassword' });
40+
41+
const assignableRoutePromise = api.customer('SYNC').assignableRoute('blue_line')
42+
.fetch()
43+
.then(assignableRoute => assignableRoute); // Do things with assignable route
44+
45+
return assignableRoutePromise;
46+
});
47+
});
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import chai from 'chai';
2+
import chaiAsPromised from 'chai-as-promised';
3+
import fetchMock from 'fetch-mock';
4+
import Track from '../index';
5+
import { charlie, assignableStops as mockAssignableStops } from '../mocks';
6+
7+
chai.should();
8+
chai.use(chaiAsPromised);
9+
10+
describe('When getting assignable stops', () => {
11+
const api = new Track({ autoRenew: false });
12+
13+
beforeEach(() => charlie.setUpSuccessfulMock(api.client));
14+
beforeEach(() => mockAssignableStops.setUpSuccessfulMock(api.client));
15+
beforeEach(() => fetchMock.catch(503));
16+
afterEach(fetchMock.restore);
17+
18+
it('should get a list of assignable stops', () => {
19+
api.logIn({ username: 'charlie@example.com', password: 'securepassword' });
20+
21+
const assignableStopsPromise = api.customer('SYNC').assignableStops()
22+
.getPage()
23+
.then(page => page.list)
24+
.then(assignableStops => assignableStops); // Do things with list of assignableStops
25+
26+
return assignableStopsPromise;
27+
});
28+
});
29+
30+
describe('When retrieving an assignable stop by ID', () => {
31+
const api = new Track({ autoRenew: false });
32+
33+
beforeEach(() => charlie.setUpSuccessfulMock(api.client));
34+
beforeEach(() => mockAssignableStops.setUpSuccessfulMock(api.client));
35+
beforeEach(() => fetchMock.catch(503));
36+
afterEach(fetchMock.restore);
37+
38+
it('should get an assignable stop', () => {
39+
api.logIn({ username: 'charlie@example.com', password: 'securepassword' });
40+
41+
const assignableStopPromise = api.customer('SYNC').assignableStop('1st_and_main')
42+
.fetch()
43+
.then(assignableStop => assignableStop); // Do things with assignable stop
44+
45+
return assignableStopPromise;
46+
});
47+
});

src/mocks/assignableRoutes.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// eslint-disable-next-line import/no-extraneous-dependencies
2+
import fetchMock from 'fetch-mock';
3+
import Client from '../Client';
4+
5+
const assignableRoutes = {
6+
setUpSuccessfulMock: (client) => {
7+
const listResponse = () => new Response(
8+
Client.toBlob(assignableRoutes.list), {
9+
headers: {
10+
Link: '</1/SYNC/assignable_entities/routes?page=1&per_page=10&sort=>; rel="next", </1/SYNC/assignable_entities/routes?page=1&per_page=10&sort=>; rel="last"',
11+
},
12+
});
13+
const singleResponse = () => new Response(Client.toBlob(assignableRoutes.getById('blue_line')));
14+
15+
fetchMock
16+
.get(client.resolve('/1/SYNC/assignable_entities/routes?page=1&per_page=10&sort='), listResponse)
17+
.get(client.resolve('/1/SYNC/assignable_entities/routes/blue_line'), singleResponse);
18+
},
19+
getById: id => assignableRoutes.list.find(v => v.id === id),
20+
list: [{
21+
id: 'blue_line',
22+
href: '/1/SYNC/assignable_entities/routes/blue_line',
23+
name: 'Blue Line',
24+
short_name: 'BLU',
25+
color: '#0000FF',
26+
text_color: '#FFFFFF',
27+
provider_id: 1,
28+
agency_id: 'SYNC',
29+
route_type: 'MetropolitanStreetLevelRail'
30+
},
31+
{
32+
id: 'red_line',
33+
href: '/1/SYNC/assignable_entities/routes/red_line',
34+
name: 'Red Line',
35+
short_name: 'RED',
36+
color: '#FF0000',
37+
text_color: '#FFFFFF',
38+
provider_id: 1,
39+
agency_id: 'SYNC',
40+
route_type: 'MetropolitanUndergroundRail'
41+
}],
42+
};
43+
44+
export default assignableRoutes;

src/mocks/assignableStops.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// eslint-disable-next-line import/no-extraneous-dependencies
2+
import fetchMock from 'fetch-mock';
3+
import Client from '../Client';
4+
5+
const assignableStops = {
6+
setUpSuccessfulMock: (client) => {
7+
const listResponse = () => new Response(
8+
Client.toBlob(assignableStops.list), {
9+
headers: {
10+
Link: '</1/SYNC/assignable_entities/stops?page=1&per_page=10&sort=>; rel="next", </1/SYNC/assignable_entities/stops?page=1&per_page=10&sort=>; rel="last"',
11+
},
12+
});
13+
const singleResponse = () => new Response(Client.toBlob(assignableStops.getById('1st_and_main')));
14+
15+
fetchMock
16+
.get(client.resolve('/1/SYNC/assignable_entities/stops?page=1&per_page=10&sort='), listResponse)
17+
.get(client.resolve('/1/SYNC/assignable_entities/stops/1st_and_main'), singleResponse);
18+
},
19+
getById: id => assignableStops.list.find(v => v.id === id),
20+
list: [{
21+
id: '1st_and_main',
22+
href: '/1/SYNC/assignable_entities/stops/1st_and_main',
23+
name: 'First and Main',
24+
provider_id: 1,
25+
agency_id: 'SYNC',
26+
},
27+
{
28+
id: '6th_and_olive',
29+
href: '/1/SYNC/assignable_entities/stops/6th_and_olive',
30+
name: 'Sixth and Olive',
31+
provider_id: 1,
32+
agency_id: 'SYNC',
33+
}],
34+
};
35+
36+
export default assignableStops;

src/mocks/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import Client from '../Client';
55
export { default as agencies } from './agencies';
66
export { default as areas } from './areas';
77
export { default as assets } from './assets';
8+
export { default as assignableRoutes } from './assignableRoutes';
9+
export { default as assignableStops } from './assignableStops';
810
export { default as blocks } from './blocks';
911
export { default as calls } from './calls';
1012
export { default as callParticipants } from './callParticipants';

src/resources/AssignableRoute.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import Resource from './Resource';
2+
3+
/**
4+
* AssignableRoute resource
5+
*/
6+
class AssignableRoute extends Resource {
7+
/**
8+
* Creates a new AssignableRoute
9+
*
10+
* @param {Client} client Instance of pre-configured client
11+
* @param {Array} rest Remaining arguments to use in assigning values to this instance
12+
*/
13+
constructor(client, ...rest) {
14+
super(client);
15+
16+
const newProperties = Object.assign({}, ...rest);
17+
const hydrated = !Object.keys(newProperties).every(k => k === 'href' || k === 'code');
18+
19+
Object.assign(this, newProperties, {
20+
hydrated,
21+
});
22+
}
23+
24+
/**
25+
* Makes a href for a given customer code and ID
26+
*
27+
* @param {string} customerCode Customer code
28+
* @param {string} id Assignable Route ID
29+
* @returns {{href: string}} URI to instance of Assignable Route
30+
*/
31+
static makeHref(customerCode, id) {
32+
return {
33+
href: `/1/${customerCode}/assignable_entities/routes/${id}`,
34+
code: customerCode,
35+
};
36+
}
37+
38+
/**
39+
* Fetches the data for this assignable route via the client.
40+
*
41+
* @returns {Promise} If successful, a hydrated instance of this assignable route
42+
*/
43+
fetch() {
44+
return this.client.get(this.href)
45+
.then(response => response.json())
46+
.then(assignableRoute => new AssignableRoute(this.client, this, assignableRoute));
47+
}
48+
}
49+
50+
export default AssignableRoute;
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import chai from 'chai';
2+
import chaiAsPromised from 'chai-as-promised';
3+
import fetchMock from 'fetch-mock';
4+
import Client from '../Client';
5+
import AssignableStop from './AssignableStop';
6+
import { assignableStops as mockAssignableStops } from '../mocks';
7+
8+
chai.should();
9+
chai.use(chaiAsPromised);
10+
11+
describe('When instantiating an assignable route based on customer and ID', () => {
12+
const client = new Client();
13+
const assignableRoute = new AssignableStop(client, AssignableStop.makeHref('SYNC', '1st_and_main'));
14+
15+
it('should set the href', () => assignableRoute.href.should.equal('/1/SYNC/assignable_entities/stops/1st_and_main'));
16+
it('should not be hydrated', () => assignableRoute.hydrated.should.equal(false));
17+
});
18+
19+
describe('When instantiating an assignable route based on an object', () => {
20+
const client = new Client();
21+
const assignableRoute = new AssignableStop(client, mockAssignableStops.getById('1st_and_main'));
22+
23+
it('should set the ID', () => assignableRoute.id.should.equal('1st_and_main'));
24+
it('should set the href', () => assignableRoute.href.should.equal('/1/SYNC/assignable_entities/stops/1st_and_main'));
25+
it('should be hydrated', () => assignableRoute.hydrated.should.equal(true));
26+
});
27+
28+
describe('When fetching an assignable route based on customer and ID', () => {
29+
const client = new Client();
30+
31+
beforeEach(() => mockAssignableStops.setUpSuccessfulMock(client));
32+
beforeEach(() => fetchMock.catch(503));
33+
afterEach(fetchMock.restore);
34+
35+
let promise;
36+
beforeEach(() => {
37+
promise = new AssignableStop(client, AssignableStop.makeHref('SYNC', '1st_and_main')).fetch();
38+
});
39+
40+
it('should resolve the promise', () => promise.should.be.fulfilled);
41+
it('should set the ID', () => promise.then(v => v.id).should.eventually.equal('1st_and_main'));
42+
it('should set the href', () => promise.then(v => v.href).should.eventually.equal('/1/SYNC/assignable_entities/stops/1st_and_main'));
43+
it('should be hydrated', () => promise.then(v => v.hydrated).should.eventually.equal(true));
44+
});
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import 'isomorphic-fetch';
2+
import PagedContext from './PagedContext';
3+
import AssignableRoute from './AssignableRoute';
4+
5+
/**
6+
* AssignableRoutes querying context
7+
*
8+
* This is used to query the list of Assignable Routes for a customer
9+
*/
10+
class AssignableRoutesContext extends PagedContext {
11+
/**
12+
*
13+
* @param {Client} client Instance of pre-configurred client
14+
* @param {string} customerCode Customer code
15+
* @param {object} params Object of querystring parameters to append to the URL
16+
*/
17+
constructor(client, customerCode, params) {
18+
super(client, { ...params });
19+
this.code = customerCode;
20+
}
21+
22+
/**
23+
* Gets the first page of results for this context
24+
* @returns {Promise} If successful, a page of AssignableRoute objects
25+
* @see AssignableRoute
26+
*/
27+
getPage() {
28+
return this.page(AssignableRoute, `/1/${this.code}/assignable_entities/routes`);
29+
}
30+
}
31+
32+
export default AssignableRoutesContext;
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import chai from 'chai';
2+
import chaiAsPromised from 'chai-as-promised';
3+
import fetchMock from 'fetch-mock';
4+
import Client from '../Client';
5+
import AssignableRoutesContext from './AssignableRoutesContext';
6+
import { assignableRoutes as mockAssignableRoutes } from '../mocks';
7+
8+
chai.should();
9+
chai.use(chaiAsPromised);
10+
11+
describe('When building a query for assignable routes', () => {
12+
const client = new Client();
13+
client.setAuthenticated();
14+
15+
beforeEach(() => fetchMock
16+
.get(client.resolve('/1/SYNC/assignable_entities/routes?page=1&per_page=10&sort='), mockAssignableRoutes.list)
17+
.catch(503));
18+
afterEach(fetchMock.restore);
19+
20+
let promise;
21+
beforeEach(() => {
22+
const routes = new AssignableRoutesContext(client, 'SYNC');
23+
promise = routes
24+
.withPage(1)
25+
.withPerPage(10)
26+
.getPage();
27+
});
28+
29+
it('should make the expected request', () => promise.should.be.fulfilled);
30+
});

src/resources/AssignableStop.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import Resource from './Resource';
2+
3+
/**
4+
* AssignableStop resource
5+
*/
6+
class AssignableStop extends Resource {
7+
/**
8+
* Creates a new AssignableStop
9+
*
10+
* @param {Client} client Instance of pre-configured client
11+
* @param {Array} rest Remaining arguments to use in assigning values to this instance
12+
*/
13+
constructor(client, ...rest) {
14+
super(client);
15+
16+
const newProperties = Object.assign({}, ...rest);
17+
const hydrated = !Object.keys(newProperties).every(k => k === 'href' || k === 'code');
18+
19+
Object.assign(this, newProperties, {
20+
hydrated,
21+
});
22+
}
23+
24+
/**
25+
* Makes a href for a given customer code and ID
26+
*
27+
* @param {string} customerCode Customer code
28+
* @param {string} id Assignable Stop ID
29+
* @returns {{href: string}} URI to instance of Assignable Stop
30+
*/
31+
static makeHref(customerCode, id) {
32+
return {
33+
href: `/1/${customerCode}/assignable_entities/stops/${id}`,
34+
code: customerCode,
35+
};
36+
}
37+
38+
/**
39+
* Fetches the data for this assignable stop via the client.
40+
*
41+
* @returns {Promise} If successful, a hydrated instance of this assignable stop
42+
*/
43+
fetch() {
44+
return this.client.get(this.href)
45+
.then(response => response.json())
46+
.then(assignableStop => new AssignableStop(this.client, this, assignableStop));
47+
}
48+
}
49+
50+
export default AssignableStop;

0 commit comments

Comments
 (0)