@coding-blocks/jsonapi-server
Version:
A config driven NodeJS framework implementing json:api
794 lines (663 loc) • 28.4 kB
JavaScript
const assert = require('assert')
const helpers = require('./helpers.js')
const request = require('request')
const jsonApiTestServer = require('../example/server.js')
describe('Testing jsonapi-server', () => {
describe('Searching for resources', () => {
it('unknown resource should error', done => {
const url = 'http://localhost:16006/rest/foobar'
helpers.request({
method: 'GET',
url
}, (err, res, json) => {
assert.equal(err, null)
helpers.validateError(json)
assert.equal(res.statusCode, '404', 'Expecting 404')
done()
})
})
it('empty search should return all objects', done => {
const url = 'http://localhost:16006/rest/articles'
helpers.request({
method: 'GET',
url
}, (err, res, json) => {
assert.equal(err, null)
json = helpers.validateJson(json)
assert.equal(res.statusCode, '200', 'Expecting 200 OK')
assert.deepEqual(json.included, [ ], 'Response should have no included resources')
assert.equal(json.data.length, 4, 'Response should contain exactly 4 resources')
json.data.forEach(resource => {
helpers.validateArticle(resource)
})
done()
})
})
describe('applying sort', () => {
it('ASC sort', done => {
const url = 'http://localhost:16006/rest/articles?sort=title'
helpers.request({
method: 'GET',
url
}, (err, res, json) => {
assert.equal(err, null)
json = helpers.validateJson(json)
assert.equal(res.statusCode, '200', 'Expecting 200 OK')
assert.equal(json.data.length, 4, 'Response should contain exactly 4 resources')
const previous = json.data[0]
let current
for (let i = 1; i < json.data.length; i++) {
current = json.data[i]
assert.ok(previous.attributes.title < current.attributes.title, 'Resources should be ordered')
}
done()
})
})
it('DESC sort', done => {
const url = 'http://localhost:16006/rest/articles?sort=-title'
helpers.request({
method: 'GET',
url
}, (err, res, json) => {
assert.equal(err, null)
json = helpers.validateJson(json)
assert.equal(res.statusCode, '200', 'Expecting 200 OK')
assert.equal(json.data.length, 4, 'Response should contain exactly 4 resources')
const previous = json.data[0]
let current
for (let i = 1; i < json.data.length; i++) {
current = json.data[i]
assert.ok(previous.attributes.title > current.attributes.title, 'Resources should be ordered')
}
done()
})
})
})
describe('applying filter', () => {
it('unknown attribute should error', done => {
const url = 'http://localhost:16006/rest/articles?filter[foobar]=<M'
helpers.request({
method: 'GET',
url
}, (err, res, json) => {
assert.equal(err, null)
json = helpers.validateError(json)
assert.equal(res.statusCode, '403', 'Expecting 403 FORBIDDEN')
const error = json.errors[0]
assert.equal(error.code, 'EFORBIDDEN')
assert.equal(error.title, 'Invalid filter')
done()
})
})
it('unknown multiple attribute should error', done => {
const url = 'http://localhost:16006/rest/articles?filter[foo]=bar&filter[foo]=baz'
helpers.request({
method: 'GET',
url
}, (err, res, json) => {
assert.equal(err, null)
json = helpers.validateError(json)
assert.equal(res.statusCode, '403', 'Expecting 403 FORBIDDEN')
const error = json.errors[0]
assert.equal(error.code, 'EFORBIDDEN')
assert.equal(error.title, 'Invalid filter')
done()
})
})
it('value of wrong type should error', done => {
const url = 'http://localhost:16006/rest/photos?filter[raw]=bob'
helpers.request({
method: 'GET',
url
}, (err, res, json) => {
assert.equal(err, null)
json = helpers.validateError(json)
assert.equal(res.statusCode, '403', 'Expecting 403 FORBIDDEN')
const error = json.errors[0]
assert.equal(error.code, 'EFORBIDDEN')
assert.equal(error.title, 'Invalid filter')
assert(error.detail.match("Filter value for key '.*?' is invalid"))
done()
})
})
it('equality for strings', done => {
const url = 'http://localhost:16006/rest/articles?filter[title]=How%20to%20AWS'
helpers.request({
method: 'GET',
url
}, (err, res, json) => {
assert.equal(err, null)
json = helpers.validateJson(json)
assert.equal(res.statusCode, '200', 'Expecting 200 OK')
const titles = json.data.map(i => i.attributes.title)
titles.sort()
assert.deepEqual(titles, [ 'How to AWS' ], 'expected matching resources')
done()
})
})
it('equality for numbers', done => {
const url = 'http://localhost:16006/rest/articles?filter[views]=10'
helpers.request({
method: 'GET',
url
}, (err, res, json) => {
assert.equal(err, null)
json = helpers.validateJson(json)
assert.equal(res.statusCode, '200', 'Expecting 200 OK')
const titles = json.data.map(i => i.attributes.title)
titles.sort()
assert.deepEqual(titles, [ 'NodeJS Best Practices' ], 'expected matching resources')
done()
})
})
describe('equality for booleans', () => {
it('matches false', done => {
const url = 'http://localhost:16006/rest/photos?filter[raw]=false'
helpers.request({
method: 'GET',
url
}, (err, res, json) => {
assert.equal(err, null)
json = helpers.validateJson(json)
assert.equal(res.statusCode, '200', 'Expecting 200 OK')
const photoTypes = json.data.map(i => i.attributes.raw)
assert.deepEqual(photoTypes, [ false, false ], 'expected matching resources')
done()
})
})
it('matches true', done => {
const url = 'http://localhost:16006/rest/photos?filter[raw]=true'
helpers.request({
method: 'GET',
url
}, (err, res, json) => {
assert.equal(err, null)
json = helpers.validateJson(json)
assert.equal(res.statusCode, '200', 'Expecting 200 OK')
const photoTypes = json.data.map(i => i.attributes.raw)
assert.deepEqual(photoTypes, [ true, true ], 'expected matching resources')
done()
})
})
})
it('less than for strings', done => {
const url = 'http://localhost:16006/rest/articles?filter[title]=<M'
helpers.request({
method: 'GET',
url
}, (err, res, json) => {
assert.equal(err, null)
json = helpers.validateJson(json)
assert.equal(res.statusCode, '200', 'Expecting 200 OK')
const titles = json.data.map(i => i.attributes.title)
titles.sort()
assert.deepEqual(titles, [ 'How to AWS', 'Linux Rocks' ], 'expected matching resources')
done()
})
})
it('less than for numbers', done => {
const url = 'http://localhost:16006/rest/articles?filter[views]=<23'
helpers.request({
method: 'GET',
url
}, (err, res, json) => {
assert.equal(err, null)
json = helpers.validateJson(json)
assert.equal(res.statusCode, '200', 'Expecting 200 OK')
const titles = json.data.map(i => i.attributes.title)
titles.sort()
assert.deepEqual(titles, [ 'Linux Rocks', 'NodeJS Best Practices' ], 'expected matching resources')
done()
})
})
it('greater than for strings', done => {
const url = 'http://localhost:16006/rest/articles?filter[title]=>M'
helpers.request({
method: 'GET',
url
}, (err, res, json) => {
assert.equal(err, null)
json = helpers.validateJson(json)
assert.equal(res.statusCode, '200', 'Expecting 200 OK')
const titles = json.data.map(i => i.attributes.title)
titles.sort()
assert.deepEqual(titles, [ 'NodeJS Best Practices', 'Tea for Beginners' ], 'expected matching resources')
done()
})
})
it('greater than for numbers', done => {
const url = 'http://localhost:16006/rest/articles?filter[views]=>27'
helpers.request({
method: 'GET',
url
}, (err, res, json) => {
assert.equal(err, null)
json = helpers.validateJson(json)
assert.equal(res.statusCode, '200', 'Expecting 200 OK')
const titles = json.data.map(i => i.attributes.title)
titles.sort()
assert.deepEqual(titles, [ 'How to AWS', 'Tea for Beginners' ], 'expected matching resources')
done()
})
})
it('case insensitive', done => {
const url = 'http://localhost:16006/rest/articles?filter[title]=~linux rocks'
helpers.request({
method: 'GET',
url
}, (err, res, json) => {
assert.equal(err, null)
json = helpers.validateJson(json)
assert.equal(res.statusCode, '200', 'Expecting 200 OK')
const titles = json.data.map(i => i.attributes.title)
assert.deepEqual(titles, [ 'Linux Rocks' ], 'expected matching resources')
done()
})
})
it('case insensitive for non-string types', done => {
const url = 'http://localhost:16006/rest/articles?filter[created]=~2016-01-01'
helpers.request({
method: 'GET',
url
}, (err, res, json) => {
assert.equal(err, null)
json = helpers.validateJson(json)
assert.equal(res.statusCode, '200', 'Expecting 200 OK')
assert.equal(json.data.length, 0, "didn't expect matching resources")
done()
})
})
it('similar to', done => {
const url = 'http://localhost:16006/rest/articles?filter[title]=:for'
helpers.request({
method: 'GET',
url
}, (err, res, json) => {
assert.equal(err, null)
json = helpers.validateJson(json)
assert.equal(res.statusCode, '200', 'Expecting 200 OK')
const titles = json.data.map(i => i.attributes.title)
assert.deepEqual(titles, [ 'Tea for Beginners' ], 'expected matching resources')
done()
})
})
it('similar to for non-string types', done => {
const url = 'http://localhost:16006/rest/articles?filter[created]=:2016-01-01'
helpers.request({
method: 'GET',
url
}, (err, res, json) => {
assert.equal(err, null)
json = helpers.validateJson(json)
assert.equal(res.statusCode, '200', 'Expecting 200 OK')
assert.equal(json.data.length, 0, "didn't expect matching resources")
done()
})
})
it('allows filtering by id', done => {
const url = 'http://localhost:16006/rest/articles?filter[id]=1be0913c-3c25-4261-98f1-e41174025ed5&filter[id]=de305d54-75b4-431b-adb2-eb6b9e546014'
helpers.request({
method: 'GET',
url
}, (err, res, json) => {
assert.equal(err, null)
json = helpers.validateJson(json)
assert.equal(res.statusCode, '200', 'Expecting 200 OK')
assert.equal(json.data.length, 2, 'Should only give the 2x requested resources')
done()
})
})
it('allows for multiple filter values to be combined in a comma-separated list', done => {
const url = 'http://localhost:16006/rest/articles?filter[tags]=6ec62f6d-9f82-40c5-b4f4-279ed1765492,7541a4de-4986-4597-81b9-cf31b6762486,2a3bdea4-a889-480d-b886-104498c86f69'
helpers.request({
method: 'GET',
url
}, (err, res, json) => {
assert.equal(err, null)
json = helpers.validateJson(json)
assert.equal(res.statusCode, '200', 'Expecting 200 OK')
assert.equal(json.data.length, 3, 'Should only give the 3x requested resources')
done()
})
})
it('allows for a compound of comma-separated list filters', done => {
const url = 'http://localhost:16006/rest/articles?filter[tags]=6ec62f6d-9f82-40c5-b4f4-279ed1765492,7541a4de-4986-4597-81b9-cf31b6762486,2a3bdea4-a889-480d-b886-104498c86f69&filter[id]=de305d54-75b4-431b-adb2-eb6b9e546014,1be0913c-3c25-4261-98f1-e41174025ed5'
helpers.request({
method: 'GET',
url
}, (err, res, json) => {
assert.equal(err, null)
json = helpers.validateJson(json)
assert.equal(res.statusCode, '200', 'Expecting 200 OK')
assert.equal(json.data.length, 2, 'Should only give the 2x requested resources')
done()
})
})
it('allows deep filtering', done => {
const url = 'http://localhost:16006/rest/articles?include=author&filter[author]=d850ea75-4427-4f81-8595-039990aeede5&filter[author][firstname]=Mark'
helpers.request({
method: 'GET',
url
}, (err, res, json) => {
assert.equal(err, null)
json = helpers.validateJson(json)
assert.equal(res.statusCode, '200', 'Expecting 200 OK')
assert.equal(json.data.length, 1, 'Should give the one matching resource')
assert.equal(json.included.length, 1, 'Should give the one matching include')
done()
})
})
})
describe('applying fields', () => {
it('unknown attribute should error', done => {
const url = 'http://localhost:16006/rest/articles?fields[article]=title'
helpers.request({
method: 'GET',
url
}, (err, res, json) => {
assert.equal(err, null)
helpers.validateError(json)
assert.equal(res.statusCode, '403', 'Expecting 403')
done()
})
})
it('just title', done => {
const url = 'http://localhost:16006/rest/articles?fields[articles]=title'
request({
method: 'GET',
url
}, (err, res, json) => {
assert.equal(err, null)
json = helpers.validateJson(json)
assert.equal(res.statusCode, '200', 'Expecting 200 OK')
json.data.forEach(resource => {
const keys = Object.keys(resource.attributes)
assert.deepEqual(keys, [ 'title' ], 'should only have the title attribute')
})
done()
})
})
it('title AND content', done => {
const url = 'http://localhost:16006/rest/articles?fields[articles]=title,content'
request({
method: 'GET',
url
}, (err, res, json) => {
assert.equal(err, null)
json = helpers.validateJson(json)
assert.equal(res.statusCode, '200', 'Expecting 200 OK')
json.data.forEach(resource => {
const keys = Object.keys(resource.attributes)
assert.deepEqual(keys, [ 'title', 'content' ], 'should only have the title attribute')
})
done()
})
})
})
describe('applying includes', () => {
it('unknown attribute should error', done => {
const url = 'http://localhost:16006/rest/articles?include=foobar'
helpers.request({
method: 'GET',
url
}, (err, res, json) => {
assert.equal(err, null)
helpers.validateError(json)
assert.equal(res.statusCode, '403', 'Expecting 403')
done()
})
})
it('include author', done => {
const url = 'http://localhost:16006/rest/articles?include=author'
helpers.request({
method: 'GET',
url
}, (err, res, json) => {
assert.equal(err, null)
json = helpers.validateJson(json)
assert.equal(res.statusCode, '200', 'Expecting 200 OK')
assert.equal(json.included.length, 4, 'Should be 4 included resources')
const people = json.included.filter(resource => resource.type === 'people')
assert.equal(people.length, 4, 'Should be 4 included people resources')
done()
})
})
it('include author and photos', done => {
const url = 'http://localhost:16006/rest/articles?include=author,photos'
helpers.request({
method: 'GET',
url
}, (err, res, json) => {
assert.equal(err, null)
json = helpers.validateJson(json)
assert.equal(res.statusCode, '200', 'Expecting 200 OK')
assert.equal(json.included.length, 7, 'Should be 7 included resources')
const people = json.included.filter(resource => resource.type === 'people')
assert.equal(people.length, 4, 'Should be 4 included people resources')
const photos = json.included.filter(resource => resource.type === 'photos')
assert.equal(photos.length, 3, 'Should be 3 included photos resources')
done()
})
})
it('include author.photos and photos', done => {
const url = 'http://localhost:16006/rest/articles?include=author.photos,photos'
helpers.request({
method: 'GET',
url
}, (err, res, json) => {
assert.equal(err, null)
json = helpers.validateJson(json)
assert.equal(res.statusCode, '200', 'Expecting 200 OK')
assert.equal(json.included.length, 8, 'Should be 8 included resources')
const people = json.included.filter(resource => resource.type === 'people')
assert.equal(people.length, 4, 'Should be 4 included people resources')
const photos = json.included.filter(resource => resource.type === 'photos')
assert.equal(photos.length, 4, 'Should be 4 included photos resources')
done()
})
})
it('include author.photos', done => {
const url = 'http://localhost:16006/rest/articles?include=author.photos'
helpers.request({
method: 'GET',
url
}, (err, res, json) => {
assert.equal(err, null)
json = helpers.validateJson(json)
assert.equal(res.statusCode, '200', 'Expecting 200 OK')
assert.equal(json.included.length, 8, 'Should be 8 included resources')
const people = json.included.filter(resource => resource.type === 'people')
assert.equal(people.length, 4, 'Should be 4 included people resources')
const photos = json.included.filter(resource => resource.type === 'photos')
assert.equal(photos.length, 4, 'Should be 4 included photos resources')
done()
})
})
it('include author with filter and people fields', done => {
const url = 'http://localhost:16006/rest/articles?include=author&filter[title]=Linux+Rocks&fields[people]=email'
request({
method: 'GET',
url
}, (err, res, json) => {
assert.equal(err, null)
json = helpers.validateJson(json)
assert.equal(res.statusCode, '200', 'Expecting 200 OK')
assert.equal(json.data.length, 1, 'Should be 1 result article')
assert.equal(json.data[0].attributes.created, '2015-11-11', 'Article should have created attribute')
assert.equal(json.included.length, 1, 'Should be 1 included resource')
const people = json.included.filter(resource => resource.type === 'people')
assert.equal(people.length, 1, 'Should be 1 included people resource')
assert.equal(people[0].attributes.email, 'mark.fermor@example.com', 'Included people should have email attribute')
done()
})
})
it('include author with filter and article fields', done => {
const url = 'http://localhost:16006/rest/articles?include=author&filter[title]=Linux+Rocks&fields[articles]=created'
request({
method: 'GET',
url
}, (err, res, json) => {
assert.equal(err, null)
json = helpers.validateJson(json)
assert.equal(res.statusCode, '200', 'Expecting 200 OK')
assert.equal(json.data.length, 1, 'Should be 1 result article')
assert.equal(json.data[0].attributes.created, '2015-11-11', 'Article should have created attribute')
assert.equal(json.included.length, 1, 'Should be 1 included resource')
const people = json.included.filter(resource => resource.type === 'people')
assert.equal(people.length, 1, 'Should be 1 included people resource')
assert.equal(people[0].attributes.email, 'mark.fermor@example.com', 'Included people should have email attribute')
done()
})
})
it('include author with filter and people+article fields', done => {
const url = 'http://localhost:16006/rest/articles?include=author&filter[title]=Linux+Rocks&fields[people]=email&fields[articles]=created'
request({
method: 'GET',
url
}, (err, res, json) => {
assert.equal(err, null)
json = helpers.validateJson(json)
assert.equal(res.statusCode, '200', 'Expecting 200 OK')
assert.equal(json.data.length, 1, 'Should be 1 result article')
assert.equal(json.data[0].attributes.created, '2015-11-11', 'Article should have created attribute')
assert.equal(json.included.length, 1, 'Should be 1 included resource')
const people = json.included.filter(resource => resource.type === 'people')
assert.equal(people.length, 1, 'Should be 1 included people resource')
assert.equal(people[0].attributes.email, 'mark.fermor@example.com', 'Included people should have email attribute')
done()
})
})
it('include author.photos with filter', done => {
const url = 'http://localhost:16006/rest/articles?include=author.photos&filter[author][firstname]=Mark'
helpers.request({
method: 'GET',
url
}, (err, res, json) => {
assert.equal(err, null)
json = helpers.validateJson(json)
assert.equal(res.statusCode, '200', 'Expecting 200 OK')
assert.equal(json.included.length, 2, 'Should be 2 included resources')
const people = json.included.filter(resource => resource.type === 'people')
assert.equal(people.length, 1, 'Should be 1 included people resource')
const photos = json.included.filter(resource => resource.type === 'photos')
assert.equal(photos.length, 1, 'Should be 1 included photos resource')
done()
})
})
it('include author.photos with multiple filters', done => {
const url = 'http://localhost:16006/rest/articles?include=author.photos&filter[author]=ad3aa89e-9c5b-4ac9-a652-6670f9f27587&filter[author]=cc5cca2e-0dd8-4b95-8cfc-a11230e73116'
helpers.request({
method: 'GET',
url
}, (err, res, json) => {
assert.equal(err, null)
json = helpers.validateJson(json)
assert.equal(res.statusCode, '200', 'Expecting 200 OK')
assert.equal(json.included.length, 5, 'Should be 2 included resources')
const people = json.included.filter(resource => resource.type === 'people')
assert.equal(people.length, 2, 'Should be 2 included people resource')
const photos = json.included.filter(resource => resource.type === 'photos')
assert.equal(photos.length, 3, 'Should be 2 included photos resource')
done()
})
})
it('include author.photos with multiple filters comma delineated', done => {
const url = 'http://localhost:16006/rest/articles?include=author.photos&filter[author][firstname]=Mark,Oli'
helpers.request({
method: 'GET',
url
}, (err, res, json) => {
assert.equal(err, null)
json = helpers.validateJson(json)
assert.equal(res.statusCode, '200', 'Expecting 200 OK')
assert.equal(json.included.length, 4, 'Should be 2 included resources')
const people = json.included.filter(resource => resource.type === 'people')
assert.equal(people.length, 2, 'Should be 2 included people resource')
const photos = json.included.filter(resource => resource.type === 'photos')
assert.equal(photos.length, 2, 'Should be 2 included photos resource')
done()
})
})
})
describe('by foreign key', () => {
it('should find resources by a relation', done => {
const url = 'http://localhost:16006/rest/articles/?filter[photos]=aab14844-97e7-401c-98c8-0bd5ec922d93'
helpers.request({
method: 'GET',
url
}, (err, res, json) => {
assert.equal(err, null)
json = helpers.validateJson(json)
assert.equal(res.statusCode, '200', 'Expecting 200 OK')
assert.equal(json.data.length, 2, 'Should be 2 matching resources')
done()
})
})
it('should find resources by many relations', done => {
const url = 'http://localhost:16006/rest/articles/?filter[photos]=aab14844-97e7-401c-98c8-0bd5ec922d93&filter[photos]=4a8acd65-78bb-4020-b9eb-2d058a86a2a0'
helpers.request({
method: 'GET',
url
}, (err, res, json) => {
assert.equal(err, null)
json = helpers.validateJson(json)
assert.equal(res.statusCode, '200', 'Expecting 200 OK')
assert.equal(json.data.length, 3, 'Should be 3 matching resources')
done()
})
})
it('should error with incorrectly named relations', done => {
const url = 'http://localhost:16006/rest/articles/?filter[photo]=aab14844-97e7-401c-98c8-0bd5ec922d93'
helpers.request({
method: 'GET',
url
}, (err, res, json) => {
assert.equal(err, null)
json = helpers.validateError(json)
assert.equal(res.statusCode, '403', 'Expecting 403 FORBIDDEN')
const error = json.errors[0]
assert.equal(error.code, 'EFORBIDDEN')
assert.equal(error.title, 'Invalid filter')
assert(error.detail.match('do not have attribute or relationship'))
done()
})
})
it('should error when querying the foreign end of a relationship', done => {
const url = 'http://localhost:16006/rest/comments/?filter[article]=aab14844-97e7-401c-98c8-0bd5ec922d93'
helpers.request({
method: 'GET',
url
}, (err, res, json) => {
assert.equal(err, null)
json = helpers.validateError(json)
assert.equal(res.statusCode, '403', 'Expecting 403 FORBIDDEN')
const error = json.errors[0]
assert.equal(error.code, 'EFORBIDDEN')
assert.equal(error.title, 'Invalid filter')
assert(error.detail.match('is a foreign reference and does not exist on'))
done()
})
})
it('should give clean validation errors', done => {
const url = 'http://localhost:16006/rest/articles?include=fdfdds,sdf'
helpers.request({
method: 'GET',
url
}, (err, res, json) => {
assert.equal(err, null)
json = helpers.validateError(json)
assert.equal(res.statusCode, '403', 'Expecting 403 EFORBIDDEN')
assert.equal(json.errors.length, 2, 'Should be 2 errors')
done()
})
})
})
})
before(() => {
jsonApiTestServer.start()
})
after(() => {
jsonApiTestServer.close()
})
})