Skip to content

Commit

Permalink
feat(signs): Added Signs API client (EN-1573) (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
zuver authored and thzinc committed Mar 24, 2017
1 parent 57bf228 commit 4b2f8ed
Show file tree
Hide file tree
Showing 8 changed files with 278 additions and 1 deletion.
48 changes: 48 additions & 0 deletions src/examples/get_signs.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, signs as mockSigns } from '../mocks';

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

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

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

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

const signsPromise = api.customer('SYNC').signs()
.withQuery('first') // Signs containing "first" in their name
.getPage()
.then(page => page.list)
.then(signs => signs); // Do things with list of signs

return signsPromise;
});
});

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

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

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

const signPromise = api.customer('SYNC').sign(1)
.fetch()
.then(sign => sign); // Do things with sign

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

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

fetchMock
.get(client.resolve('/1/SYNC/signs?page=1&perPage=10&q=first'), listResponse)
.get(client.resolve('/1/SYNC/signs/1'), singleResponse);
},
getById: id => signs.list.find(v => v.id === id),
list: [
{
href: '/1/SYNC/signs/1',
id: 1,
name: 'The first sign',
enabled: true,
},
],
};

export const vehicles = {
setUpSuccessfulMock: (client) => {
const listResponse = () => new Response(
Expand Down
21 changes: 20 additions & 1 deletion src/resources/Customer.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import Resource from './Resource';
import VehiclesContext from './VehiclesContext';
import Sign from './Sign';
import SignsContext from './SignsContext';
import Vehicle from './Vehicle';
import VehiclesContext from './VehiclesContext';

/**
* Customer resource
Expand All @@ -23,6 +25,23 @@ class Customer extends Resource {
this.code = customerCode;
}

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

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

/**
* Gets a context for querying this customer's vehicles
* @returns {VehicleContext} 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 @@ -2,6 +2,8 @@ import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import Client from '../Client';
import Customer from './Customer';
import Sign from './Sign';
import SignsContext from './SignsContext';
import Vehicle from './Vehicle';
import VehiclesContext from './VehiclesContext';

Expand All @@ -12,6 +14,8 @@ describe('When getting resources related to a customer', () => {
const client = new Client();
const customer = new Customer(client, 'SYNC');

it('should allow signs to be searched', () => customer.signs().should.be.instanceof(SignsContext));
it('should allow a sign to be retrieved', () => customer.sign().should.be.instanceof(Sign));
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));
});
57 changes: 57 additions & 0 deletions src/resources/Sign.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import Resource from './Resource';

/**
* Sign resource
*/
class Sign extends Resource {
/**
* Creates a new sign
*
* Will populate itself with the values given to it after the client parameter
* @example <caption>Assigning partial sign data to a new instance</caption>
* const client = new Client();
* const partialSignData = {
* href: '/1/SYNC/sign/2',
* name: 'The second sign',
* };
* const sign = new Sign(client, partialSignData);
*
* sign.hydrated == true;
* @param {Client} client Instance of pre-configured client
* @param {Array} rest Remaining arguments to use in assigning values to this instance
*/
constructor(client, ...rest) {
super(client);

const newProperties = Object.assign({}, ...rest);
const hydrated = !Object.keys(newProperties).every(k => k === 'href');

Object.assign(this, newProperties, {
hydrated,
});
}

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

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

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

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

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

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

describe('When instantiating a sign based on an object', () => {
const client = new Client();
const sign = new Sign(client, mockSigns.getById(1));

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

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

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

let promise;
beforeEach(() => {
promise = new Sign(client, Sign.makeHref('SYNC', 1)).fetch();
});

it('should resolve the promise', () => promise.should.be.fulfilled);
it('should set the ID', () => promise.then(v => v.id).should.eventually.equal(1));
it('should set the href', () => promise.then(v => v.href).should.eventually.equal('/1/SYNC/signs/1'));
it('should be hydrated', () => promise.then(v => v.hydrated).should.eventually.equal(true));
});
48 changes: 48 additions & 0 deletions src/resources/SignsContext.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import 'isomorphic-fetch';
import PagedContext from './PagedContext';
import Sign from './Sign';

/**
* Sign querying context
*
* This is used to query the list of signs for a customer
*/
class SignsContext extends PagedContext {
/**
* Creates a new sign 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 signs = new SignsContext(...);
* signs
* .withQuery('12')
* .getPage()
* .then(page => ...);
* @param {string} term Query term to search for
* @returns {SignsContext} 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 Sign objects
* @see Sign
*/
getPage() {
return this.page(Sign, `/1/${this.code}/signs`);
}
}

export default SignsContext;
31 changes: 31 additions & 0 deletions src/resources/SignsContext.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 SignsContext from './SignsContext';
import { signs as mockSigns } from '../mocks';

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

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

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

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

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

0 comments on commit 4b2f8ed

Please sign in to comment.