ghunti-devour-client
Version:
A lightweight, framework agnostic, flexible JSON API client
648 lines (549 loc) • 21 kB
JavaScript
/* global describe, it, beforeEach, afterEach */
import JsonApi from '../../src/index'
import jsonApiGetMiddleware from '../../src/middleware/json-api/req-get'
import jsonApiDeleteMiddleware from '../../src/middleware/json-api/req-delete'
import mockResponse from '../helpers/mock-response'
import expect from 'expect.js'
import sinon from 'sinon'
describe('JsonApi', () => {
var jsonApi = null
beforeEach(() => {
jsonApi = new JsonApi({apiUrl: 'http://myapi.com'})
})
afterEach(() => {
jsonApi.resetBuilder()
})
describe('Constructors', () => {
it('should allow both object and deprecated constructors to be used', () => {
let jsonApi
jsonApi = new JsonApi('http://myapi.com')
expect(jsonApi).to.be.a(JsonApi)
jsonApi = new JsonApi('http://myapi.com', [])
expect(jsonApi).to.be.a(JsonApi)
jsonApi = new JsonApi({apiUrl: 'http://myapi.com'})
expect(jsonApi).to.be.a(JsonApi)
})
it('should allow apiUrl to be set via the initializer object', () => {
let jsonApi = new JsonApi({apiUrl: 'http://myapi.com'})
expect(jsonApi.apiUrl).to.eql('http://myapi.com')
})
it('should allow middleware to be set via the initializer object', () => {
let middleware = [
{
name: 'm1',
req: function (req) {
return req
},
res: function (res) {
return res
}
},
{
name: 'm2',
req: function (req) {
return req
},
res: function (res) {
return res
}
}
]
let jsonApi = new JsonApi({apiUrl: 'http://myapi.com', middleware: middleware})
expect(jsonApi.middleware).to.eql(middleware)
expect(jsonApi.apiUrl).to.eql('http://myapi.com')
})
it('should set the apiUrl during setup', () => {
expect(jsonApi.apiUrl).to.eql('http://myapi.com')
})
it('should have a empty models and middleware properties after instantiation', () => {
expect(jsonApi.models).to.be.an('object')
expect(jsonApi.middleware).to.be.an('array')
})
it('should initialize with an empty headers object', () => {
expect(jsonApi.headers).to.be.an('object')
expect(jsonApi.headers).to.eql({})
})
it('should allow users to add headers', () => {
jsonApi.headers['A-Header-Name'] = 'a value'
expect(jsonApi.headers).to.eql({
'A-Header-Name': 'a value'
})
})
it('should allow users to add HTTP Basic Auth options', (done) => {
jsonApi = new JsonApi({apiUrl: 'http://myapi.com', auth: {username: 'admin', password: 'cheesecake'}})
jsonApi.define('foo', {title: ''})
let inspectorMiddleware = {
name: 'inspector-middleware',
req: (payload) => {
expect(payload.req.auth).to.be.eql({username: 'admin', password: 'cheesecake'})
return {}
}
}
const jsonApiHttpBasicAuthMiddleware = require('./../../src/middleware/json-api/req-http-basic-auth')
jsonApi.middleware = [jsonApiHttpBasicAuthMiddleware, inspectorMiddleware]
jsonApi.one('foo', 1).get().then(() => done())
})
it.skip('should throw Exception if the constructor does not receive proper arguments', () => {
expect(function () {
throw new Error('boom!')
}).toThrow(/boom/)
})
})
describe('urlFor, pathFor and path builders', () => {
it('should construct collection paths for models', () => {
jsonApi.define('product', {})
expect(jsonApi.collectionPathFor('product')).to.eql('products')
})
it('should allow overrides for collection paths', () => {
jsonApi.define('product', {}, {collectionPath: 'my-products'})
expect(jsonApi.collectionPathFor('product')).to.eql('my-products')
})
it('should allow arbitrary collections without a model', () => {
expect(jsonApi.collectionPathFor('foo')).to.eql('foos')
})
it('should construct single resource paths for models', () => {
jsonApi.define('product', {})
expect(jsonApi.resourcePathFor('product', 1)).to.eql('products/1')
})
it('should construct collection urls for models', () => {
jsonApi.define('product', {})
expect(jsonApi.collectionUrlFor('product')).to.eql('http://myapi.com/products')
})
it('should construct single resource urls for models', () => {
jsonApi.define('product', {})
expect(jsonApi.resourceUrlFor('product', 1)).to.eql('http://myapi.com/products/1')
})
it('should allow urlFor to be called with various options', () => {
expect(jsonApi.urlFor({model: 'foo', id: 1})).to.eql('http://myapi.com/foos/1')
expect(jsonApi.urlFor({model: 'foo'})).to.eql('http://myapi.com/foos')
expect(jsonApi.urlFor({})).to.eql('http://myapi.com/')
expect(jsonApi.urlFor()).to.eql('http://myapi.com/')
expect(jsonApi.all('foo').urlFor()).to.eql('http://myapi.com/foos')
})
it('should allow pathFor to be called with various options', () => {
expect(jsonApi.pathFor({model: 'foo', id: 1})).to.eql('foos/1')
expect(jsonApi.pathFor({model: 'foo'})).to.eql('foos')
expect(jsonApi.pathFor({})).to.eql('')
expect(jsonApi.pathFor()).to.eql('')
expect(jsonApi.all('foo').pathFor()).to.eql('foos')
})
})
describe('Middleware', () => {
it('should allow users to register middleware', () => {
let catMiddleWare = {
name: 'cat-middleware',
req: function (req) {
return req
},
res: function (res) {
return res
}
}
jsonApi.middleware.unshift(catMiddleWare)
expect(jsonApi.middleware[0].name).to.eql('cat-middleware')
})
it('should allow users to register middleware before or after existing middleware', () => {
let responseMiddleware = jsonApi.middleware.filter(middleware => middleware.name === 'response')[0]
let beforeMiddleware = {
name: 'before'
}
let afterMiddleware = {
name: 'after'
}
let index = jsonApi.middleware.indexOf(responseMiddleware)
jsonApi.insertMiddlewareBefore('response', beforeMiddleware)
jsonApi.insertMiddlewareAfter('response', afterMiddleware)
expect(jsonApi.middleware.indexOf(beforeMiddleware)).to.eql(index)
expect(jsonApi.middleware.indexOf(afterMiddleware)).to.eql(index + 2)
})
})
describe('Models and serializers', () => {
it('should expose the serialize and deserialize objects', () => {
expect(jsonApi.serialize.collection).to.be.a('function')
expect(jsonApi.serialize.resource).to.be.a('function')
expect(jsonApi.deserialize.collection).to.be.a('function')
expect(jsonApi.deserialize.resource).to.be.a('function')
})
it('should allow users to define models', () => {
jsonApi.define('product', {
id: '',
title: ''
})
expect(jsonApi.models['product']).to.be.an('object')
expect(jsonApi.models['product']['attributes']).to.have.key('id')
expect(jsonApi.models['product']['attributes']).to.have.key('title')
})
})
describe('Basic API calls', () => {
it('should make basic find calls', (done) => {
mockResponse(jsonApi, {
data: {
data: {
id: '1',
type: 'products',
attributes: {
title: 'Some Title'
}
}
}
})
jsonApi.define('product', {
title: ''
})
jsonApi.find('product', 1).then((product) => {
expect(product.id).to.eql('1')
expect(product.title).to.eql('Some Title')
done()
}).catch(err => console.log(err))
})
it('should make basic findAll calls', (done) => {
mockResponse(jsonApi, {
data: {
data: [
{
id: '1',
type: 'products',
attributes: {
title: 'Some Title'
}
},
{
id: '2',
type: 'products',
attributes: {
title: 'Another Title'
}
}
]
}
})
jsonApi.define('product', {
title: ''
})
jsonApi.findAll('product').then((products) => {
expect(products[0].id).to.eql('1')
expect(products[0].title).to.eql('Some Title')
expect(products[1].id).to.eql('2')
expect(products[1].title).to.eql('Another Title')
done()
}).catch(err => console.log(err))
})
it('should make basic create call', (done) => {
let inspectorMiddleware = {
name: 'inspector-middleware',
req: (payload) => {
expect(payload.req.method).to.be.eql('POST')
expect(payload.req.url).to.be.eql('http://myapi.com/foos')
expect(payload.req.data).to.be.eql({title: 'foo'})
return {}
}
}
jsonApi.middleware = [inspectorMiddleware]
jsonApi.define('foo', {
title: ''
})
jsonApi.create('foo', {title: 'foo'}).then(() => done()).catch(() => done())
})
it('should include meta information on response objects', (done) => {
mockResponse(jsonApi, {
data: {
meta: {
totalObjects: 1
},
data: [{
id: '1',
type: 'products',
attributes: {
title: 'Some Title'
}
}]
}
})
jsonApi.define('product', {
title: ''
})
jsonApi.findAll('product').then((products) => {
expect(products.meta.totalObjects).to.eql(1)
expect(products[0].id).to.eql('1')
expect(products[0].title).to.eql('Some Title')
done()
}).catch(err => console.log(err))
})
it('should expose a method for arbitrary HTTP calls', () => {
const url = 'https://example.com'
const method = 'PATCH'
const params = { id: 3 }
const data = { body: 'something different' }
jsonApi.runMiddleware = sinon.spy()
jsonApi.request(url, method, params, data)
expect(jsonApi.runMiddleware.called).to.be.truthy
expect(jsonApi.runMiddleware.calledWith(url, method, params, data)).to.be.truthy
})
it('should handle null primary data', (done) => {
mockResponse(jsonApi, {
data: {
data: null
}
})
jsonApi.define('product', {
title: ''
})
jsonApi.find('product', 1).then((product) => {
expect(product).to.eql(null)
done()
}).catch(err => console.log(err))
})
it('should have an empty body on GET requests', (done) => {
let inspectorMiddleware = {
name: 'inspector-middleware',
req: (payload) => {
expect(payload.req.method).to.be.eql('GET')
expect(payload.req.data).to.be(undefined)
expect(payload.req.url).to.be.eql('http://myapi.com/foos/1')
return {}
}
}
jsonApi.middleware = [jsonApiGetMiddleware, inspectorMiddleware]
jsonApi.one('foo', 1).find().then(() => done()).catch(() => done())
})
it('should have an empty body on DELETE requests', (done) => {
let inspectorMiddleware = {
name: 'inspector-middleware',
req: (payload) => {
expect(payload.req.method).to.be.eql('DELETE')
expect(payload.req.data).to.be(undefined)
expect(payload.req.url).to.be.eql('http://myapi.com/foos/1')
return {}
}
}
jsonApi.middleware = [jsonApiDeleteMiddleware, inspectorMiddleware]
jsonApi.destroy('foo', 1).then(() => done()).catch(() => done())
})
})
describe('Builder pattern for route construction', () => {
beforeEach(() => {
jsonApi.define('foo', {title: ''})
jsonApi.define('bar', {title: ''})
jsonApi.define('baz', {title: ''})
})
it('should respect resetBuilderOnCall', (done) => {
let inspectorMiddleware = {
name: 'inspector-middleware',
req: (payload) => {
expect(payload.req.method).to.be.eql('GET')
expect(payload.req.url).to.be.eql('http://myapi.com/')
return {}
}
}
jsonApi.middleware = [inspectorMiddleware]
jsonApi.get()
.then(() => {
let inspectorMiddleware = {
name: 'inspector-middleware',
req: (payload) => {
expect(payload.req.method).to.be.eql('GET')
expect(payload.req.url).to.be.eql('http://myapi.com/foos')
return {}
}
}
jsonApi.middleware = [inspectorMiddleware]
return jsonApi.all('foo').get()
})
.then(() => done())
.catch(() => done())
expect(jsonApi.buildUrl()).to.eql('http://myapi.com/')
})
it('should respect resetBuilderOnCall when it is disabled', (done) => {
jsonApi = new JsonApi({apiUrl: 'http://myapi.com', resetBuilderOnCall: false})
let inspectorMiddleware = {
name: 'inspector-middleware',
req: (payload) => {
expect(payload.req.method).to.be.eql('GET')
expect(payload.req.url).to.be.eql('http://myapi.com/foos/1')
return {}
}
}
jsonApi.middleware = [inspectorMiddleware]
jsonApi.one('foo', 1).get().then(() => {
let inspectorMiddleware = {
name: 'inspector-middleware',
req: (payload) => {
expect(payload.req.method).to.be.eql('GET')
expect(payload.req.url).to.be.eql('http://myapi.com/foos/1/bars')
return {}
}
}
jsonApi.middleware = [inspectorMiddleware]
return jsonApi.all('bar').get().then(() => done())
}).catch(() => done())
})
it('should allow builders to be used', () => {
expect(jsonApi.buildUrl()).to.eql('http://myapi.com/')
})
it('should allow builders on all', () => {
expect(jsonApi.all('foo').all('bar').all('baz').pathFor()).to.eql('foos/bars/bazs')
jsonApi.resetBuilder()
expect(jsonApi.all('foos').all('bars').all('bazs').pathFor()).to.eql('foos/bars/bazs')
})
it('should allow builders on one', () => {
expect(jsonApi.one('foo', 1).one('bar', 2).one('baz', 3).pathFor()).to.eql('foos/1/bars/2/bazs/3')
jsonApi.resetBuilder()
expect(jsonApi.one('foos', 1).one('bars', 2).one('bazs', 3).pathFor()).to.eql('foos/1/bars/2/bazs/3')
})
it('should allow builders on all and one', () => {
expect(jsonApi.one('foo', 1).one('bar', 2).all('baz').pathFor()).to.eql('foos/1/bars/2/bazs')
jsonApi.resetBuilder()
expect(jsonApi.one('foos', 1).one('bars', 2).all('bazs').pathFor()).to.eql('foos/1/bars/2/bazs')
})
it('should allow builders to be called to the base url', (done) => {
mockResponse(jsonApi, {
data: {
data: [
{
id: '1',
type: 'foo',
attributes: {
title: 'foo 1'
}
}
]
}
})
jsonApi.get().then((foos) => {
expect(foos[0].id).to.eql('1')
expect(foos[0].title).to.eql('foo 1')
done()
}).catch(err => console.log(err))
})
it('should allow builders to be called with get', (done) => {
let inspectorMiddleware = {
name: 'inspector-middleware',
req: (payload) => {
expect(payload.req.method).to.be.eql('GET')
expect(payload.req.url).to.be.eql('http://myapi.com/')
return {}
}
}
jsonApi.middleware = [inspectorMiddleware]
jsonApi.get().then(() => done()).catch(() => done())
})
it('should allow builders to be called with get with query params', (done) => {
let inspectorMiddleware = {
name: 'inspector-middleware',
req: (payload) => {
expect(payload.req.method).to.be.eql('GET')
expect(payload.req.url).to.be.eql('http://myapi.com/')
expect(payload.req.params).to.be.eql({page: {number: 2}})
return {}
}
}
jsonApi.middleware = [inspectorMiddleware]
jsonApi.get({page: {number: 2}}).then(() => done()).catch(() => done())
})
it('should allow builders to be called with get on all', (done) => {
let inspectorMiddleware = {
name: 'inspector-middleware',
req: (payload) => {
expect(payload.req.method).to.be.eql('GET')
expect(payload.req.url).to.be.eql('http://myapi.com/foos')
return {}
}
}
jsonApi.middleware = [inspectorMiddleware]
jsonApi.all('foo').get().then(() => done()).catch(() => done())
})
it('should allow builders to be called with get on one', (done) => {
let inspectorMiddleware = {
name: 'inspector-middleware',
req: (payload) => {
expect(payload.req.method).to.be.eql('GET')
expect(payload.req.url).to.be.eql('http://myapi.com/foos/1')
return {}
}
}
jsonApi.middleware = [inspectorMiddleware]
jsonApi.one('foo', 1).get().then(() => done()).catch(() => done())
})
it('should allow builders to be called with post', (done) => {
let inspectorMiddleware = {
name: 'inspector-middleware',
req: (payload) => {
expect(payload.req.method).to.be.eql('POST')
expect(payload.req.url).to.be.eql('http://myapi.com/foos')
expect(payload.req.data).to.be.eql({title: 'foo'})
return {}
}
}
jsonApi.middleware = [inspectorMiddleware]
jsonApi.all('foo').post({title: 'foo'}).then(() => done()).catch(() => done())
})
it('should allow builders to be called with post with nested one', (done) => {
let inspectorMiddleware = {
name: 'inspector-middleware',
req: (payload) => {
expect(payload.req.method).to.be.eql('POST')
expect(payload.req.url).to.be.eql('http://myapi.com/foos/1/bars')
expect(payload.req.data).to.be.eql({title: 'foo'})
return {}
}
}
jsonApi.middleware = [inspectorMiddleware]
jsonApi.one('foo', 1).all('bar').post({title: 'foo'}).then(() => done()).catch(() => done())
})
it('should allow builders to be called with patch', (done) => {
let inspectorMiddleware = {
name: 'inspector-middleware',
req: (payload) => {
expect(payload.req.method).to.be.eql('PATCH')
expect(payload.req.url).to.be.eql('http://myapi.com/foos/1')
expect(payload.req.data).to.be.eql({title: 'bar'})
return {}
}
}
jsonApi.middleware = [inspectorMiddleware]
jsonApi.one('foo', 1).patch({title: 'bar'}).then(() => done()).catch(() => done())
})
it('should allow builders to be called with patch with nested one', (done) => {
let inspectorMiddleware = {
name: 'inspector-middleware',
req: (payload) => {
expect(payload.req.method).to.be.eql('PATCH')
expect(payload.req.url).to.be.eql('http://myapi.com/foos/1/bars')
expect(payload.req.data).to.be.eql({title: 'bar'})
return {}
}
}
jsonApi.middleware = [inspectorMiddleware]
jsonApi.one('foo', 1).all('bar').patch({title: 'bar'}).then(() => done()).catch(() => done())
})
it('should allow builders to be called with destroy', (done) => {
let inspectorMiddleware = {
name: 'inspector-middleware',
req: (payload) => {
expect(payload.req.method).to.be.eql('DELETE')
expect(payload.req.url).to.be.eql('http://myapi.com/foos/1')
return {}
}
}
jsonApi.middleware = [inspectorMiddleware]
jsonApi.one('foo', 1).destroy().then(() => done()).catch(() => done())
})
it('should allow builders to be called with destroy with nested one', (done) => {
let inspectorMiddleware = {
name: 'inspector-middleware',
req: (payload) => {
expect(payload.req.method).to.be.eql('DELETE')
expect(payload.req.url).to.be.eql('http://myapi.com/foos/1/bars/2')
return {}
}
}
jsonApi.middleware = [inspectorMiddleware]
jsonApi.one('foo', 1).one('bar', 2).destroy().then(() => done()).catch(() => done())
})
it('should Wacky Waving Inflatable Arm-Flailing Tubeman! Wacky Waving Inflatable Arm-Flailing Tubeman! Wacky Waving Inflatable Arm-Flailing Tubeman!', () => {
jsonApi.one('foo', 1).one('bar', 2).all('foo').one('bar', 3).all('baz').one('baz', 1).one('baz', 2).one('baz', 3)
expect(jsonApi.pathFor()).to.be.eql('foos/1/bars/2/foos/bars/3/bazs/bazs/1/bazs/2/bazs/3')
expect(jsonApi.urlFor()).to.be.eql('http://myapi.com/foos/1/bars/2/foos/bars/3/bazs/bazs/1/bazs/2/bazs/3')
})
})
})