I'm testing an express server using super-test and I need to test a post call. I assume the post should be successful and return a status of 200 but it is returning 401. I've been told by someone that I need to pass a request body with the post but I'm unsure exactly how to do this.
I've attempted to use .send({name: 'aName'}) but that gives me the same 401 code.
Below is the app.js
require('dotenv').config();
const express = require('express');
const bodyParser = require('body-parser');
const hateoasLinker = require('express-hateoas-links');
const AValidator = require('./AValidator');
const BValidator = require('./BValidator');
const schema_v1 = require("./schema.json");
const {
logService: logger
} = require("#utils");
let aValidator = AValidator(schema_v1);
let ValidatorApi = BValidator.ValidatorApi('api');
let adminValidator = BValidator.ValidatorAdmin('admin');
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(hateoasLinker);
app.post('/*/activate',admiValidator, (req, res) => {
console.log("In Activate===============>");
res.status(200);
res.json({
rel: "self",
method: "POST",
title: 'Activate Solution',
href: "/activate"
});
});
Here is the code for the BValidator
ValidatorAdmin = function(callType){
return function (req,res,next){
let authoizationHeader = req.headers['authorization'];
try {
Verifier.verifyPayload(authoizationHeader, callType, (verificationError) => {
if (verificationError) {
res.setHeader('Content-Type', 'application/json');
res.status(401);
res.json({
message : "verificationError "+verificationError.message
});
} else {
next();
}
});
} catch (authorizationError) {
res.setHeader('Content-Type', 'application/json');
res.status(401);
res.json({
message : authorizationError.message
});
}
}
}
Here is the app.test.js
const request = require('supertest');
const bodyParser = require('body-parser');
let AValidator = require('../src/AValidator');
let BValidator = require('../src/BValidator');
BValidator = jest.fn();
AValidator = jest.fn();
app = require('../src/app');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
describe('Test os GET/POST calls in app.js', ()=>{
test('Tests activate post', (done)=>{
BValidator.mockReturnValue({
ValidatorApi: (req,res,next)=>{
next();
},
ValidatorAdmin:(req,res,next)=>{
next();
}
});
AValidator.mockImplementation((schema)=>{
return function (req,res,next){
next();
}
});
request(app)
.post('/test/activate')
.set({name:'josh'})
.then((response)=>{
expect(response.statusCode).toBe(200);
done();
})
})
});
So ultimately I'd like this post to resolve successfully and return a status code of 200.
I am assuming you have set the parser correctly. Example:
const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));
Once this is set, I believe you're missing the headers. Example:
const payload = {name: 'john', email: 'xyz#sadfjak.com', password: '2342388' };
const res = await request(app)
.post('/api/register')
.send(payload)
.set('Content-Type', 'application/json')
.set('Accept', 'application/json')
Your problem is that you have a wrong understanding of what those mock functions are doing. First of all, you are completely overwriting the original values of AValidator and BValidator with jest.fn().
So doing
let AValidator = require('../src/AValidator');
let BValidator = require('../src/BValidator');
in your test is redundant.
The purpose of mockReturnValue is so that you can call that function and get back the return value you specified.
Taken straight from Jest docs
const myMockFn = jest
.fn()
.mockReturnValue('default')
.mockReturnValueOnce('first call')
.mockReturnValueOnce('second call');
// 'first call', 'second call', 'default', 'default'
console.log(myMockFn(), myMockFn(), myMockFn(), myMockFn());
You are never using or calling your mock functions, and furthermore your api has no clue they even exist.
The solution is to provide the appropriate headers in the request when you run your tests so they don't fail in the middleware. Also, to do that, you have to know what Verifier.verifyPayload is doing.
with supertest your request should look like
request(app)
.post('/test/activate')
.set({authorization: 'a_valid_value_goes_here'})
.then((response)=>{
expect(response.statusCode).toBe(200);
done();
})
Related
I created simple react + express app for fetching data from Strava.
Authorization is done by passport.js and there aren't any problems when I run it locally (on separate ports client/server: 3000/8080 or using only server to provide production build) although when deployed the app on render.com I'm getting 401 unauthorised when making calls to my API.
I have two environments:
(client) static site for react app: https://statz.onrender.com
(server) web service for my express API: https://statz-api.onrender.com
For instance - to get logged athlete data client makes request to the server and then server is requesting that data from Strava API and returns the data to the client.
For now I have only two endpoints and both throw error 401 when called from client side.
Authentication is done server side and at the end the user is redirected back to the client. After all every request from client to API throws 401 but when calling endpoint directly from the browser eg: (https://statz-api.onrender.com/api/athlete) I'm getting complete data. Demo: https://share.getcloudapp.com/NQu4J7YL
Probably it's worth to mention there is no cookie in the request headers when calling from client.
I had some CORS errors first but probably I sorted them out since there is nothing about it on the console.
I would be grateful if somebody could look at this and show me what I'm still missing in my configs.
Link to github: https://github.com/PatrykJamroz/StravaStats/tree/deploy-1
server:
const express = require("express");
const passport = require("passport");
const cors = require("cors");
const logger = require("morgan");
const StravaStrategy = require("passport-strava-oauth2").Strategy;
const cookieParser = require("cookie-parser");
const bodyParser = require("body-parser");
const cookieSession = require("cookie-session");
const app = express();
app.use(
cors({
credentials: true,
origin: "https://statz.onrender.com",
exposedHeaders: ["Set-Cookie"],
})
);
app.use(function (req, res, next) {
res.header("Access-Control-Allow-Credentials", true);
res.header(
"Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept"
);
next();
});
require("dotenv").config();
const fetch = (...args) =>
import("node-fetch").then(({ default: _fetch }) => _fetch(...args));
const STRAVA_CLIENT_ID = process.env.STRAVA_CLIENT_ID;
const STRAVA_CLIENT_SECRET = process.env.STRAVA_CLIENT_SECRET;
passport.serializeUser(function (user, done) {
done(null, user);
});
passport.deserializeUser(function (obj, done) {
done(null, obj);
});
passport.use(
new StravaStrategy(
{
clientID: STRAVA_CLIENT_ID,
clientSecret: STRAVA_CLIENT_SECRET,
callbackURL: "/auth/strava/callback",
},
function (accessToken, refreshToken, profile, done) {
process.nextTick(function () {
return done(null, profile);
});
}
)
);
app.use(logger("combined"));
app.use(cookieParser());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(express.methodOverride());
app.use(cookieSession({ secret: "keyboard cat" }));
app.use(passport.initialize());
app.use(passport.session());
app.use(app.router);
const SCOPES = "read,activity:read_all,read_all";
app.get(
"/auth/strava",
passport.authenticate("strava", { scope: SCOPES }),
function (req, res) {
// The request will be redirected to Strava for authentication, so this
// function will not be called.
}
);
app.get(
"/auth/strava/callback",
passport.authenticate("strava", {
scope: SCOPES,
failureRedirect: "/login",
}),
function (req, res) {
res.redirect("https://statz.onrender.com");
}
);
app.get("/api/athlete", async (req, res) => {
fetch(
`https://www.strava.com/api/v3/athlete?access_token=${
req.user?.token ?? ""
}`
)
.then((response) => {
if (response.ok) {
return response.json();
}
res.status(response.status).json({ error: response.statusText });
throw new Error(`${response.status} - ${response.statusText}`);
})
.then((data) => res.json(data))
.catch((error) => console.error({ error }));
});
app.get("/api/activities", async function (req, res) {
if (!req.user) {
res.json({ error: "Not authenticated" });
}
let page = 1;
let activities = [];
while (true) {
const activitiesPromise = await fetch(
`https://www.strava.com/api/v3/athlete/activities?per_page=30&page=${page}&access_token=${req.user.token}`
);
const activitiesData = await activitiesPromise.json();
page += 1;
activities = [...activities, ...activitiesData];
logger({ page });
if (activitiesData.length < 30) {
return res.json(activities);
}
}
});
app.get("/api/ping", function (req, res) {
res.send("pong");
});
const listener = app.listen(process.env.PORT || 8080, () => {
console.log(`Your app is listening on port ${listener.address().port}`);
});
Client API calls:
import axios from 'axios';
import { Activity, Athlete } from '../models/Strava';
const BASE_URL = 'https://statz-api.onrender.com';
export const getAthlete = async (): Promise<Athlete> => {
return axios.get(`${BASE_URL}/api/athlete`, { withCredentials: true }).then(({ data }) => data);
};
export const getActivities = async (): Promise<Activity[]> => {
return axios
.get(`${BASE_URL}/api/activities`, { withCredentials: true })
.then(({ data }) => data);
};
Authorization component:
export function Authorization() {
const login = () => {
window.open('https://statz-api.onrender.com/auth/strava', '_self');
};
return (
<Button onClick={login} variant="outlined">
<img alt="authorize" src="/btn_strava_auth.png" />
</Button>
);
}
I'm currently learning angular and working on a project with a mongoDB database and express for my APIs. I want to fetch the comments of a post by the post ID,
The get request returns me a list of comments. the problem is when I first run node js the get request doesn't work, it only works when I first post a new comment and then run the get request for the comments again.
And as long as node is running the get request will continue to work whenever it's called for, until I restart node once again for the error to happen again.
it returns a 404 not found error.
This error doesn't happen with any other route, but my code is the same in all of them.
PS : I Have made sure that the function is getting the post id before the get request is made.
this is my server.js file
let express = require('express'),
path = require('path'),
mongoose = require('mongoose'),
cors = require('cors'),
bodyParser = require('body-parser'),
dbConfig = require('./database/db');
//create Error definition
const createError = require('http-errors');
// Connecting with mongo db
mongoose.Promise = global.Promise;
mongoose.connect(dbConfig.db, {
useNewUrlParser: true
}).then(() => {
console.log('Database sucessfully connected')
},
error => {
console.log('Database could not connected: ' + error)
}
)
const userRoute = require('./routes/user.route');
const postRoute = require('./routes/post.route');
const galleryRoute = require('./routes/Gallery.route');
const likeRoute = require('./routes/Like.Route');
const commentRoute = require('./routes/Comment.route');
const shareRoute = require('./routes/Share.route');
const profilePicRoute = require('./routes/ProfilePic.route');
const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
extended: true
}));
app.use(cors());
app.use(express.static(path.join(__dirname, 'dist/mean-stack-crud-app')));
app.use('/', express.static(path.join(__dirname, 'dist/mean-stack-crud-app')));
app.use('/api/users', userRoute);
app.use('/api/posts', postRoute);
app.use('/api/likes', likeRoute);
app.use('/api/profilePics', profilePicRoute);
app.use('/api/comments', commentRoute);
app.use('/api/shares', shareRoute);
app.use('/api/gallery', galleryRoute);
// Create port
const port = process.env.PORT || 4000;
const server = app.listen(port, () => {
console.log('Connected to port ' + port)
})
// Find 404 and hand over to error handler
app.use((req, res, next) => {
next(createError(404));
});
// error handler
app.use(function (err, req, res, next) {
console.error(err.message); // Log error message in our server's console
if (!err.statusCode) err.statusCode = 500; // If err has no specified error code, set error code to 'Internal Server Error (500)'
res.status(err.statusCode).send(err.message); // All HTTP requests must have a response, so let's send back an error with its status code and message
});
this is my commentRoute.js
const express = require('express');
const commentRoute = express.Router();
// Comment model
let Comment = require('../models/Comment');
const createError = require('http-errors');
//multer for pic upload
const uploadMedia = require('../middleware/picUpload')
// Add Comment
commentRoute.route('/create').post((req, res, next) => {
// if(req?.files[0]){
// newComment.media = req?.files[0]
// }
let newComment = req.body;
newComment.creationDate = new Date(req.body.creationDate)
console.log(newComment)
Comment.create(newComment, (error, data) => {
// if (error instanceof multer.MulterError ) {
// error.message += "\nmulter Error";
// return next(error)
// }else
if (error){
return next(error)
}
else {
res.json(data);
}
})
//Get comments by parent ID
commentRoute.route('/read/byParentId/:idParent').get( async (req, res, next) => {
await Comment.find({idParent : req.params.idParent}, (error, data) => {
if(error){
return next(error)
}else{
res.json(data)
}
})
})
})
module.exports = commentRoute;
this is my mongoose comment schema
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
// Define collection and schema
let Comment = new Schema({
idUser: {
type : String
},
idParent : {
type : String
},
text : {
type : String
},
media : {
fieldname : { type : String },
originalname : { type : String },
encoding : { type : String },
mimetype : { type : String },
buffer : { type : Buffer },
},
creationDate : {
type : Date
}
},
{
collection: 'comments'
})
module.exports = mongoose.model('Comment', Comment);
this is my client side http get request
baseUrl = 'http://localhost:4000/api/comments';
headers = new HttpHeaders().set('Content-Type', 'application/json');
constructor(private http : HttpClient) { }
getCommentsByParentId(idParent : any){
return this.http.get(`${this.baseUrl}/read/byParentId/${idParent}`);
}
this is how I consume the api in the client side
getComments(){
this.commentService.getCommentsByParentId(this.idPost).subscribe({
next : (res : any) => {
this.comments = res
this.commentsCount = res.length
},
error : (err : any) => {
console.log("error getting comment list for post "+this.idPost)
}
})
}
client side error :
server side error :
thank you.
Edit :
post without the list of comments before I post a new comment
post after I post a new comment
Well, that's very obvious that the server can't find the entity in the DB.
You need to check one of the following:
Maybe when you restart the node server, you restart the db too. that can happen if you're using docker-compose locally. then when you run your node server again your DB starts but there's no data in the DB, therefore the service can't find any data.
After service restart you're using non-existing ID because of wrong UI flow.
I would guess that you're facing the first option.
I am writing a bulk import function for a password manager for myself and I have come across an issue.
There is an array of passwords to import and I'm using a forEach() method to iterate through each password to import.
I call the insert function and everything just stops. No error, no callback, no saving to file. Here is my code:
const express = require('express')
const app = express()
const { encrypt, decrypt } = require('./crypto')
const Datastore = require('nedb')
app.post('/bulkimport', checkAuthenticated, (req, res) => {
var passwords = JSON.parse(req.body.passwords)
var dbForUser = new Datastore('./passwords/' + req._passport.session.user + '.db')
passwords.forEach(password => {
function doc(code, link, name, password) {
this.code = code
this.link = link
this.name = name
this.password = password
}
var entry = new doc(password.name, password.url, password.username, password.password)
console.log(entry)
console.log('before insert') // gets logged
dbForUser.insert(entry, function(err, doc) {
console.log('after insert') // doesn't get logged
if (err) return res.status(500).send()
console.log(doc)
})
});
})
Middlewares I'm using:
app.use(bodyParser.json())
app.use(express.urlencoded({ extended: false }))
app.use(flash())
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false
}))
app.use(passport.initialize())
app.use(passport.session())
app.use(methodOverride('_method'))
Thanks for the help!
I see two problems. db.insert according to the nedb docs takes a plain JS object, and you're calling a res.send() in a forEach (which could result in the cannot set headers after they are sent error. You can also skip parsing the body by using a module for that.
const express = require('express')
// added
const bodyParser = require('body-parser')
const app = express()
const { encrypt, decrypt } = require('./crypto')
const Datastore = require('nedb')
// added
app.use(bodyParser.json())
app.post('/bulkimport', checkAuthenticated, (req, res) => {
// changed
var passwords = req.body.passwords
var dbForUser = new Datastore('./passwords/' + req._passport.session.user + '.db')
// changed: forEach would just keep running, potentially causing errors
for (let password of passwords) {
// changed
var entry = {
link: password.url,
password: password.password,
name: password.username,
code: password.name,
}
// changed to remove res.send from callback
let e = false
dbForUser.insert(entry, (err, doc) => {
if (err) {
e = true
}
})
// added to exit from route if an error
if (e) {
res.status(500).send()
return
}
}
res.status(201).send()
})
EDIT:
In the chat we also discovered that nedb doesn't really do anything on disk unless you call it with autoload: true or call loadDatabase, which was happening on one of the databases but not the one in the Express route (docs). Adding that got things working.
I have a list of articles that have a property views and I want to increment that property in the database each time a user clicks on an article title. Currently nothing happens when I do it. Why isn't it working and how can I increment that property each time on click? Here is my React part:
const incrementViews = (id) => {
var item = posts.find(x => x._id === id);
item.views += 1;
}
<div className="post-title">
<Link to={`/post/${post._id}`}>
<h2><a href="#" onClick={() => incrementViews(post._id)}>{post.title}</a>
</h2>
</Link>
</div>
and my server.js:
// Requiring the dependencies
const express = require('express');
const app = express();
const bodyParser = require('body-parser');
const cors = require('cors');
require('dotenv').config();
const mongoose = require('mongoose');
const PORT = process.env.PORT || 3001;
const BASE_URL = process.env.REACT_APP_BASE_URL;
console.log(BASE_URL)
const itemRoutes = express.Router();
let Comment = require('./comment.model');
app.use(cors());
app.use(bodyParser.json());
mongoose.connect(BASE_URL, { useNewUrlParser: true })
const connection = mongoose.connection;
connection.once('open', function () {
console.log('Connection to MongoDB established succesfully!');
});
let collection = connection.collection("posts_with_tags_test");
collection.createIndex(
{
postContent: 'text',
title: 'text'
}
);
// Serve static assets
if (process.env.NODE_ENV === 'production') {
app.use(express.static('build'));
}
itemRoutes.route('/').get(async (req, res) => {
let collection = connection.collection("posts_with_tags_test");
let response = await collection.find({})
.toArray();
res.send(response);
});
itemRoutes.route('/search').post(async (req, res) => {
let result = await connection.collection("posts_with_tags_test").find({
$text: {
$search: req.body.searchString
}
}).toArray();
res.send(result);
});
itemRoutes.route("increment/:id"").post(async (req, res) => {
const { id } = req.params;
collection.updateOne({ _id: id }, { $inc: { views: 1 } });
return res.status(200).json({ msg: "OK" });
});
itemRoutes.route('/comments').get(async (req, res) => {
let collection = connection.collection("comments");
let response = await collection.find({})
.toArray();
res.send(response);
});
itemRoutes.route('/comments')
.post((req, res) => {
res.setHeader('Content-Type', 'application/json');
let comment = new Comment(req.body);
comment.save()
.then(comment => {
res.status(200).json({ comment })
})
.catch(err => {
res.status(400).send('failed')
})
});
app.use('/', itemRoutes);
app.use('/comments', itemRoutes);
app.use('/search', itemRoutes);
app.use('/increment', itemRoutes);
app.listen(PORT, function () {
console.log('Server is running on' + ' ' + PORT);
})
I think there are two problems in frontend and backend respectively.
Front-end
You should use post variable as a state variable so as to re-render then component when changes are made on post.
Back-end
There is no issue with increasing view in your code.
Here, you need to return success status.
The function incrementViews only increments views on the frontend and never sends any data to the API. One way you can make it work is as follows:
server.js
itemRoutes.route("/increment/:id").post(async (req, res) => {
const { id } = req.params;
collection.updateOne({ _id: id }, { $inc: { views: 1 } });
return res.status(200).json({ msg: "OK" });
});
React
const incrementViews = (id) => {
// Assuming your API server is running on port 5000.
fetch(`http://localhost:5000/increment/${id}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
})
.then((res) => res.json())
.then(console.log)
.catch(console.error);
};
Update
The reason you're getting 404 is a missing colon : in the route parameters.
// Notice the :id, colon is important.
itemRoutes.route("/increment/:id").post(async (req, res) => {
const { id } = req.params;
// ...
});
Here is a demo reproduced on Glitch. Removed database logic and just added a response messages.
I tested the demo using Postman and it works fine.
On a POST request to https://adaptive-sassy-legal.glitch.me/increment/123, should return a response as shown below.
{
msg: "itemsRoute increment.",
id: "123"
}
Update 2
Another thing which I forgot to mention in the previous update was to update the middleware.
// Use only `/increment` instead of `increment/:id`.
app.use("/increment", itemRoutes);
Here is an updated demo.
I'm working on an example project for a Udacity course and I'm tripped up with something. I'm trying to capture some user input from a form and make a post request to return a javascript object and when I try to run the server with node js, I get the error:
TypeError: Cannot read property 'body' of undefined
This is the server code for the project:
server.js
projectData = {};
/* Express to run server and routes */
const express = require('express');
/* Start up an instance of app */
const app = express();
/* Dependencies */
const bodyParser = require('body-parser')
/* Middleware*/
app.use(express.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
const cors = require('cors');
app.use(cors());
/* Initialize the main project folder*/
app.use(express.static('project1'));
const port = 8000;
/* Spin up the server*/
const server = app.listen(port, listening);
function listening(){
// console.log(server);
console.log(`running on localhost: ${port}`);
};
// GET route
const animalData = [];
app.get('/all', getData);
function getData(req, res){
res.send(AnimalData)
console.log(AnimalData)
}
// function sendData (request, response) {
// response.send(projectData);
// };
// POST route
app.post('/add', callBack);
function callBack(req,res){
res.send('POST received');
}
// POST an animal
const data = [];
// TODO-Call Function
app.route('/addAnimal')
.get(function (req, res) {
res.sendFile('index.html', {root: 'project1'})
})
.post(addAnimal())
function addAnimal(req, res){
newEntry = {
animal: req.body.animal,
facts: req.body.fact,
fav: req.body.fav
}
data.push(req.body);
res.status(200).send(req.body);
animalData.push(newEntry)
res.send(animalData)
console.log(animalData)
};
and this is the code for the client side:
app.js
function performActuion(e){
const fav = document.getElementById('fav').value;
const getAnimal = async (url) =>{
const res = await fetch(url);
try {
const data = await res.json();
console.log(data)
return data;
} catch(error) {
console.log()
}
};
/* Function to POST data */
const postData = async ( url = '', data = {})=>{
console.log(data);
const response = await fetch(url, {
method: 'POST', // *GET, POST, PUT, DELETE, etc.
credentials: 'same-origin', // include, *same-origin, omit
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data), // body data type must match "Content-Type" header
});
try {
const newData = await response.json();
console.log(newData);
// console.log(newData);
return newData.json()
console.log(await response.json());
return await response.json()
}catch(error) {
console.log("error", error);
// appropriately handle the error
};
};
// TODO-Call Function
(async function(){
let res = await postData('/addAnimal', (animal: data.animal, fact: data.fact, fav: fav));;
console.log(res);
})();
In the example for the lesson, this code appears to work but when I try to run it on my end I can't even test it because I get that type error. Any help would be greatly appreciated.
Thank you,
Mike
The problem is in this code:
app.route('/addAnimal')
.get(function (req, res) {
res.sendFile('index.html', {root: 'project1'})
})
.post(addAnimal())
You are actually calling the function instead of use it as callback, use the following code:
app.route('/addAnimal')
.get(function (req, res) {
res.sendFile('index.html', {root: 'project1'})
})
.post(addAnimal)