lux-framework
Version:
Build scalable, Node.js-powered REST APIs with almost no code.
609 lines (506 loc) • 14.8 kB
JavaScript
// @flow
import faker from 'faker';
import { expect } from 'chai';
import { it, describe, before, beforeEach, afterEach } from 'mocha';
import Controller from '../index';
import Serializer from '../../serializer';
import { Model } from '../../database';
import setType from '../../../utils/set-type';
import { getTestApp } from '../../../../test/utils/get-test-app';
import type { Request, Response } from '../../server';
const HOST = 'localhost:4000';
describe('module "controller"', () => {
describe('class Controller', () => {
let Post: Class<Model>;
let subject: Controller;
const attributes = [
'id',
'body',
'title',
'isPublic',
'createdAt',
'updatedAt'
];
const assertRecord = (item, keys = attributes) => {
expect(item).to.be.an.instanceof(Post);
if (item instanceof Post) {
expect(item.rawColumnData).to.have.all.keys(keys);
}
};
before(async () => {
const app = await getTestApp();
const model = app.models.get('post');
if (model) {
Post = model;
}
subject = new Controller({
model: Post,
namespace: '',
serializer: new Serializer({
model: Post,
parent: null,
namespace: ''
})
});
subject.controllers = app.controllers;
});
describe('#index()', () => {
const createRequest = (params = {}): Request => setType(() => ({
params,
route: {
controller: subject
},
defaultParams: {
sort: 'createdAt',
filter: {},
fields: {
posts: attributes
},
page: {
size: 25,
number: 1
}
}
}));
it('returns an array of records', async () => {
const request = createRequest();
const result = await subject.index(request);
expect(result).to.be.an('array').with.lengthOf(25);
result.forEach(item => assertRecord(item));
});
it('supports specifying page size', async () => {
const request = createRequest({
page: {
size: 10
}
});
const result = await subject.index(request);
expect(result).to.be.an('array').with.lengthOf(10);
result.forEach(item => assertRecord(item));
});
it('supports filter parameters', async () => {
const request = createRequest({
filter: {
isPublic: false
}
});
const result = await subject.index(request);
expect(result).to.be.an('array').with.length.above(0);
result.forEach(item => {
assertRecord(item);
expect(Reflect.get(item, 'isPublic')).to.be.false;
});
});
it('supports sparse field sets', async () => {
const request = createRequest({
fields: {
posts: ['id', 'title']
}
});
const result = await subject.index(request);
expect(result).to.be.an('array').with.lengthOf(25);
result.forEach(item => assertRecord(item, ['id', 'title']));
});
it('supports eager loading relationships', async () => {
const request = createRequest({
include: ['user'],
fields: {
users: [
'id',
'name',
'email'
]
}
});
const result = await subject.index(request);
expect(result).to.be.an('array').with.lengthOf(25);
result.forEach(item => {
assertRecord(item, [
...attributes,
'user'
]);
expect(item.rawColumnData.user).to.have.all.keys([
'id',
'name',
'email'
]);
});
});
});
describe('#show()', () => {
const createRequest = (params = {}): Request => setType(() => ({
params,
route: {
controller: subject
},
defaultParams: {
fields: {
posts: attributes
}
}
}));
it('returns a single record', async () => {
const request = createRequest({ id: 1 });
const result = await subject.show(request);
expect(result).to.be.ok;
if (result) {
assertRecord(result);
}
});
it('throws an error if the record is not found', async () => {
const request = createRequest({ id: 10000 });
await subject.show(request).catch(err => {
expect(err).to.be.an.instanceof(Error);
});
});
it('supports sparse field sets', async () => {
const request = createRequest({
id: 1,
fields: {
posts: ['id', 'title']
}
});
const result = await subject.show(request);
expect(result).to.be.ok;
assertRecord(result, ['id', 'title']);
});
it('supports eager loading relationships', async () => {
const request = createRequest({
id: 1,
include: ['user'],
fields: {
users: [
'id',
'name',
'email'
]
}
});
const result = await subject.show(request);
expect(result).to.be.ok;
if (result) {
assertRecord(result, [
...attributes,
'user'
]);
expect(result.rawColumnData.user).to.have.all.keys([
'id',
'name',
'email'
]);
}
});
});
describe('#create()', () => {
let result: Model;
const createRequest = (params = {}): Request => setType(() => ({
params,
url: {
pathname: '/posts'
},
route: {
controller: subject
},
headers: new Map([
['host', HOST]
]),
connection: {
encrypted: false
},
defaultParams: {
fields: {
posts: attributes,
users: ['id']
}
}
}));
const createResponse = (): Response => setType(() => ({
headers: new Map(),
statusCode: 200,
setHeader(key: string, value: string): void {
this.headers.set(key, value);
},
getHeader(key: string): string | void {
return this.headers.get(key);
}
}));
afterEach(async () => {
await result.destroy();
});
it('returns the newly created record', async () => {
const response = createResponse();
const request = createRequest({
include: ['user'],
data: {
type: 'posts',
attributes: {
title: '#create() Test',
isPublic: true
},
relationships: {
user: {
data: {
id: 1,
type: 'posts'
}
}
}
},
fields: {
users: ['id']
}
});
result = await subject.create(request, response);
assertRecord(result, [
'id',
'user',
'title',
'isPublic',
'createdAt',
'updatedAt'
]);
const user = await Reflect.get(result, 'user');
const title = Reflect.get(result, 'title');
const isPublic = Reflect.get(result, 'isPublic');
expect(user.id).to.equal(1);
expect(title).to.equal('#create() Test');
expect(isPublic).to.equal(true);
});
it('sets `response.statusCode` to the number `201`', async () => {
const response = createResponse();
const request = createRequest({
data: {
type: 'posts',
attributes: {
title: '#create() Test'
}
}
});
result = await subject.create(request, response);
expect(response.statusCode).to.equal(201);
});
it('sets the correct `Location` header', async () => {
const response = createResponse();
const request = createRequest({
data: {
type: 'posts',
attributes: {
title: '#create() Test'
}
}
});
result = await subject.create(request, response);
const id = Reflect.get(result, 'id');
const location = response.getHeader('Location');
expect(location).to.equal(`http://${HOST}/posts/${id}`);
});
});
describe('#update()', () => {
let User;
let record;
const createRequest = (params = {}): Request => setType(() => ({
params,
route: {
controller: subject
},
defaultParams: {
fields: {
posts: attributes
}
}
}));
beforeEach(async () => {
const { models } = await getTestApp();
const userModel = models.get('user');
if (!userModel) {
throw new Error('Could not find model "user".');
}
User = userModel;
return Post
.create({
title: '#update() Test'
})
.then(post => post.unwrap())
.then(post => {
record = post;
});
});
afterEach(async () => {
await record.destroy();
});
it('returns a record if attribute(s) change', async () => {
let item = record;
const id = Reflect.get(item, 'id');
expect(item).to.have.property('isPublic', false);
const request = createRequest({
id,
type: 'posts',
data: {
attributes: {
isPublic: true
}
}
});
assertRecord(await subject.update(request));
expect(await Post.find(id)).to.have.property('isPublic', true);
});
it('returns a record if relationships(s) change', async () => {
let item = record;
let user = await Reflect.get(item, 'user');
let comments = await Reflect.get(item, 'comments');
const id = item.getPrimaryKey();
expect(user).to.be.null;
expect(comments).to.deep.equal([]);
const newUser = await User
.create({
name: `${faker.name.firstName()} ${faker.name.lastName()}`,
email: faker.internet.email(),
password: faker.internet.password(8)
})
.then(res => res.unwrap());
const request = createRequest({
id,
type: 'posts',
include: ['user'],
data: {
relationships: {
user: {
data: {
id: newUser.getPrimaryKey(),
type: 'users'
}
},
comments: {
data: [
{
id: 1,
type: 'comments'
},
{
id: 2,
type: 'comments'
},
{
id: 3,
type: 'comments'
}
]
}
}
},
fields: {
users: ['id'],
comments: ['id']
}
});
const result = await subject.update(request);
assertRecord(result, [
...attributes,
'user',
'comments'
]);
// $FlowIgnore
item = await item.reload().include('user', 'comments');
({ rawColumnData: { user, comments } } = item);
expect(user).to.have.property('id', newUser.getPrimaryKey());
expect(comments)
.to.be.an('array')
.with.lengthOf(3);
});
it('returns the number `204` if no changes occur', async () => {
const request = createRequest({
id: record.getPrimaryKey(),
type: 'posts',
data: {
attributes: {
title: '#update() Test'
}
}
});
expect(await subject.update(request)).to.equal(204);
});
it('throws an error if the record is not found', async () => {
const request = createRequest({
id: 10000,
type: 'posts',
data: {
attributes: {
isPublic: true
}
}
});
await subject.update(request).catch(err => {
expect(err).to.be.an.instanceof(Error);
});
});
it('supports sparse field sets', async () => {
const request = createRequest({
id: record.getPrimaryKey(),
type: 'posts',
data: {
attributes: {
title: 'Sparse Field Sets Work With #update()!'
}
},
fields: {
posts: [
'id',
'title'
]
}
});
expect(record).to.have.deep.property(
'rawColumnData.title',
'#update() Test'
);
assertRecord(await subject.update(request), [
'id',
'title'
]);
expect(await record.reload()).to.have.deep.property(
'rawColumnData.title',
'Sparse Field Sets Work With #update()!'
);
});
});
describe('#destroy()', () => {
let record: Model;
const createRequest = (params = {}): Request => setType(() => ({
params,
route: {
controller: subject
},
defaultParams: {
fields: {
posts: attributes
}
}
}));
before(async () => {
record = await Post.create({
title: '#destroy() Test'
});
});
it('returns the number `204` if the record is destroyed', async () => {
const id = Reflect.get(record, 'id');
const result = await subject.destroy(createRequest({ id }));
expect(result).to.equal(204);
await Post.find(id).catch(err => {
expect(err).to.be.an.instanceof(Error);
});
});
it('throws an error if the record is not found', async () => {
const request = createRequest({ id: 10000 });
await subject.destroy(request).catch(err => {
expect(err).to.be.an.instanceof(Error);
});
});
});
describe('#preflight()', () => {
it('returns the number `204`', async () => {
const result = await subject.preflight();
expect(result).to.equal(204);
});
});
});
});