I have a plain node express webserver that send MongoDB Atlas a post request. I use mongooose.
Before do this there is some express-validators work.
Two API key used in this project. Thats enabled and work. Of course not share here...
I test with Postman.
This code from Node and MongoDB expert.
If i change MongoDB cluster not help.
I got two error:
Validation error: Invalid inputs passed, please check your data 422
If I comment out validation got different error:
Other error on post route: Creating place failed, please try again 500
app.js
const express = require('express');
const bodyParser = require('body-parser');
const mongoose = require('mongoose');
const placesRoutes = require('./routes/places-routes');
const usersRoutes = require('./routes/users-routes');
const HttpError = require('./models/http-error');
const app = express();
app.use(bodyParser.json());
app.use('/api/places', placesRoutes); // => /api/places...
app.use('/api/users', usersRoutes);
app.use((req, res, next) => {
const error = new HttpError('Could not find this route.', 404);
throw error;
});
app.use((error, req, res, next) => {
if (res.headerSent) {
return next(error);
}
res.status(error.code || 500);
res.json({ message: error.message || 'An unknown error occurred!' });
});
mongoose
.connect('xxxx')
.then(() => {
app.listen(5000);
})
.catch(err => {
console.log(err);
});
places-routes.js
const express = require('express');
const { check } = require('express-validator');
const placesControllers = require('../controllers/places-controllers');
const router = express.Router();
router.get('/:pid', placesControllers.getPlaceById);
router.get('/user/:uid', placesControllers.getPlacesByUserId);
router.post(
'/',
[
// check('title')
// .not()
// .isEmpty(),
// check('description').isLength({ min: 5 }),
// check('address')
// .not()
// .isEmpty()
],
placesControllers.createPlace
);
router.patch(
'/:pid',
[
check('title')
.not()
.isEmpty(),
check('description').isLength({ min: 5 })
],
placesControllers.updatePlace
);
router.delete('/:pid', placesControllers.deletePlace);
module.exports = router;
places-controllers.js
const uuid = require('uuid/v4');
const { validationResult } = require('express-validator');
const HttpError = require('../models/http-error');
const getCoordsForAddress = require('../util/location');
const Place = require('../models/place');
let DUMMY_PLACES = [
{
id: 'p1',
title: 'Empire State Building',
description: 'One of the most famous sky scrapers in the world!',
location: {
lat: 40.7484474,
lng: -73.9871516
},
address: '20 W 34th St, New York, NY 10001',
creator: 'u1'
}
];
const getPlaceById = (req, res, next) => {
const placeId = req.params.pid; // { pid: 'p1' }
const place = DUMMY_PLACES.find(p => {
return p.id === placeId;
});
if (!place) {
throw new HttpError('Could not find a place for the provided id.', 404);
}
res.json({ place }); // => { place } => { place: place }
};
// function getPlaceById() { ... }
// const getPlaceById = function() { ... }
const getPlacesByUserId = (req, res, next) => {
const userId = req.params.uid;
const places = DUMMY_PLACES.filter(p => {
return p.creator === userId;
});
if (!places || places.length === 0) {
return next(
new HttpError('Could not find places for the provided user id.', 404)
);
}
res.json({ places });
};
const createPlace = async (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return next(
new HttpError('Invalid inputs passed, please check your data.', 422)
);
}
const { title, description, address, creator } = req.body;
let coordinates;
try {
coordinates = await getCoordsForAddress(address);
} catch (error) {
return next(error);
}
// const title = req.body.title;
const createdPlace = new Place({
title,
description,
address,
location: coordinates,
image: 'https://upload.wikimedia.org/wikipedia/commons/thumb/1/10/Empire_State_Building_%28aerial_view%29.jpg/400px-Empire_State_Building_%28aerial_view%29.jpg',
creator
});
try {
await createdPlace.save();
} catch (err) {
const error = new HttpError(
'Creating place failed, please try again.',
500
);
return next(error);
}
res.status(201).json({ place: createdPlace });
};
const updatePlace = (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
throw new HttpError('Invalid inputs passed, please check your data.', 422);
}
const { title, description } = req.body;
const placeId = req.params.pid;
const updatedPlace = { ...DUMMY_PLACES.find(p => p.id === placeId) };
const placeIndex = DUMMY_PLACES.findIndex(p => p.id === placeId);
updatedPlace.title = title;
updatedPlace.description = description;
DUMMY_PLACES[placeIndex] = updatedPlace;
res.status(200).json({ place: updatedPlace });
};
const deletePlace = (req, res, next) => {
const placeId = req.params.pid;
if (!DUMMY_PLACES.find(p => p.id === placeId)) {
throw new HttpError('Could not find a place for that id.', 404);
}
DUMMY_PLACES = DUMMY_PLACES.filter(p => p.id !== placeId);
res.status(200).json({ message: 'Deleted place.' });
};
exports.getPlaceById = getPlaceById;
exports.getPlacesByUserId = getPlacesByUserId;
exports.createPlace = createPlace;
exports.updatePlace = updatePlace;
exports.deletePlace = deletePlace;
package.json
{
"name": "backend",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "nodemon app.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"axios": "^0.19.0",
"body-parser": "^1.19.0",
"express": "^4.17.1",
"express-validator": "^6.2.0",
"mongoose": "^5.7.8",
"uuid": "^3.3.3"
},
"devDependencies": {
"nodemon": "^1.19.4"
}
}
Firstly use this middleware in app.js, and remove body-parser.
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
It parses your request and you get all values...
Only a plus ":" sign by title
wrong
{
"title:": "Empire State Building",
"description": "A very famous building in NY",
"address": "20 W 34th St, New York, NY 10001",
"creator": "u2"
}
and the good:
{
"title": "Empire State Building",
"description": "A very famous building in NY",
"address": "20 W 34th St, New York, NY 10001",
"creator": "u2"
}
Related
I'm trying to deploy a node express backend on Vercel, but I'm always getting 404 or 500.
I have a folder on the root of the project named "handlers", inside of it, there're multiple folders with the routes and um index.js file that gets all promises and send to the index.js file on the root of the project.
I'm not so sure how the vercel.config should be.
handler/index.js
folders structure
[handler/*.js example]
const { exec } = require("child_process");
const { promisify } = require("util");
/**
* #param {*} event
* #param {*} models
* #returns
*/
module.exports.handler = async function (event, models) {
const execPromise = promisify(exec);
const pythonReturn = await execPromise("python3 app.py");
const result = JSON.parse(pythonReturn.stdout)
return {
statusCode: 200,
body: JSON.stringify(result),
};
};
module.exports.path = "/getSurebets";
module.exports.method = "GET";
[server.js]
'use strict'
const express = require('express')
const bodyParser = require('body-parser')
const cors = require('cors')
const app = express()
const router = express.Router()
const aliveTime = new Date().getTime()
const PORT = process.env.PORT || 3002;
const Handler = require("./index")
router.use(cors())
router.use(bodyParser.json())
router.use(bodyParser.urlencoded({ extended: true }))
router.get('/', (req, res) => {
req.json({ aliveTime })
})
router.use(async function(req, res){
const event = {
"resource": req.path,
"path": req.path,
"httpMethod": req.method,
"headers": req.headers,
"queryStringParameters": req.query,
"pathParameters": req.params,
"stageVariables": null,
"body": JSON.stringify(req.body),
"isBase64Encoded": false,
"requestContext": {
"authorizer": {}
}
}
try{
const response = await Handler.handler(event)
res
.set(response.headers)
.status(response.statusCode)
const data = response.body
if(req.path.indexOf("render") < 0){
res.send(JSON.parse(data))
}else{
res.send(data)
}
}catch(e){
console.error(e)
res.status(500).send(e)
}
})
app.use('/v1/', router)
app.listen(PORT)
console.log(`Listen on http://0.0.0.0:${PORT}`)
module.exports = app
root/index.js
const fns = require("./handlers")
const cors = require('./lib/cors')
const models = require("./models");
const extractPathValues = (pathExpression, httpPath) => {
const pathExpressionPattern = pathExpression.replace(/{[\w]+}|:[\w]+/g, '([^/]+)')
const pathValueRegex = new RegExp(`^${pathExpressionPattern}$`)
const pathValues = pathValueRegex.exec(httpPath)
return pathValues && pathValues.length > 0 ? pathValues.slice(1) : null
}
const extractPathNames = (pathExpression) => {
const pathExpressionPattern = pathExpression?.replace(/{[\w.]+}|:[\w.]+/g, '[:{]([\\w]+)}?');
const pathNameRegex = new RegExp(`^${pathExpressionPattern}$`)
const pathNames = pathNameRegex.exec(pathExpression)
return pathNames && pathNames.length > 0 ? pathNames.slice(1) : null
}
const getRouterHandler = (event) => {
const routesByMethod = fns.filter(r => r.method === event.httpMethod)
for(const route of routesByMethod) {
const pathPartNames = extractPathNames(route.path)
const pathValues = extractPathValues(route.path, event.path)
if (pathValues && pathPartNames) {
const params = {}
let keyIndex = 0;
for(const key of pathPartNames) {
params[ key ] = pathValues[ keyIndex ]
keyIndex++;
}
return {route, params}
}
}
}
const getHandler = async (event) => {
return getRouterHandler(event)
}
exports.handler = async (event, context) => {
if(event.Records) return tRender(event, context, models)
if(process.env.NODE_ENV!=='test') console.log(event)
const HANDLER = await getHandler(event)
if(!HANDLER) return { statusCode: 404, body: JSON.stringify({ message: 'Not Found' }) }
const { route, params } = HANDLER
if(!event.pathParameters || !Object.keys(event.pathParameters).length) {
event.pathParameters = params
}
if((process.env.NODE_ENV || '').trim() !== 'test') console.debug({ event })
const response = cors(
await route.handler(event, models, context).catch(err => {
console.error(err)
if(!err.statusCode || !err.body ) {
return Promise.reject(err)
}
return Promise.resolve(err)
})
)
return response
}
[vercel.config]
{
"version": 2,
"name": "omnes-api",
"builds": [
{ "src": "index.js", "use": "#vercel/node" }
],
"routes": [
{
"src": "handlers/index.js",
"dest": "index.js"
}
]
}
Error:
I already tried some possible solutions and even created and wrote the code again but I am still getting errors. I have created a diminute version of my whole code which connects to the database using Mongoose but after the Schema is created and I import the model in places-controllers my data that I write in POSTMAN goes directly to:
FYI: In this case I want POST request from createPlace to properly work.
Data entry: URL: http://localhost:5000/api/places/
{
"title": "Punta Arena Stfdsfdsfsdfop",
"description": "One stop Stop. Does not have tr12affic lights.",
"busrespect": "12ysdfdsfsfes",
"address": "Avenida Solunna",
"creator": "52peru soiflsdjf36"
}
OUTPUT:
{
"status": "error caught"
}
which is what I told the program to define if the try did not work.
IN app.js I have the following code:
const express= require('express');
const mongoose = require('mongoose');
const bodyParser = require('body-parser');
const placesRoutes = require("./routes/places-routes");
const HttpError = require ("./models/http-error");
const app = express();
app.use(bodyParser.json());
app.use('/api/places', placesRoutes);
app.use((req, res, next) => {
const error= new HttpError('Route not available. Try something different?', 404);
throw error;
});
app.use((error, req, res, next) =>{
if (res.headerSent) {
return next(error);
}
res.status(error.code || 500)
res.json({message: error.message || "An unknown error occured! Sorry" });
});
url = '<mongo_url>'
mongoose.connect(url, {useNewUrlParser: true}).then(()=>{
console.log("Connected to database")
app.listen(5000);
}).catch(erro => {
console.log(erro)
});
In places-routes.js I have the following code:
const express = require('express');
const {check} = require('express-validator')
const placesControllers=require('../controllers/places-controllers');
const router = express.Router();
router.get('/:pid', placesControllers.getPlaceById );
router.get('/user/:uid',placesControllers.getPlacesByCreatorId );
router.post('/' ,[
check('title')
.not()
.isEmpty(),
check('description').isLength({ min: 5 }),
check('address')
.not()
.isEmpty()
],
placesControllers.createPlace);
router.patch('/:pid', [
check('title')
.not()
.isEmpty(),
check('description').isLength({ min: 5 })
] , placesControllers.updatePlace );
router.delete('/:pid', placesControllers.deletePlace);
module.exports=router;
In places-controllers.js I have the following code:
const HttpError = require('../models/http-error');
const { validationResult } = require('express-validator');
//const getCoordsForAddress= require('../util/location');
const BusStop = require('../models/place');
let INITIAL_DATA = [
{
id: "p1",
title: "Samoa Stop",
description: "My first bus stop in Lima",
//location: {
// lat: 40.1382,
// lng:-23.23
// },
address: "Av. La Molina interseccion con calle Samoa",
busrespect: "yes",
creator: "u1"
}
];
const getPlaceById = (req, res, next) => {
const placeId = req.params.pid // Accessing the p1 in pid URL scrapping {pid:'p1'}
const place= INITIAL_DATA.find(p => { //find method goes over each element in the array, the argument p represents the element where find loop is
return p.id ===placeId
});
if (!place) {
const error= new HttpError('No bus stop found for the provided ID.', 404);
throw error;
}
res.json({place: place});
};
const getPlacesByCreatorId = (req, res, next)=> {
const userId = req.params.uid;
const places = INITIAL_DATA.filter(p=>{ //filter to retrieve multiple places, not only the first one
return p.creator ===userId;
});
if (!places || places.length===0) {
return next(
new HttpError('Could not find bus stops for the provide user id', 404)
);
}
res.json({places});
};
const createPlace = async (req, res,next) => {
const errors = validationResult(req);
if (!errors.isEmpty()){
return next(new HttpError ('Invalid bus stop please check your data', 422));
}
//const { title, description, busrespect, address, creator } = req.body; //erased location for now.
/* let place = new BusStop({
title: req.body.title,
description: req.body.description,
busrespect: req.body.busrespect,
address : req.body.address,
creator: req.body.creator
})
awaitplace.save()
.then(response=>{
res.json({
message : "Employee added sucessfully!"
})
})
.catch(err=>{
res.json({
message : "An error has occured!"
})
})
} */
const { title, description, busrespect, address, creator } = req.body;
try {
await BusStop.create({
title:title,
description: description,
busrespect:busrespect,
address: address,
creator: creator
});
res.send({status: "ok"});
} catch(error) {
res.send({status:"error caught"});
}
};
const updatePlace = (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()){
console.log(errors);
throw new HttpError ("Invalid inputs passed, please check your data ", 422);
};
const { title, description } = req.body;
const placeId = req.params.pid;
const updatedPlace = { ...INITIAL_DATA.find(p => p.id === placeId)};
const placeIndex = INITIAL_DATA.findIndex(p => p.id === placeId);
updatedPlace.title = title;
updatedPlace.description = description;
INITIAL_DATA[placeIndex] = updatedPlace;
res.status(200).json({place: updatedPlace});
};
const deletePlace = (req, res, next) => {
const placeId = req.params.pid;
if (!INITIAL_DATA.find(p=> p.id ===placesId))
throw new HttpError('Could not find a bus stop for that ID ')
INITIAL_DATA = INITIAL_DATA.filter(p=> p.id !== placeId)
res.status(200).json({message: 'Deleted Place'});
};
exports.getPlaceById= getPlaceById;
exports.getPlacesByCreatorId = getPlacesByCreatorId;
exports.createPlace = createPlace;
exports.updatePlace = updatePlace;
exports.deletePlace = deletePlace;
Inside models folder I have two files: http-error.js which has this code:
class HttpError extends Error {
constructor(message, errorCode) {
super (message);
this.code = errorCode;
}
}
module.exports = HttpError;
The other file inside is the schema which is place.js
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const placeSchema = new Schema({
title: {
type: String
},
description: {
type: String
},
address: {
type: String
},
busrespect: {
type: String
},
creator: {
type: String
}
},
)
const BusStop = mongoose.model('BusStop', placeSchema)
module.exports= BusStop
Summary: somewhere in the try catch part from createPlace something is going wrong since my data entry is always going to the error status I indicated in that part.
I am trying to send data from front end to back end with react and nodejs. but when I console in front end it appear data but in backend it didn't get anything.
Here is my front end code:
const [imageSelected, setImageSelected] = useState("");
// Form submission
const handleSubmit = async (event) => {
event.preventDefault();
const formData = new FormData();
formData.append("file", imageSelected);
const payload = { title, mission, isSearchable, userId: currentUser.id };
formData.append("payload", payload);
// Send post request for signup
const res = await axios.post("/api/v1/teams/team", formData, {
headers: { "Content-type": "multipart/form-data" },
});
// If no validation errors were found
if (res.data.validationErrors === undefined) {
// Clear any errors
setErrorsArr([]);
// Hide the errors component
setShowErrors(false);
// Toggle the modal
toggleModal();
// Go to team management page
NextRouter.push(`/team/${res.data}`);
} else {
// Set errors
setErrorsArr(res.data.validationErrors.errors);
// Show the errors component
setShowErrors(true);
}
};
<input
type="file"
className="team--modal_upload_avatar"
ref={inputFile}
onChange={(e) => setImageSelected(e.target.files[0])}
/>
<Grid item xs={12} className={classes.avatarDiv}>
<Avatar
aria-label="team"
className={classes.avatar}
component="div"
onClick={onButtonClick}
>
<AddAPhotoIcon className={classes.avatarIcon} />
</Avatar>
Here is my route:
const express = require("express");
const router = express.Router();
const { catchErrors } = require("../errors/errorHandlers");
const { body, param } = require("express-validator");
const cloudinary = require("cloudinary").v2;
const path = require("path");
const Datauri = require("datauri/parser");
const cloud_name = process.env.CLOUDINARY_NAME;
const cloud_api_key = process.env.CLOUDINARY_API_KEY;
const cloud_api_secret = process.env.CLOUDINARY_API_SECRET;
const cloud_url = process.env.CLOUDINARY_URL;
cloudinary.config({
cloud_name: cloud_name,
api_key: cloud_api_key,
api_secret: cloud_api_secret,
cloudinary_url: cloud_url,
});
// Controller
const {
createTeam,
getUsersTeams,
getManagedTeams,
getTeamCredits,
getTeamData,
updateTeamData,
shutdownTeam,
checkTeamPermissions,
checkTeamPermissionsAndLimits,
addMember,
removeMember,
addMemberBackToTeam,
quitTeam,
} = require("./teamsController");
const {
checkUserVerification,
restrictedRoute,
checkData,
} = require("../helpers/apiHelpers");
router.post(
"/team",
(req, res) => {
console.log(res);
console.log("req body", req.body);
console.log("req files", req.files);
console.log("req user", req.user);
const dUri = new Datauri();
const dataUri = (req) =>
dUri.format(path.extname(req.name).toString(), req.data);
if (req.files !== undefined && req.files !== null) {
const { file, id } = req.files;
const newFile = dataUri(file).content;
cloudinary.uploader
.upload(newFile, {
folder: "TeamAvatar",
})
.then((result) => {
const imageUrl = result.url;
const data = { id: req.body.id, imageUrl };
createTeam(data);
return res
.status(200)
.json({ message: "Success", data: { imageUrl } });
})
.catch((err) =>
res.status(400).json({ message: "Error", data: { err } })
);
} else {
return res.status(400).json({ message: "Error" });
}
},
restrictedRoute,
[
body(
"title",
"Only alphabetical characters, numbers, and spaces are allowed."
)
.not()
.isEmpty()
.isLength({ min: 1, max: 25 })
.trim()
.matches(/^[a-zA-Z0-9 ]+$/)
.blacklist("\\<\\>\\;\\[\\]\\{\\}\\|\\%\\=\\(\\)\\~\\#")
.escape(),
body("mission", "Only alphabetical characters and spaces are allowed.")
.not()
.isEmpty()
.trim()
.blacklist("\\<\\>\\;\\[\\]\\{\\}\\|\\%\\=\\(\\)\\~\\#")
.escape(),
],
checkData,
catchErrors(checkUserVerification),
catchErrors(createTeam)
);
Here is my controller that have create team function:
exports.createTeam = async (req, res) => {
// Get the user id from the session
const userId = req.session.passport.user.id;
console.log("body", req.body);
console.log("files", req.files);
console.log("file", req.file);
// Make sure user has the credits to create a new team
const teamInfo = await models.User.findOne({
where: {
id: userId,
},
attributes: ["teamCredits"],
});
if (teamInfo.dataValues.teamCredits <= 0) {
res.status(200).json({
validationErrors: {
errors: [
{
msg: "You don't have any more team credits.",
},
],
},
});
return;
}
const { title, mission } = req.body;
// const { picture } = req.imageUrl;
// Make sure the user hasn't already created a team with that title.
const existingTeam = await models.Team.findOne({
where: {
title: title,
creatorId: userId,
},
});
if (existingTeam !== null) {
// Response and let the user know.
res.status(200).json({
validationErrors: {
errors: [
{
msg: "You already created a team with that name.",
},
],
},
});
return;
}
// Generator a public team id
const firstLetter = title[0];
const secondLetter = title[1];
const thirdLetter = title[2];
const timePart = Date.now();
const generatedPublicId = `${firstLetter}${secondLetter}${thirdLetter}${timePart}`;
const roomEntry = {
name: title,
status: true,
};
const roomResponse = await models.Room.create({ ...roomEntry });
const defaultTeamValues = {
title: title,
type: "simple team",
mission: mission,
// picture: picture,
agreement: "default",
inputs: "",
outputs: "",
duration_in_months: 12,
status: "Seeking new members",
public_team_id: generatedPublicId,
mergedTo: null,
creatorId: userId,
date_closed: null,
current_members_count: 1,
current_invites_count: 0,
max_team_members_allowed: 10,
max_invites_allowed: 20,
roomID: roomResponse.dataValues.id,
};
// No existing team was found with that title and created by that user.
// Create team.
const team = await models.Team.create(defaultTeamValues);
const defaultRoleValues = {
title: "creator",
duties: "",
rights: "all",
};
// Create role for new team
const role = await models.Role.create(defaultRoleValues);
const defaultMembershipValues = {
interests: "",
contributions: "",
authorization: "creator",
status: "active",
application_letter: "",
date_applied: Sequelize.literal("CURRENT_TIMESTAMP"),
date_joined: Sequelize.literal("CURRENT_TIMESTAMP"),
date_departed: null,
memberId: userId,
teamId: team.dataValues.id,
roleId: role.dataValues.id,
};
// Create membership for team with role and team ids
await models.Membership.create(defaultMembershipValues);
const newCreditValue = teamInfo.dataValues.teamCredits - 1;
// Update team credits the user has.
await models.User.update(
{ teamCredits: newCreditValue },
{
where: {
id: userId,
},
}
);
// Done
res.status(200).json(team.dataValues.public_team_id);
};
Why my back end didn't get any data I sent from front end to back end?
Add files
server.js:
/* eslint-disable no-undef */
const express = require("express");
const next = require("next");
const dotenv = require("dotenv");
const dev = process.env.NODE_ENV !== "production";
const app = next({ dev });
const handle = app.getRequestHandler();
const compression = require("compression");
const bodyParser = require("body-parser");
const logger = require("morgan");
const session = require("express-session");
const SequelizeStore = require("connect-session-sequelize")(session.Store);
const passport = require("passport");
const helmet = require("helmet");
const sslRedirect = require("heroku-ssl-redirect");
const socketIo = require("socket.io");
const http = require("http");
const {
saveMessage,
getAllMessagedByRoomID,
} = require("./services/chat/chatController");
// Setup Next.js then run express.
app.prepare().then(() => {
// Setup express
const server = express();
const chatServer = http.createServer(server);
const io = socketIo(chatServer);
// Socket Connection Start
io.on("connection", (socket) => {
chatID = socket.handshake.query.chatID;
socket.join(chatID);
//Send message to only a particular user
socket.on("send_message", (message) => {
saveMessage(message);
io.in(message.roomID).emit("receive_message", {
content: message.content,
roomID: message.roomID,
userID: message.userID,
});
});
socket.on("get_all_messages", async ({ roomID }) => {
const allMessagedByRoomID = await getAllMessagedByRoomID(roomID);
io.in(roomID).emit("send_all_messages", {
allMessagedByRoomID,
});
});
});
// Redirect all traffic to use ssl(https);
server.use(sslRedirect());
// Define PORT
const port = process.env.PORT || 3000;
let serverMode = "development";
// Check if node is setup for production
if (!dev) {
serverMode = "production";
}
if (serverMode === "production") {
server.use(helmet());
}
// Logger
server.use(
logger("dev", {
skip: function (req, res) {
return res.statusCode < 199; // Only log 400 and 500 codes
},
})
);
// Use body parser
server.use(bodyParser.urlencoded({ extended: false }));
server.use(bodyParser.json());
// Compression
server.use(compression());
// Database
const db = require("./models/index");
const sequelize = db.sequelize;
// Test db connection
sequelize
.authenticate()
.then(() => {
console.log("Database successfully connected!");
})
.catch((err) => {
throw new Error(err);
});
// Sessions Setup
const sessionMaxTime = 1000 * 60 * 60 * 24 * 5; // 5 Days
// Session options
const theSession = {
secret: process.env.SECRET,
name: "sessId",
resave: false,
saveUninitialized: false,
cookie: {
maxAge: sessionMaxTime,
sameSite: true,
},
store: new SequelizeStore({
db: sequelize,
table: "Session",
}),
};
// Session production options
if (serverMode === "production") {
server.set("trust proxy", 1); // Trust first proxy
theSession.cookie.secure = true; // Serve cookies on HTTPS only
}
server.use(session(theSession));
// Passport Setup
// require("./config/passport")(passport);
require("./config/passport");
server.use(passport.initialize());
server.use(passport.session());
// API Routes
const userRoutes = require("./services/users/usersAPI");
server.use("/api/v1/users", userRoutes);
const profileRoutes = require("./services/profiles/profilesAPI");
server.use("/api/v1/profiles", profileRoutes);
const teamRoutes = require("./services/teams/teamsAPI");
server.use("/api/v1/teams", teamRoutes);
const searchRoutes = require("./services/searches/searchAPI");
server.use("/api/v1/search", searchRoutes);
const ratingRoutes = require("./services/ratings/ratingsAPI");
server.use("/api/v1/ratings", ratingRoutes);
const inviteRoutes = require("./services/invites/invitesAPI");
server.use("/api/v1/invites", inviteRoutes);
const feedbackRoutes = require("./services/feedback/feedbackAPI");
server.use("/api/v1/feedback", feedbackRoutes);
const couponRoutes = require("./services/coupons/couponsAPI");
server.use("/api/v1/coupons", couponRoutes);
const chatRoutes = require("./services/chat/chatAPI");
server.use("/api/v1/chat", chatRoutes);
// Restricted Pages
const restrictedRoutes = require("./services/restricted/restrictedAPI");
server.use(restrictedRoutes);
// Run server
sequelize.sync({ force: true }).then(() => {
server.listen(port, (err) => {
if (err) throw err;
console.log(`> Ready in ${serverMode} mode.`);
});
});
});
To handle HTTP POST requests in Express.js version 4 and above, you need to install the middleware module called body-parser.
body-parser extracts the entire body portion of an incoming request stream and exposes it on req.body.
const express = require('express')
const app = express()
const bodyParser = require('body-parser');
// support parsing of application/json type post data
app.use(bodyParser.json());
//support parsing of application/x-www-form-urlencoded post data
app.use(bodyParser.urlencoded({ extended: true }));
router.post("/team", (req, res) => {...})
In Addition:
To handle multipart/form-data request that support file upload, you
need to use multer multer module.
Basic usage example:
Don't forget the enctype="multipart/form-data" in your form.
<form action="/profile" method="post" enctype="multipart/form-data">
<input type="file" name="avatar" />
</form>
var express = require('express')
var multer = require('multer')
var upload = multer({ dest: 'uploads/' })
var app = express()
app.post('/profile', upload.single('avatar'), function (req, res, next) {
// req.file is the `avatar` file
// req.body will hold the text fields, if there were any
})
app.post('/photos/upload', upload.array('photos', 12), function (req, res, next) {
// req.files is array of `photos` files
// req.body will contain the text fields, if there were any
})
var cpUpload = upload.fields([{ name: 'avatar', maxCount: 1 }, { name: 'gallery', maxCount: 8 }])
app.post('/cool-profile', cpUpload, function (req, res, next) {
// req.files is an object (String -> Array) where fieldname is the key, and the value is array of files
//
// e.g.
// req.files['avatar'][0] -> File
// req.files['gallery'] -> Array
//
// req.body will contain the text fields, if there were any
})
In your case: route.js
const express = require("express");
const app = express();
const multer = require('multer');
const upload = multer({ dest: 'uploads/' });
const router = express.Router();
const { catchErrors } = require("../errors/errorHandlers");
const { body, param } = require("express-validator");
const cloudinary = require("cloudinary").v2;
const path = require("path");
const Datauri = require("datauri/parser");
const cloud_name = process.env.CLOUDINARY_NAME;
const cloud_api_key = process.env.CLOUDINARY_API_KEY;
const cloud_api_secret = process.env.CLOUDINARY_API_SECRET;
const cloud_url = process.env.CLOUDINARY_URL;
cloudinary.config({
cloud_name: cloud_name,
api_key: cloud_api_key,
api_secret: cloud_api_secret,
cloudinary_url: cloud_url,
});
// Controller
const {
createTeam,
getUsersTeams,
getManagedTeams,
getTeamCredits,
getTeamData,
updateTeamData,
shutdownTeam,
checkTeamPermissions,
checkTeamPermissionsAndLimits,
addMember,
removeMember,
addMemberBackToTeam,
quitTeam,
} = require("./teamsController");
const {
checkUserVerification,
restrictedRoute,
checkData,
} = require("../helpers/apiHelpers");
router.post(
"/team", upload.single('file'),
(req, res) => {
console.log(res);
// req.file is the `file` file
// req.body will hold the text fields, if there were any
console.log("req body", req.body);
console.log("req file", req.file);
console.log("req user", req.user);
const dUri = new Datauri();
const dataUri = (req) =>
dUri.format(path.extname(req.name).toString(), req.data);
if (req.files !== undefined && req.files !== null) {
const { file, id } = req.files;
const newFile = dataUri(file).content;
cloudinary.uploader
.upload(newFile, {
folder: "TeamAvatar",
})
.then((result) => {
const imageUrl = result.url;
const data = { id: req.body.id, imageUrl };
createTeam(data);
return res
.status(200)
.json({ message: "Success", data: { imageUrl } });
})
.catch((err) =>
res.status(400).json({ message: "Error", data: { err } })
);
} else {
return res.status(400).json({ message: "Error" });
}
},
restrictedRoute,
[
body(
"title",
"Only alphabetical characters, numbers, and spaces are allowed."
)
.not()
.isEmpty()
.isLength({ min: 1, max: 25 })
.trim()
.matches(/^[a-zA-Z0-9 ]+$/)
.blacklist("\\<\\>\\;\\[\\]\\{\\}\\|\\%\\=\\(\\)\\~\\#")
.escape(),
body("mission", "Only alphabetical characters and spaces are allowed.")
.not()
.isEmpty()
.trim()
.blacklist("\\<\\>\\;\\[\\]\\{\\}\\|\\%\\=\\(\\)\\~\\#")
.escape(),
],
checkData,
catchErrors(checkUserVerification),
catchErrors(createTeam)
);
When trying to make a PUT request to update a house for my MERN application. The route does not seem to work on the server-side, in postman it shows a 404 not found error. I have speculation that it may be the way my routes are set up because it does not even recognize the that I am trying to make a PUT request. All of my other routes work but for some reason I am getting the index.html Error in postman. The PUT request is at the bottom of the HouseList Route code.
HouseList Route
// const express = require('express')
// const router = express.Router()
const router = require('express').Router();
const {House} = require('../../Models/House');
const Formidable = require('formidable');
const cloudinary = require('cloudinary').v2
const mongoose = require('mongoose');
require("dotenv").config()
// const { request, response } = require('express');
// const dotenv = require("dotenv");
// dotenv.config();
//mongoDB and Cloudinary
const mongoURI = process.env.Mongo_URI;
cloudinary.config({
cloud_name: process.env.CLOUD_NAME,
api_key: process.env.API_KEY,
api_secret: process.env.API_SECRET
})
mongoose.connect(mongoURI, {useNewUrlParser:true, useUnifiedTopology:true}, (error)=>{
if(error) {
return console.log(error)
}
return console.log("database is connected")
})
router.post("/api/house-listing", async (request, response)=>{
const form = new Formidable.IncomingForm();
form.parse(request, (error, fields, files)=>{
const {
price,
city,
county,
numOfBeds,
numOfBaths,
numOfGarages,
isSaleOrRent,
us_state,
squarefeet
} = fields
const { house_image } = files;
console.log('Price: ', price)
console.log('City: ', city)
console.log('county: ', county)
console.log('state: ', us_state)
console.log('squarefeet', squarefeet)
console.log('numOfGarages: ', numOfGarages)
console.log('numOfBeds: ', numOfBeds)
console.log('numOfBaths: ', numOfBaths)
console.log('isSaleOrRent: ', isSaleOrRent)
console.log('houseImage', house_image.path)
cloudinary.uploader.upload( house_image.path,
{folder:"/houseAgency/houses"}, async(error, result)=>{
if(error) {
console.log(error)
}
const image_url = result.url;
const newHouse = new House({
house_location: {
county: county,
city: city,
us_state: us_state
},
house_details: {
price: price,
squarefeet: squarefeet,
numOfBeds: numOfBeds,
numOfBaths: numOfBaths,
numOfGarages: numOfGarages,
isSaleOrRent: isSaleOrRent,
house_image: image_url,
}
})
const savedHouse = await newHouse.save();
return response.status(200).json(savedHouse)
})
})
})
router.put('/api/house-details/:id', (req, res) => {
House.findByIdAndUpdate({_id: req.params.id}, req.body).exec().then((data)=>{
console.log(req.body)
if (data === null) {
return res.status(404).json({ errors: [{location: "house-details", msg: "Not found", param: req.params.id}]})
}
return res.status(200).json(data)
}).catch((error)=>{
return res.status(500).json({"error": error})
})
});
module.exports = router;
HouseFetch
// const express= require('express')
// const router = express.Router()
const router = require('express').Router();
const {House} = require('../../Models/House');
//HOUSE Fetch
router.get('/api/house-sale', async(req, res)=>{
try{
House.find({'house_details.isSaleOrRent': 'SALE'}).exec().then((data)=>{
// console.log(data);
return res.status(200).json(data)
})
} catch(error) {
return res.status(500).json({msg: 'server is currently Down :('})
}
})
router.get('/api/house-rent', async(req, res)=>{
try{
House.find({'house_details.isSaleOrRent': 'RENT'}).exec().then((data)=>{
return res.status(200).json(data)
})
} catch(error) {
return res.status(500).json({msg: 'server is currently Down :('})
}
})
router.get('/api/house-details/:id', async(req, res)=>{
await House.findOne({_id:req.params.id}).exec().then(data=>{
return res.status(200).json(data)
}).catch(error =>{
return res.status(400).json(error)
})
})
router.get("/api/house-search/:query", async (request, response) => {
const us_states = [
"Alabama", "Alaska", "American Samoa", "Arizona", "Arkansas", "California", "Colorado", "Connecticut", "Delaware", "District of Columbia", "Florida", "Georgia", "Guam", "Hawaii", "Idaho", "Illinois", "Indiana", "Iowa", "Kansas", "Kentucky", "Louisiana", "Maine", "Maryland", "Massachusetts", "Michigan", "Minnesota", "Minor Outlying Islands", "Mississippi", "Missouri", "Montana", "Nebraska", "Nevada", "New Hampshire", "New Jersey", "New Mexico", "New York", "North Carolina", "North Dakota", "Northern Mariana Islands", "Ohio", "Oklahoma", "Oregon", "Pennsylvania", "Puerto Rico", "Rhode Island", "South Carolina", "South Dakota", "Tennessee", "Texas", "U.S. Virgin Islands", "Utah", "Vermont", "Virginia", "Washington", "West Virginia", "Wisconsin", "Wyoming"
];
const query = request.params.query;
const result = [];
for (let counter = 0; counter < us_states.length; counter++) {
let currentStates = us_states[counter];
if (query.toLowerCase().includes(currentStates.toLowerCase())) {
result.push(currentStates);
}
}
House.find({ "house_location.us_state": result[0] })
.exec()
.then((data) => {
return response.status(200).json(data);
})
.catch((error) => {
console.log(error);
});
});
module.exports = router;
don't use async/await and .then .catch together, use async/await with try/catch, and you don't need to return when you use res so you should update all queries like this:
try {
let data = await House.findByIdAndUpdate({_id: req.params.id}, req.body).toObject()
res.status(200).json(data)
} catch (error) {
res.status(404).json({"error": error})
}
if you want result of find be a plain object javascript you can use lean() for find and use toObject for findOne or findById also you can try like this
let data = await House.findByIdAndUpdate({_id: req.params.id}, req.body)
res.status(200).json({data})
You are trying to execute the method put in a route but you have not declared it in your code.
In your question you do not mention what route you are trying to access. I suppose you would like to do a put in route /api/house-details/:id.
In order to achieve this, you should declare a new route with put method, after put route declaration you should write your route handler:
router.put('/api/house-details/:id', (req, res) => {
const options = { new: true };
House.findByIdAndUpdate(req.params.id, req.body, options).exec().then((data)=>{
console.log(req.body)
if (data.value === null) {
return res.status(404).json({ errors: [{location: "house-details", msg: "Not found", param: req.params.id}]})
}
return res.status(200).json(data)
}).catch((error)=>{
return res.status(500).json({"error": error})
})
});
To return the modified the modified collection item (after update), you must include options: { new: true}, as stated by documentation.
I am creating an API using Node but am struggling to understand how to properly Unit test the API. The API itself uses Express and Mongo (with Mongoose).
So far I have been able to create Integration tests for end to end testing of the API endpoints themselves. I have used supertest, mocha and chai for the integration tests along with dotenv to use a test database when running it. The npm test script sets the environment to test before the integration tests run. It works excellently.
But I would like to also create Unit Tests for various components such as the controller functions.
I'm keen to use Sinon for the Unit Tests but I'm struggling to know what next steps to take.
I'll detail a genericised version of the API rewritten to be everybody's favourite Todos.
The app has the following directory structure:
api
|- todo
| |- controller.js
| |- model.js
| |- routes.js
| |- serializer.js
|- test
| |- integration
| | |- todos.js
| |- unit
| | |- todos.js
|- index.js
|- package.json
package.json
{
"name": "todos",
"version": "1.0.0",
"description": "",
"main": "index.js",
"directories": {
"doc": "docs"
},
"scripts": {
"test": "mocha test/unit --recursive",
"test-int": "NODE_ENV=test mocha test/integration --recursive"
},
"author": "",
"license": "ISC",
"dependencies": {
"body-parser": "^1.15.0",
"express": "^4.13.4",
"jsonapi-serializer": "^3.1.0",
"mongoose": "^4.4.13"
},
"devDependencies": {
"chai": "^3.5.0",
"mocha": "^2.4.5",
"sinon": "^1.17.4",
"sinon-as-promised": "^4.0.0",
"sinon-mongoose": "^1.2.1",
"supertest": "^1.2.0"
}
}
index.js
var express = require('express');
var app = express();
var mongoose = require('mongoose');
var bodyParser = require('body-parser');
// Configs
// I really use 'dotenv' package to set config based on environment.
// removed and defaults put in place for brevity
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
// Database
mongoose.connect('mongodb://localhost/todosapi');
//Middleware
app.set('port', 3000);
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());
// Routers
var todosRouter = require('./api/todos/routes');
app.use('/todos', todosRouter);
app.listen(app.get('port'), function() {
console.log('App now running on http://localhost:' + app.get('port'));
});
module.exports = app;
serializer.js
(This purely takes the output from Mongo and serializes it into JsonAPI format. So it is a bit superfluous to this example but I left it in as it is something I currently make use of in the api.)
'use strict';
var JSONAPISerializer = require('jsonapi-serializer').Serializer;
module.exports = new JSONAPISerializer('todos', {
attributes: ['title', '_user']
,
_user: {
ref: 'id',
attributes: ['username']
}
});
routes.js
var router = require('express').Router();
var controller = require('./controller');
router.route('/')
.get(controller.getAll)
.post(controller.create);
router.route('/:id')
.get(controller.getOne)
.put(controller.update)
.delete(controller.delete);
module.exports = router;
model.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var todoSchema = new Schema({
title: {
type: String
},
_user: {
type: Schema.Types.ObjectId,
ref: 'User'
}
});
module.exports = mongoose.model('Todo', todoSchema);
controller.js
var Todo = require('./model');
var TodoSerializer = require('./serializer');
module.exports = {
getAll: function(req, res, next) {
Todo.find({})
.populate('_user', '-password')
.then(function(data) {
var todoJson = TodoSerializer.serialize(data);
res.json(todoJson);
}, function(err) {
next(err);
});
},
getOne: function(req, res, next) {
// I use passport for handling User authentication so assume the user._id is set at this point
Todo.findOne({'_id': req.params.id, '_user': req.user._id})
.populate('_user', '-password')
.then(function(todo) {
if (!todo) {
next(new Error('No todo item found.'));
} else {
var todoJson = TodoSerializer.serialize(todo);
return res.json(todoJson);
}
}, function(err) {
next(err);
});
},
create: function(req, res, next) {
// ...
},
update: function(req, res, next) {
// ...
},
delete: function(req, res, next) {
// ...
}
};
test/unit/todos.js
var mocha = require('mocha');
var sinon = require('sinon');
require('sinon-as-promised');
require('sinon-mongoose');
var expect = require('chai').expect;
var app = require('../../index');
var TodosModel = require('../../api/todos/model');
describe('Routes: Todos', function() {
it('getAllTodos', function (done) {
// What goes here?
});
it('getOneTodoForUser', function (done) {
// What goes here?
});
});
Now I don't want to test the routes themselves (I do that in the Integration Tests not detailed here).
My current thinking is that the next best thing is to actually unit test controller.getAll or controller.getOne functions. And then to Mock the calls to Mongo via Mongoose using Sinon stubs.
But I have no idea what to do next despite having read the sinon docs :/
Questions
How do I test controller functions if it requires req, res, next as parameters?
Do I move the model's find and populate (currently in the Controller function) into todoSchema.static functions?
How to mock the populate function to do a Mongoose JOIN?
Basically what goes into test/unit/todos.js to get the above in a solid Unit Test state :/
The end goal is to run mocha test/unit and have it unit test the various parts of that API section
Hi I've created some test for you to understand how to use mocks.
Full example github/nodejs_unit_tests_example
controller.test.js
const proxyquire = require('proxyquire')
const sinon = require('sinon')
const faker = require('faker')
const assert = require('chai').assert
describe('todo/controller', () => {
describe('controller', () => {
let mdl
let modelStub, serializerStub, populateMethodStub, fakeData
let fakeSerializedData, fakeError
let mongoResponse
before(() => {
fakeData = faker.helpers.createTransaction()
fakeError = faker.lorem.word()
populateMethodStub = {
populate: sinon.stub().callsFake(() => mongoResponse)
}
modelStub = {
find: sinon.stub().callsFake(() => {
return populateMethodStub
}),
findOne: sinon.stub().callsFake(() => {
return populateMethodStub
})
}
fakeSerializedData = faker.helpers.createTransaction()
serializerStub = {
serialize: sinon.stub().callsFake(() => {
return fakeSerializedData
})
}
mdl = proxyquire('../todo/controller.js',
{
'./model': modelStub,
'./serializer': serializerStub
}
)
})
beforeEach(() => {
modelStub.find.resetHistory()
modelStub.findOne.resetHistory()
populateMethodStub.populate.resetHistory()
serializerStub.serialize.resetHistory()
})
describe('getAll', () => {
it('should return serialized search result from mongodb', (done) => {
let resolveFn
let fakeCallback = new Promise((res, rej) => {
resolveFn = res
})
mongoResponse = Promise.resolve(fakeData)
let fakeRes = {
json: sinon.stub().callsFake(() => {
resolveFn()
})
}
mdl.getAll(null, fakeRes, null)
fakeCallback.then(() => {
sinon.assert.calledOnce(modelStub.find)
sinon.assert.calledWith(modelStub.find, {})
sinon.assert.calledOnce(populateMethodStub.populate)
sinon.assert.calledWith(populateMethodStub.populate, '_user', '-password')
sinon.assert.calledOnce(serializerStub.serialize)
sinon.assert.calledWith(serializerStub.serialize, fakeData)
sinon.assert.calledOnce(fakeRes.json)
sinon.assert.calledWith(fakeRes.json, fakeSerializedData)
done()
}).catch(done)
})
it('should call next callback if mongo db return exception', (done) => {
let fakeCallback = (err) => {
assert.equal(fakeError, err)
done()
}
mongoResponse = Promise.reject(fakeError)
let fakeRes = sinon.mock()
mdl.getAll(null, fakeRes, fakeCallback)
})
})
describe('getOne', () => {
it('should return serialized search result from mongodb', (done) => {
let resolveFn
let fakeCallback = new Promise((res, rej) => {
resolveFn = res
})
mongoResponse = Promise.resolve(fakeData)
let fakeRes = {
json: sinon.stub().callsFake(() => {
resolveFn()
})
}
let fakeReq = {
params: {
id: faker.random.number()
},
user: {
_id: faker.random.number()
}
}
let findParams = {
'_id': fakeReq.params.id,
'_user': fakeReq.user._id
}
mdl.getOne(fakeReq, fakeRes, null)
fakeCallback.then(() => {
sinon.assert.calledOnce(modelStub.findOne)
sinon.assert.calledWith(modelStub.findOne, findParams)
sinon.assert.calledOnce(populateMethodStub.populate)
sinon.assert.calledWith(populateMethodStub.populate, '_user', '-password')
sinon.assert.calledOnce(serializerStub.serialize)
sinon.assert.calledWith(serializerStub.serialize, fakeData)
sinon.assert.calledOnce(fakeRes.json)
sinon.assert.calledWith(fakeRes.json, fakeSerializedData)
done()
}).catch(done)
})
it('should call next callback if mongodb return exception', (done) => {
let fakeReq = {
params: {
id: faker.random.number()
},
user: {
_id: faker.random.number()
}
}
let fakeCallback = (err) => {
assert.equal(fakeError, err)
done()
}
mongoResponse = Promise.reject(fakeError)
let fakeRes = sinon.mock()
mdl.getOne(fakeReq, fakeRes, fakeCallback)
})
it('should call next callback with error if mongodb return empty result', (done) => {
let fakeReq = {
params: {
id: faker.random.number()
},
user: {
_id: faker.random.number()
}
}
let expectedError = new Error('No todo item found.')
let fakeCallback = (err) => {
assert.equal(expectedError.message, err.message)
done()
}
mongoResponse = Promise.resolve(null)
let fakeRes = sinon.mock()
mdl.getOne(fakeReq, fakeRes, fakeCallback)
})
})
})
})
model.test.js
const proxyquire = require('proxyquire')
const sinon = require('sinon')
const faker = require('faker')
describe('todo/model', () => {
describe('todo schema', () => {
let mongooseStub, SchemaConstructorSpy
let ObjectIdFake, mongooseModelSpy, SchemaSpy
before(() => {
ObjectIdFake = faker.lorem.word()
SchemaConstructorSpy = sinon.spy()
SchemaSpy = sinon.spy()
class SchemaStub {
constructor(...args) {
SchemaConstructorSpy(...args)
return SchemaSpy
}
}
SchemaStub.Types = {
ObjectId: ObjectIdFake
}
mongooseModelSpy = sinon.spy()
mongooseStub = {
"Schema": SchemaStub,
"model": mongooseModelSpy
}
proxyquire('../todo/model.js',
{
'mongoose': mongooseStub
}
)
})
it('should return new Todo model by schema', () => {
let todoSchema = {
title: {
type: String
},
_user: {
type: ObjectIdFake,
ref: 'User'
}
}
sinon.assert.calledOnce(SchemaConstructorSpy)
sinon.assert.calledWith(SchemaConstructorSpy, todoSchema)
sinon.assert.calledOnce(mongooseModelSpy)
sinon.assert.calledWith(mongooseModelSpy, 'Todo', SchemaSpy)
})
})
})
routes.test.js
const proxyquire = require('proxyquire')
const sinon = require('sinon')
const faker = require('faker')
describe('todo/routes', () => {
describe('router', () => {
let expressStub, controllerStub, RouterStub, rootRouteStub, idRouterStub
before(() => {
rootRouteStub = {
"get": sinon.stub().callsFake(() => rootRouteStub),
"post": sinon.stub().callsFake(() => rootRouteStub)
}
idRouterStub = {
"get": sinon.stub().callsFake(() => idRouterStub),
"put": sinon.stub().callsFake(() => idRouterStub),
"delete": sinon.stub().callsFake(() => idRouterStub)
}
RouterStub = {
route: sinon.stub().callsFake((route) => {
if (route === '/:id') {
return idRouterStub
}
return rootRouteStub
})
}
expressStub = {
Router: sinon.stub().returns(RouterStub)
}
controllerStub = {
getAll: sinon.mock(),
create: sinon.mock(),
getOne: sinon.mock(),
update: sinon.mock(),
delete: sinon.mock()
}
proxyquire('../todo/routes.js',
{
'express': expressStub,
'./controller': controllerStub
}
)
})
it('should map root get router with getAll controller', () => {
sinon.assert.calledWith(RouterStub.route, '/')
sinon.assert.calledWith(rootRouteStub.get, controllerStub.getAll)
})
it('should map root post router with create controller', () => {
sinon.assert.calledWith(RouterStub.route, '/')
sinon.assert.calledWith(rootRouteStub.post, controllerStub.create)
})
it('should map /:id get router with getOne controller', () => {
sinon.assert.calledWith(RouterStub.route, '/:id')
sinon.assert.calledWith(idRouterStub.get, controllerStub.getOne)
})
it('should map /:id put router with update controller', () => {
sinon.assert.calledWith(RouterStub.route, '/:id')
sinon.assert.calledWith(idRouterStub.put, controllerStub.update)
})
it('should map /:id delete router with delete controller', () => {
sinon.assert.calledWith(RouterStub.route, '/:id')
sinon.assert.calledWith(idRouterStub.delete, controllerStub.delete)
})
})
})
serializer.test.js
const proxyquire = require('proxyquire')
const sinon = require('sinon')
describe('todo/serializer', () => {
describe('json serializer', () => {
let JSONAPISerializerStub, SerializerConstructorSpy
before(() => {
SerializerConstructorSpy = sinon.spy()
class SerializerStub {
constructor(...args) {
SerializerConstructorSpy(...args)
}
}
JSONAPISerializerStub = {
Serializer: SerializerStub
}
proxyquire('../todo/serializer.js',
{
'jsonapi-serializer': JSONAPISerializerStub
}
)
})
it('should return new instance of Serializer', () => {
let schema = {
attributes: ['title', '_user']
,
_user: {
ref: 'id',
attributes: ['username']
}
}
sinon.assert.calledOnce(SerializerConstructorSpy)
sinon.assert.calledWith(SerializerConstructorSpy, 'todos', schema)
})
})
})