Test create project server code (prep for #1095)
* upgrades jest to latest version * tests Project model with mockingoose * extracts createProject controller to own file * tests createProject controller with sinon-mongoose
This commit is contained in:
parent
eb82968a9f
commit
d2cad7438e
8 changed files with 2906 additions and 1044 deletions
3608
package-lock.json
generated
3608
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -60,7 +60,7 @@
|
|||
"@babel/preset-react": "^7.0.0",
|
||||
"babel-core": "^7.0.0-bridge.0",
|
||||
"babel-eslint": "^9.0.0",
|
||||
"babel-jest": "^23.4.2",
|
||||
"babel-jest": "^24.8.0",
|
||||
"babel-loader": "^8.0.0",
|
||||
"babel-plugin-transform-react-remove-prop-types": "^0.2.12",
|
||||
"chunk-manifest-webpack-plugin": "github:catarak/chunk-manifest-webpack-plugin",
|
||||
|
@ -75,7 +75,7 @@
|
|||
"eslint-plugin-react": "^7.12.3",
|
||||
"extract-text-webpack-plugin": "^3.0.2",
|
||||
"file-loader": "^2.0.0",
|
||||
"jest": "^23.6.0",
|
||||
"jest": "^24.8.0",
|
||||
"node-sass": "^4.11.0",
|
||||
"nodemon": "^1.18.9",
|
||||
"postcss-cssnext": "^2.11.0",
|
||||
|
@ -125,12 +125,14 @@
|
|||
"friendly-words": "^1.1.3",
|
||||
"htmlhint": "^0.10.1",
|
||||
"is-url": "^1.2.4",
|
||||
"jest-express": "^1.10.1",
|
||||
"js-beautify": "^1.8.9",
|
||||
"jsdom": "^9.8.3",
|
||||
"jshint": "^2.10.1",
|
||||
"lodash": "^4.17.11",
|
||||
"loop-protect": "github:catarak/loop-protect",
|
||||
"mjml": "^3.3.2",
|
||||
"mockingoose": "^2.13.0",
|
||||
"mongoose": "^4.6.8",
|
||||
"node-uuid": "^1.4.7",
|
||||
"nodemailer": "^2.6.4",
|
||||
|
@ -167,6 +169,8 @@
|
|||
"sass-extract-js": "^0.4.0",
|
||||
"sass-extract-loader": "^1.1.0",
|
||||
"shortid": "^2.2.14",
|
||||
"sinon": "^7.3.2",
|
||||
"sinon-mongoose": "^2.3.0",
|
||||
"slugify": "^1.3.4",
|
||||
"srcdoc-polyfill": "^0.2.0",
|
||||
"url": "^0.11.0",
|
||||
|
|
155
server/controllers/__test__/project.controller.test.js
Normal file
155
server/controllers/__test__/project.controller.test.js
Normal file
|
@ -0,0 +1,155 @@
|
|||
/**
|
||||
* @jest-environment node
|
||||
*/
|
||||
import { Response } from 'jest-express';
|
||||
|
||||
import { createMock } from '../../models/project';
|
||||
import createProject from '../project.controller/createProject';
|
||||
|
||||
jest.mock('../../models/project');
|
||||
|
||||
describe('project.controller', () => {
|
||||
describe('createProject()', () => {
|
||||
let ProjectMock;
|
||||
|
||||
beforeEach(() => {
|
||||
ProjectMock = createMock();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
ProjectMock.restore();
|
||||
});
|
||||
|
||||
it('fails if create fails', (done) => {
|
||||
const error = new Error('An error');
|
||||
|
||||
ProjectMock
|
||||
.expects('create')
|
||||
.rejects(error);
|
||||
|
||||
const request = { user: {} };
|
||||
const response = new Response();
|
||||
|
||||
const promise = createProject(request, response);
|
||||
|
||||
function expectations() {
|
||||
expect(response.json).toHaveBeenCalledWith({ success: false });
|
||||
|
||||
done();
|
||||
}
|
||||
|
||||
promise.then(expectations, expectations).catch(expectations);
|
||||
});
|
||||
|
||||
it('extracts parameters from request body', (done) => {
|
||||
const request = {
|
||||
user: { _id: 'abc123' },
|
||||
body: {
|
||||
name: 'Wriggly worm',
|
||||
files: [{ name: 'file.js', content: 'var hello = true;' }]
|
||||
}
|
||||
};
|
||||
const response = new Response();
|
||||
|
||||
|
||||
ProjectMock
|
||||
.expects('create')
|
||||
.withArgs({
|
||||
user: 'abc123',
|
||||
name: 'Wriggly worm',
|
||||
files: [{ name: 'file.js', content: 'var hello = true;' }]
|
||||
})
|
||||
.resolves();
|
||||
|
||||
const promise = createProject(request, response);
|
||||
|
||||
function expectations() {
|
||||
expect(response.json).toHaveBeenCalled();
|
||||
|
||||
done();
|
||||
}
|
||||
|
||||
promise.then(expectations, expectations).catch(expectations);
|
||||
});
|
||||
|
||||
// TODO: This should be extracted to a new model object
|
||||
// so the controllers just have to call a single
|
||||
// method for this operation
|
||||
it('populates referenced user on project creation', (done) => {
|
||||
const request = { user: { _id: 'abc123' } };
|
||||
const response = new Response();
|
||||
|
||||
const result = {
|
||||
_id: 'abc123',
|
||||
id: 'abc123',
|
||||
name: 'Project name',
|
||||
serveSecure: false,
|
||||
files: []
|
||||
};
|
||||
|
||||
const resultWithUser = {
|
||||
...result,
|
||||
user: {}
|
||||
};
|
||||
|
||||
ProjectMock
|
||||
.expects('create')
|
||||
.withArgs({ user: 'abc123' })
|
||||
.resolves(result);
|
||||
|
||||
ProjectMock
|
||||
.expects('populate')
|
||||
.withArgs(result)
|
||||
.yields(null, resultWithUser)
|
||||
.resolves(resultWithUser);
|
||||
|
||||
const promise = createProject(request, response);
|
||||
|
||||
function expectations() {
|
||||
const doc = response.json.mock.calls[0][0];
|
||||
|
||||
expect(response.json).toHaveBeenCalled();
|
||||
|
||||
expect(JSON.parse(JSON.stringify(doc))).toMatchObject(resultWithUser);
|
||||
|
||||
done();
|
||||
}
|
||||
|
||||
promise.then(expectations, expectations).catch(expectations);
|
||||
});
|
||||
|
||||
it('fails if referenced user population fails', (done) => {
|
||||
const request = { user: { _id: 'abc123' } };
|
||||
const response = new Response();
|
||||
|
||||
const result = {
|
||||
_id: 'abc123',
|
||||
id: 'abc123',
|
||||
name: 'Project name',
|
||||
serveSecure: false,
|
||||
files: []
|
||||
};
|
||||
|
||||
const error = new Error('An error');
|
||||
|
||||
ProjectMock
|
||||
.expects('create')
|
||||
.resolves(result);
|
||||
|
||||
ProjectMock
|
||||
.expects('populate')
|
||||
.yields(error)
|
||||
.resolves(error);
|
||||
|
||||
const promise = createProject(request, response);
|
||||
|
||||
function expectations() {
|
||||
expect(response.json).toHaveBeenCalledWith({ success: false });
|
||||
|
||||
done();
|
||||
}
|
||||
|
||||
promise.then(expectations, expectations).catch(expectations);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -12,31 +12,7 @@ import { resolvePathToFile } from '../utils/filePath';
|
|||
import generateFileSystemSafeName from '../utils/generateFileSystemSafeName';
|
||||
import { deleteObjectsFromS3, getObjectKey } from './aws.controller';
|
||||
|
||||
export function createProject(req, res) {
|
||||
let projectValues = {
|
||||
user: req.user._id
|
||||
};
|
||||
|
||||
projectValues = Object.assign(projectValues, req.body);
|
||||
|
||||
Project.create(projectValues, (err, newProject) => {
|
||||
if (err) {
|
||||
res.json({ success: false });
|
||||
return;
|
||||
}
|
||||
Project.populate(
|
||||
newProject,
|
||||
{ path: 'user', select: 'username' },
|
||||
(innerErr, newProjectWithUser) => {
|
||||
if (innerErr) {
|
||||
res.json({ success: false });
|
||||
return;
|
||||
}
|
||||
res.json(newProjectWithUser);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
export { default as createProject } from './project.controller/createProject';
|
||||
|
||||
export function updateProject(req, res) {
|
||||
Project.findById(req.params.project_id, (findProjectErr, project) => {
|
||||
|
|
32
server/controllers/project.controller/createProject.js
Normal file
32
server/controllers/project.controller/createProject.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
import Project from '../../models/project';
|
||||
|
||||
export default function createProject(req, res) {
|
||||
let projectValues = {
|
||||
user: req.user._id
|
||||
};
|
||||
|
||||
projectValues = Object.assign(projectValues, req.body);
|
||||
|
||||
function sendFailure() {
|
||||
res.json({ success: false });
|
||||
}
|
||||
|
||||
function populateUserData(newProject) {
|
||||
return Project.populate(
|
||||
newProject,
|
||||
{ path: 'user', select: 'username' },
|
||||
(err, newProjectWithUser) => {
|
||||
if (err) {
|
||||
sendFailure();
|
||||
return;
|
||||
}
|
||||
res.json(newProjectWithUser);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
return Project.create(projectValues)
|
||||
.then(populateUserData)
|
||||
.catch(sendFailure);
|
||||
}
|
17
server/models/__mocks__/project.js
Normal file
17
server/models/__mocks__/project.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
import sinon from 'sinon';
|
||||
import 'sinon-mongoose';
|
||||
|
||||
// Import the actual model to be mocked
|
||||
const Project = jest.requireActual('../project').default;
|
||||
|
||||
// Wrap Project in a sinon mock
|
||||
// The returned object is used to configure
|
||||
// the mocked model's behaviour
|
||||
export function createMock() {
|
||||
return sinon.mock(Project);
|
||||
}
|
||||
|
||||
// Re-export the model, it will be
|
||||
// altered by mockingoose whenever
|
||||
// we call methods on the MockConfig
|
||||
export default Project;
|
101
server/models/__test__/project.test.js
Normal file
101
server/models/__test__/project.test.js
Normal file
|
@ -0,0 +1,101 @@
|
|||
import mockingoose from 'mockingoose';
|
||||
import differenceInSeconds from 'date-fns/difference_in_seconds';
|
||||
|
||||
import Project from '../project';
|
||||
|
||||
const datesWithinSeconds = (first, second) => differenceInSeconds(first, second) < 2;
|
||||
|
||||
describe('models/project', () => {
|
||||
beforeEach(() => {
|
||||
mockingoose.resetAll();
|
||||
});
|
||||
|
||||
describe('projectSchema', () => {
|
||||
it('sets default project properties', (done) => {
|
||||
const data = {};
|
||||
|
||||
mockingoose(Project).toReturn(data, 'create');
|
||||
|
||||
Project.create(data, (err, newProject) => {
|
||||
expect(err).toBeNull();
|
||||
expect(newProject).toBeDefined();
|
||||
expect(newProject.name).toBe("Hello p5.js, it's the server");
|
||||
expect(newProject.serveSecure).toBe(false);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('creates a slug from the project name', (done) => {
|
||||
const data = { name: 'My project' };
|
||||
|
||||
mockingoose(Project).toReturn(data, 'create');
|
||||
|
||||
Project.create(data, (err, newProject) => {
|
||||
expect(newProject.slug).toBe('My_project');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('exposes _id as id', (done) => {
|
||||
const data = { name: 'My project' };
|
||||
|
||||
mockingoose(Project).toReturn(data, 'create');
|
||||
|
||||
Project.create(data, (err, newProject) => {
|
||||
expect(newProject.id).toBe(newProject._id);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('generates timestamps', (done) => {
|
||||
const data = { name: 'My project' };
|
||||
const now = new Date();
|
||||
|
||||
mockingoose(Project).toReturn(data, 'create');
|
||||
|
||||
Project.create(data, (err, newProject) => {
|
||||
// Dates should be near to now, by a few ms
|
||||
expect(newProject.createdAt).toBeInstanceOf(Date);
|
||||
|
||||
expect(datesWithinSeconds(newProject.createdAt, now)).toBe(true);
|
||||
|
||||
expect(newProject.updatedAt).toBeInstanceOf(Date);
|
||||
expect(datesWithinSeconds(newProject.updatedAt, now)).toBe(true);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('serializes to JSON', (done) => {
|
||||
const data = { name: 'My project' };
|
||||
|
||||
mockingoose(Project).toReturn(data, 'create');
|
||||
|
||||
Project.create(data, (err, newProject) => {
|
||||
const now = new Date();
|
||||
const object = JSON.parse(JSON.stringify(newProject));
|
||||
|
||||
expect(object).toMatchObject({
|
||||
_id: newProject._id,
|
||||
name: 'My project',
|
||||
id: newProject._id,
|
||||
slug: 'My_project',
|
||||
files: [],
|
||||
serveSecure: false
|
||||
});
|
||||
|
||||
// Check that the timestamps deserialise
|
||||
const createdAt = new Date(object.createdAt);
|
||||
const updatedAt = new Date(object.updatedAt);
|
||||
|
||||
expect(datesWithinSeconds(createdAt, now)).toBe(true);
|
||||
expect(datesWithinSeconds(updatedAt, now)).toBe(true);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('fileSchema', () => {
|
||||
});
|
||||
});
|
|
@ -2,6 +2,9 @@ import mongoose from 'mongoose';
|
|||
import shortid from 'shortid';
|
||||
import slugify from 'slugify';
|
||||
|
||||
// Register User model as it's referenced by Project
|
||||
import './user';
|
||||
|
||||
const { Schema } = mongoose;
|
||||
|
||||
const fileSchema = new Schema(
|
||||
|
|
Loading…
Reference in a new issue