I recently added express-static-gzip to my server and have since noticed that my req.body is undefined in my router.post when submitting a form.
Previously it was working without issue but now I am getting a POST 500 internal server error, a Cannot read property "name" of undefined & a Uncaught (in promise) SyntaxError: Unexpected token < in JSON at position 0 error.
here is my form submission:
const handleSubmit = async (e) => {
e.preventDefault();
setStatus("Sending...");
const { name, email, message } = e.target.elements;
let details = {
name: name.value,
email: email.value,
message: message.value,
};
console.log(typeof JSON.stringify(details))
let response = await fetch("/api/v1/mail", {
method: "POST",
headers: {
"Content-Type": "application/json;charset=utf-8"
},
body: JSON.stringify(details),
});
setStatus("Send Message");
let result = await response.json();
alert(result.status);
};
here is my server setup, the route in question is '/api/v1/mail':
const express = require('express')
const path = require('path')
const router = express.Router();
const cors = require("cors");
var expressStaticGzip = require("express-static-gzip")
const mailRoutes = require('./routes/mail');
const server = express()
server.use('/api/v1/mail', mailRoutes)
server.use(cors())
server.use(express.json())
server.use("/", router)
server.use(expressStaticGzip(path.join(__dirname, 'public'), {
enableBrotli: true
}))
server.use(express.static(path.join(__dirname, 'public')))
and here is my POST request:
router.post("/", (req, res) => {
const name = req.body.name;
const email = req.body.email;
const orderId = req.body.orderId
const message = req.body.message;
const mail = {
from: name,
to: "info#example.com ",
subject: "Contact Form Submission",
html: `<p>Name: ${name}</p>
<p>Email: ${email}</p>
<p>Order ID: ${orderId}
<p>Message: ${message}</p>`,
};
contactEmail.sendMail(mail, (error) => {
if (error) {
res.json({ status: "ERROR" , req});
} else {
res.json({ status: "Message Sent" });
}
});
});
})
If the route you're trying to use req.body.name in is this one:
server.use('/api/v1/mail', mailRoutes)
Then, you have to move this:
server.use(express.json())
to be BEFORE that route definition.
As it is, you're trying to handle the route request before your middleware has read and parsed the JSON. Route handlers and middleware are matched and executed in the order they are registered. So, any middleware that needs to execute for a route handler to function must be registered before the route handler itself is registered.
Related
Can't get the last four lines in the helpers.js below to run for the test. The webpage works great, but I can't get the tests to pass/the mocks correct. This is my first time using Jest and unit testing in general, so there may be a fundamental understanding issue as well.
Function in helpers.js
exports.get_logout = (req, resp) => {
// sent to backend
const options = {
hostname: backend_hostname,
port: backend_port,
method: 'POST',
path: '/api/logout',
ca: ca,
headers: {
'Content-Type': 'application/json',
}
};
// set cookie
options.headers.Cookie = `ws_sid=${req.cookies.ws_sid}`
// send to backend to end session
const https_req = https.request(options, (response) => {
const response_status = response.statusCode;
const response_headers = response.headers;
response.on('data', (d) => {
process.stdout.write(d);
});
response.on('end', () =>{
switch (response_status) {
case 200:
console.log('Successfully logged out')
resp.clearCookie('ws_sid', {path: '/'})
resp.redirect('/')
break;
case 400:
console.log('Error on logout!')
resp.redirect('/');
break;
case 403:
console.log('User not logged in!')
resp.redirect('/');
break;
}
})
});
https_req.on('error', (e) => {
console.error(e);
});
https_req.write(JSON.stringify({}))
https_req.end();
}
the issue in website.test.js, is that the https_req.on is giving a TypeError:Cannot read properties of undefined (reading 'on')
Test here:
const helpers = require('./helpers');
const https = require('node:https');
// Mock for express request parameter
let mockRequest = (sessionData, method, body) => {
return {
session: { data: sessionData, cookie: {_expires: 'test_expire'}},
method: method,
body: {username:"", password:""},
cookies: {ws_sid:""},
};
};
// Mock for express response parameter
let mockResponse = () => {
const res = {};
res.status = jest.fn().mockReturnValue(res);
res.json = jest.fn().mockReturnValue(res);
res.send = jest.fn().mockReturnValue(res);
res.sendStatus = jest.fn().mockReturnValue(res)
res.render = jest.fn().mockReturnValue(res)
res.redirect = jest.fn().mockReturnValue(res)
return res;
};
jest.mock('https');
https.request = jest.fn();
https.on = jest.fn();
https.end = jest.fn();
test('should redirect to / if user successfully logged out', async () => {
const req = mockRequest({ 'username': 'all' });
const resp = mockResponse();
helpers.get_logout(req, resp);
expect(resp.redirect).toHaveBeenCalledWith('/');
});
Any help greatly appreciated.
I have tried mocking the https_req object - but kept getting errors with the implementation (not a function, can't access before initialization). I tried adding the https_req object to the mockRequest with (on, end) but got the same TypeError
So first what i want to say, is that none of the public questions on stackoverflow did not helped me with this error. I am running the Stripe CLI on my local machine like this : stripe listen --forward-to localhost:4242/webhook , but weirdly when i try to proccess all the events inside i get the error :
No signatures found matching the expected signature for payload. Are you passing the raw request body you received from Stripe? https://github.com/stripe/stripe-node#webhook-signing
I have already tried using request.rawBody , but it didn't fix my issue.
I am posting all of the code, so maybe someone will see what i can't and help me fixing it
router.js :
let express = require('express');
let router = express.Router();
let bodyParser = require('body-parser')
let postMong = require('./post')
require("dotenv").config()
router.use(express.json());
const YOUR_DOMAIN = 'http://localhost:4242';
const stripe = require('stripe')(process.env.PUBLIC_KEY);
router.post('/checkout/create-order', async (req, res) => {
const price = req.body.order.stripe_price || undefined,
product = req.body.order.stripe_product || undefined
const session = await stripe.checkout.sessions.create({
shipping_address_collection: {
allowed_countries: ['US', 'CA'],
},
shipping_options: [
{
shipping_rate_data: {
type: 'fixed_amount',
fixed_amount: {
amount: 2499,
currency: 'usd',
},
display_name: 'International Shipping',
// Delivers between 5-7 business days
delivery_estimate: {
minimum: {
unit: 'week',
value: 2,
},
}
}
},
],
line_items: [
{
price: price,
quantity: 1,
},
],
payment_method_types: ["card", 'us_bank_account'],
mode: 'payment',
success_url: `${YOUR_DOMAIN}/success.html`,
cancel_url: `${YOUR_DOMAIN}/index.html`,
});
res.json({url: session.url})
});
router.post('/posts/add', async (req,res)=>{
try{
const {author, id, name, picture, pixels, price, size, stripe_price, stripe_product} = req.body
const pos = await postMong.create( {author, id, name, picture, pixels, price, size, stripe_price, stripe_product})
res.json(pos)
} catch(e){
res.status(500).json(e)
}
})
router.get('/ideas', async (req,res)=>{
try{
const posts = await postMong.find()
return res.json(posts);
} catch(e){
reject(res.status(500).json(e))
}
})
const endpointSecret = 'whsec_****';
const fulfillOrder = (session) => {
// TODO: fill me in
console.log("Fulfilling order", session);
}
router.use(bodyParser.json());
router.post('/webhook', (request, response) => {
const payload = request.body;
const sig = request.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(request.rawBody, sig, endpointSecret);
console.log(event)
} catch (err) {
console.log(err.message)
return response.status(400).send(`Webhook Error: ${err.message}`);
}
// Handle the checkout.session.completed event
if (event.type === 'checkout.session.completed') {
const session = event.data.object;
// Fulfill the purchase...
fulfillOrder(session);
}
response.status(200);
});
module.exports = router
server.js :
const router = require("./router");
const account = require("./routerAccount");
const express = require('express');
const mongoose = require("mongoose")
const app = express();
const cors = require('cors')
var session = require('express-session');
require("dotenv").config()
const db_url = process.env.MONGO_URL
app.use(session({
cookie: {
httpOnly: true
},
rolling: true,
resave: true,
saveUninitialized: true,
secret: '~~~~~'
}));
app.set('view engine','ejs');
app.use(express.static('public'));
//app.use(express.json());
app.use(cors())
app.use('/', router)
app.use('/', account)
async function startApp(){
try{
await mongoose.connect(db_url, {
useUnifiedTopology: true,
useNewUrlParser:true
})
app.listen(4242, () => {console.log("server is working")})
} catch(e) {
console.log("some error appearead" + e)
}
}
startApp()
Normally when you see this error, it means that, either the HTTP request body Stripe sent to your webhook handler has been altered in some way or You may not be using the correct webhook secret.
The most likely reason it is throwing an exception is because your router is parsing body as JSON with router.use(express.json()). constructEvent requires the raw, unparsed body you receive from the request to verify the signature. To verify you have the raw body you can print it out and see if you get something like <Buffer 28 72 10..>
You can tell your router to keep the request body raw by setting something like this on your route router.use('/webhook', express.raw({type: "*/*"}))
I found the solution for my problem.
What i added is
app.use( "/webhook",express.raw({ type: "*/*" }))
in my server.js file.
I have a documents router which has router.post('/mine', [auth, uploadFile], async (req, res) => { ... }) route handler. The actual implementation of this route handler is below.
documents.js router
const createError = require('./../helpers/createError');
const auth = require('./../middlewares/auth');
const uploadFile = require('./../middlewares/uploadFile');
const express = require('express');
const router = express.Router();
router.post('/mine', [auth, uploadFile], async (req, res) => {
try {
let user = await User.findById(req.user._id);
let leftDiskSpace = await user.leftDiskSpace();
if(leftDiskSpace < 0) {
await accessAndRemoveFile(req.file.path);
res.status(403).send(createError('Your plan\'s disk space is exceeded.', 403));
} else {
let document = new Document({
filename: req.file.filename,
path: `/uploads/${req.user.username}/${req.file.filename}`,
size: req.file.size
});
document = await document.save();
user.documents.push(document._id);
user = await user.save();
res.send(document);
}
} catch(ex) {
res.status(500).send(createError(ex.message, 500));
}
});
module.exports = router;
I'm currently writing integration tests using Jest and Supertest. My current documents.test.js test file is below:
documents.test.js test file
const request = require('supertest');
const { Document } = require('../../../models/document');
const { User } = require('../../../models/user');
const fs = require('fs');
const path = require('path');
let server;
describe('/api/documents', () => {
beforeEach(() => { server = require('../../../bin/www'); });
afterEach(async () => {
let pathToTestFolder = path.join(process.cwd(), config.get('diskStorage.destination'), 'user');
// Remove test uploads folder for next tests
await fs.promises.access(pathToTestFolder)
.then(() => fs.promises.rm(pathToTestFolder, { recursive: true }))
.catch((err) => { return; });
// Remove all users and documents written in test database
await User.deleteMany({});
await Document.deleteMany({});
server.close();
});
describe('POST /mine', () => {
it('should call user.leftDiskSpace method once', async () => {
let user = new User({
username: 'user',
password: '1234'
});
user = await user.save();
let token = user.generateAuthToken();
let file = path.join(process.cwd(), 'tests', 'integration', 'files', 'test.json');
let documentsRouter = require('../../../routes/documents');
let errorToThrow = new Error('An error occured...');
user.leftDiskSpace = jest.fn().mockRejectedValue(errorToThrow);
let mockReq = { user: user };
let mockRes = {};
documentsRouter.post = jest.fn();
documentsRouter.post.mockImplementation((path, callback) => {
if(path === '/mine') {
console.warn('called');
callback(mockReq, mockRes);
}
});
const res = await request(server)
.post('/api/documents/mine')
.set('x-auth-token', token)
.attach('document', file);
expect(documentsRouter.post).toHaveBeenCalled();
expect(user.leftDiskSpace).toHaveBeenCalled();
});
});
});
I create mock post router handler for documents.js router. As you can see from mockImplementation for this route handler, it checks if the path is equal to '/mine' (which is my supertest endpoint), then calls console.warn('called'); and callback. When I run this test file, I can not see any yellow warning message with body 'called'. And also when POST request endpoint /api/documents/mine the server doesn't trigger my mock function documentsRouter.post. It has never been called. So I think the server's documents router is not getting replaced with my mock post route handler. It still uses original post route handler to respond my POST request. What should I do to test if my mock documentsRouter.post function have been called?
Note that my User model has a custom method for checking left disk space of user. I also tried to mock that mongoose custom method but It also doesn't work.
I am trying to build a small website. In that i using React for frontend, Nodejs for backend, and some third party api. Here my idea is, first to post the form data to nodejs. And from then i accepting that data in node and need to call an external api. For this purpose i am using axios. After receiving values from my api i have to send that value back to react application. And when i run my code in postman, the output is {}. I think that i am not getting values from my api but dont know how to resolve this. And i am new to these technologies. Someone pls help me to sort out this problem. Thanking you in advance. Here is my what i have tried so far.
const express = require('express');
const axios = require('axios');
const router = express.Router();
const request = require('request');
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended : false}));
router.get('/', (req, res) => {
res.send(" Express Homepage is running...");
});
async function callApi(emailid, pswd) {
return axios({
method:'post',
url:'http://51.X.X/api/login',
data: {
"email": `${emailid}`,
"password": `${pswd}`
},
headers: {'Content-Type': 'application/json' }
})};
callApi().then(function(response){
return response.data;
})
.catch(function(error){
console.log(error);
})
app.post('/api/login', (req, res) => {
let emailid = String(req.body.email);
let pswd = String(req.body.password);
const data = callApi(emailid, pswd);
if(data) {
res.send(data);
}else {
res.json({msg : " Response data not recieved.."})
}
});
use async/await syntax to handle asynchronous calls
app.post('/api/login', async (req, res) => {
let emailid = String(req.body.email);
let pswd = String(req.body.password);
const data = await callApi(emailid, pswd);
if(data) {
res.send(data);
}else {
res.json({msg : " Response data not recieved.."})
}
});
The problem is you are not waiting for async call to finish.
use async-await as mentioned in official doc https://www.npmjs.com/package/axios
function callAPI(){
const response = await axios({
method:'post',
url:'http://51.X.X/api/login',
data: {
"email": `${emailid}`,
"password": `${pswd}`
},
headers: {'Content-Type': 'application/json' }
})};
return response
}
app.post('/api/login', async (req, res) => {
let emailid = String(req.body.email);
let pswd = String(req.body.password);
//add try catch to catch exception
const data = await callApi(emailid, pswd);
if(data) {
//check for response from axios in official doc and send what data you
want to send
res.send(data);
}else {
res.json({msg : " Response data not recieved.."})
}
});
I am testing an api with all http 500 errors.
Here I try to use sinon.stub to test on a failing server and get a 500 error, but I get a timeOut async callback, or if I use my app a successfull 200 response statusCode as if sinon.stub has no effect. I must miss something and I am stucked...
would you see a horrifying error below ?
thanks for your precious help
process.env.NODE_ENV = "test";
const app = require("../../app");
const request = require("supertest");
const sinon = require("sinon");
// /************************** */
const usersRoute = require("../../routes/Users");
const express = require("express");
const initUsers = () => {
const app = express();
app.use(usersRoute);
return app;
};
describe("all 5xx errors tested with stub", function () {
it("should return a 500 when an error is encountered", async (done) => {
let secondApp;
sinon.stub(usersRoute, "post").throws(
new Error({
response: { status: 500, data: { message: "failed" } },
})
);
secondApp = initUsers(); //==========> Timeout Async Callback
//secondApp = require("../../app"); //==============> gives a 200 instead of 500
const fiveHundredError = await request(secondApp)
.post("/users/oauth?grant_type=client_credentials")
.send({
username: "digitalAccount",
password: "clientSecret",
});
expect(fiveHundredError.statusCode).toBe(500);
//sinon.restore();
done();
});
});
app is using express.Router to get users route :
const express = require("express");
const router = express.Router();
const axios = require("axios");
router.post("/users/oauth", async (req, res) => {
//if (all missing parts)
//else {
try {
if (req.fields) {
const response = await axios.post(
`${base_url}oauth/token?grant_type=${req.query.grant_type}`,
{},
{
auth: {
username: req.fields.username,
password: req.fields.password,
},
}
);
res.json(response.data);
}
} catch (error) {
return res.status(error.response.status).json(error.response.data);
}
}
});
module.exports = router;
See server.js :
const app = require("./app");
const port = process.env.PORT || 5000;
app.listen(port, () => console.log(`server starting on port ${port}!`));
and app.js :
// private environment:
require("dotenv").config();
const express = require("express");
const formidableMiddleware = require("express-formidable");
const cors = require("cors");
const app = express();
app.use(formidableMiddleware());
app.use(cors());
const usersRoute = require("./routes/Users");
app.use(usersRoute);
app.get("/", (req, res) => {
res.status(200).send("Welcome to Spark Admin back end!");
});
app.all("*", (req, res) => {
return res.status(404).json({ error: "Web url not found" });
});
module.exports = app;
I finally opted for 'nock' and deleted 'sinon'
const nock = require("nock");
const axios = require("axios");
describe("POST login: all 5xx errors tested with nock", function () {
it("should return a 500 when an error is encountered", async (done) => {
const scope = nock("http://localhost:5000")
.post(
"/users/oauth",
{},
{
username: "blibli",
password: "blabla",
}
)
.reply(500, {
response: {
statusCode: 500,
body: { error: "AN ERROR OCCURED" },
},
});
try {
await axios.post(
"http://localhost:5000/users/oauth",
{},
{
username: "blibli",
password: "blabla",
}
);
} catch (e) {
expect(e.response.status).toBe(500);
}
done();
});
});