Skip to content

Commit

Permalink
Merge pull request #6 from Zuver/feature/EN-1575-tags-api-client
Browse files Browse the repository at this point in the history
Tags API client
  • Loading branch information
zuver authored Apr 13, 2017
2 parents 8c81aa3 + 810c259 commit ef39563
Show file tree
Hide file tree
Showing 9 changed files with 231 additions and 7 deletions.
48 changes: 48 additions & 0 deletions src/examples/get_tags.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import fetchMock from 'fetch-mock';
import Track from '../index';
import { charlie, tags as mockTags } from '../mocks';

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

describe('When searching for tags by name', () => {
const api = new Track({ autoRenew: false });

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

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

const tagsPromise = api.customer('SYNC').tags()
.withQuery('LA') // Tags containing "LA" in their name
.getPage()
.then(page => page.list)
.then(tags => tags); // Do things with list of tags

return tagsPromise;
});
});

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

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

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

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

return tagPromise;
});
});
26 changes: 26 additions & 0 deletions src/mocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,32 @@ export const stops = {
],
};

export const tags = {
setUpSuccessfulMock: (client) => {
const listResponse = () => new Response(
toBlob(tags.list),
{
headers: {
Link: '</1/SYNC/tags?page=1&perPage=10&q=LA&sort=>; rel="next", </1/SYNC/tags?page=1&perPage=10&q=LA&sort=>; rel="last"',
},
});
const singleResponse = () => new Response(toBlob(tags.getById(3)));

fetchMock
.get(client.resolve('/1/SYNC/tags?page=1&perPage=10&q=LA&sort='), listResponse)
.get(client.resolve('/1/SYNC/tags/3'), singleResponse);
},
getById: id => tags.list.find(v => v.id === id),
list: [
{
href: '/1/SYNC/tags/3',
id: 3,
name: 'DTLA',
customerId: 1,
},
],
};

export const vehicles = {
setUpSuccessfulMock: (client) => {
const listResponse = () => new Response(
Expand Down
19 changes: 19 additions & 0 deletions src/resources/Customer.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import Sign from './Sign';
import SignsContext from './SignsContext';
import Stop from './Stop';
import StopsContext from './StopsContext';
import Tag from './Tag';
import TagsContext from './TagsContext';
import Vehicle from './Vehicle';
import VehiclesContext from './VehiclesContext';

Expand Down Expand Up @@ -99,6 +101,23 @@ class Customer extends Resource {
return this.resource(Stop, Stop.makeHref(this.code, id));
}

/**
* Gets a context for querying this customer's tags
* @returns {TagContext} Context for querying this customer's tags
*/
tags() {
return this.resource(TagsContext, this.code);
}

/**
* Gets a tag resource by id
* @param {Number} id Identity of the tag
* @returns {Tag} Tag resource
*/
tag(id) {
return this.resource(Tag, Tag.makeHref(this.code, id));
}

/**
* Gets a context for querying this customer's vehicles
* @returns {VehiclesContext} Context for querying this customer's vehicles
Expand Down
4 changes: 4 additions & 0 deletions src/resources/Customer.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import Sign from './Sign';
import SignsContext from './SignsContext';
import Stop from './Stop';
import StopsContext from './StopsContext';
import Tag from './Tag';
import TagsContext from './TagsContext';
import Vehicle from './Vehicle';
import VehiclesContext from './VehiclesContext';

Expand All @@ -28,6 +30,8 @@ describe('When getting resources related to a customer', () => {
it('should allow a sign to be retrieved', () => customer.sign().should.be.instanceof(Sign));
it('should allow stops to be searched', () => customer.stops().should.be.instanceof(StopsContext));
it('should allow a stop to be retrieved', () => customer.stop().should.be.instanceof(Stop));
it('should allow tags to be searched', () => customer.tags().should.be.instanceof(TagsContext));
it('should allow a tag to be retrieved', () => customer.tag().should.be.instanceof(Tag));
it('should allow vehicles to be searched', () => customer.vehicles().should.be.instanceof(VehiclesContext));
it('should allow a vehicle to be retrieved', () => customer.vehicle().should.be.instanceof(Vehicle));
});
4 changes: 2 additions & 2 deletions src/resources/SignMessage.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ class SignMessage extends Resource {
const hydrated = !Object.keys(newProperties).every(k => k === 'href');
const references = {
routes: newProperties.routes && newProperties.routes.map(x => new Route(this.client, x)),
schedules: newProperties.schedules && newProperties.schedules
.map(x => new SignMessageSchedule(this.client, x)),
schedules: newProperties.schedules &&
newProperties.schedules.map(x => new SignMessageSchedule(this.client, x)),
signs: newProperties.signs && newProperties.signs.map(x => new Sign(this.client, x)),
stops: newProperties.stops && newProperties.stops.map(x => new Stop(this.client, x)),
tags: newProperties.tags && newProperties.tags.map(x => new Tag(this.client, x)),
Expand Down
22 changes: 22 additions & 0 deletions src/resources/Tag.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,28 @@ class Tag extends Resource {
hydrated,
});
}

/**
* Makes a href for a given customer code and ID
* @param {string} customerCode Customer code
* @param {Number} id Tag ID
* @returns {string} URI to instance of tag
*/
static makeHref(customerCode, id) {
return {
href: `/1/${customerCode}/tags/${id}`,
};
}

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

export default Tag;
36 changes: 31 additions & 5 deletions src/resources/Tag.test.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,44 @@
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import fetchMock from 'fetch-mock';
import Client from '../Client';
import Tag from './Tag';
import { tags as mockTags } from '../mocks';

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

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

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

describe('When instantiating a tag based on an object', () => {
const client = new Client();
const mockTag = {
href: '/1/SYNC/tags/1',
name: 'tag',
};
const tag = new Tag(client, mockTag);
const tag = new Tag(client, mockTags.getById(3));

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

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

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

let promise;
beforeEach(() => {
promise = new Tag(client, Tag.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/tags/3'));
it('should be hydrated', () => promise.then(v => v.hydrated).should.eventually.equal(true));
});
48 changes: 48 additions & 0 deletions src/resources/TagsContext.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import 'isomorphic-fetch';
import PagedContext from './PagedContext';
import Tag from './Tag';

/**
* Tag querying context
*
* This is used to query the list of tags for a customer
*/
class TagsContext extends PagedContext {
/**
* Creates a new tag context
* @param {Client} client Instance of pre-configured client
* @param {string} customerCode Customer code
* @param {Object} params Object of querystring parameters to append to the URL
*/
constructor(client, customerCode, params) {
super(client, { ...params });
this.code = customerCode;
}

/**
* Sets the query term for the context
* @example
* const tags = new TagContext(...);
* tags
* .withQuery('LA')
* .getPage()
* .then(page => ...);
* @param {string} term Query term to search for
* @returns {TagsContext} Returns itself
*/
withQuery(term) {
this.params.q = term;
return this;
}

/**
* Gets the first page of results for this context
* @returns {Promise} If successful, a page of Tag objects
* @see Tag
*/
getPage() {
return this.page(Tag, `/1/${this.code}/tags`);
}
}

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

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

describe('When building a query for tags', () => {
const client = new Client();
client.setAuthenticated();

beforeEach(() => fetchMock
.get(client.resolve('/1/SYNC/tags?page=9&perPage=27&q=valid&sort='), mockTags.list)
.catch(503));
afterEach(fetchMock.restore);

let promise;
beforeEach(() => {
const tags = new TagsContext(client, 'SYNC');
promise = tags
.withPage(9)
.withPerPage(27)
.withQuery('valid')
.getPage();
});

it('should make the expected request', () => promise.should.be.fulfilled);
});

0 comments on commit ef39563

Please sign in to comment.