Test error : Status -> Expected: 422 Received: undefined - node.js

I am trying to test the following Typescript code:
import express, { Request, Response } from "express";
import { validate } from "./validator";
import pgp from "pg-promise";
const app = express();
app.use(express.json());
app.post("/checkout", async function (req: Request, res: Response) {
const connection = pgp()("postgres://postgres:dev123#localhost:5432/tdd-app");
try {
const isValid = validate(req.body.cpf);
if (!isValid) throw new Error("Invalid cpf");
const output: Output = {
total: 0,
};
const items: number[] = [];
if (req.body.items) {
for (const item of req.body.items) {
if (item.quantity <= 0) throw new Error("Invalid quantity");
if (items.includes(item.idProduct)) throw new Error("Duplicated item");
const [productData] = await connection.query(
"SELECT * FROM cccat10.product WHERE id_product = $1",
item.idProduct
);
output.total += parseFloat(productData.price) * item.quantity;
items.push(item.idProduct);
}
}
if (req.body.coupon) {
const [couponData] = await connection.query(
"SELECT * FROM cccat10.coupon WHERE code = $1",
[req.body.coupon]
);
if (couponData.expire_date.getTime() >= new Date().getTime()) {
const percentage = parseFloat(couponData.percentage);
output.total -= (output.total * percentage) / 100;
}
}
res.json(output);
} catch (e: any) {
res.status(422).json({
message: e.message,
});
} finally {
await connection.$pool.end();
}
});
type Output = {
total: number;
message?: string;
};
app.listen(3000);
And my test are this:
test("Shouldnt create an order with duplicated item", async function () {
const input = {
cpf: "684.053.160-00",
items: [
{ idProduct: 1, quantity: 1 },
{ idProduct: 1, quantity: 1 },
],
};
const response = await axios.post("http://localhost:3000/checkout", input);
const output = response.data;
expect(output.status).toBe(422);
expect(output.message).toBe("Duplicated item");
});
When I put a console.log into the catch of the try catch code, you can confirm that the test is falling into the catch, but for some reason the status is not being sent, only the message.
If I remove the line
expect(output.status).toBe(422);
inside the test, it will be work.
The problem is with the output.status == undefined inside this specific test case.
I have other tests that can receive the status from the code:
res.status(422).json({
message: e.message,
});
like the test:
test("Shouldnt accept an order with an invalid cpf ", async function () {
const input = {
cpf: "406.302.170-27",
};
const response = await axios.post("http://localhost:3000/checkout", input);
const output = response.data;
expect(response.status).toBe(422);
expect(output.message).toBe("Invalid cpf");
});
Can someone help me to fix this status code on the test "Shouldn't create an order with duplicated item" ?

Related

Path `comment` is required. MERN stack

I don't understand why I get this error. This is my controller:
export const createProductReview = async (req, res) => {
const { rating, comment } = req.body;
const product = await Product.findById(req.params.id);
if (product) {
const alreadyReviewed = product.reviews.find(
r => r.user.toString() === req.user.userId.toString()
);
if (alreadyReviewed) {
throw new NotFoundError('Product already reviewed');
}
const review = {
user: req.user.userId,
name: req.user.username,
rating: Number(rating),
comment,
};
product.reviews.push(review);
product.numOfReviews = product.reviews.length;
product.rating =
product.reviews.reduce((acc, item) => item.rating + acc, 0) /
product.reviews.length;
await product.save();
res.status(StatusCodes.OK).json({ message: 'Review added', review });
} else {
throw new NotFoundError('Product not found');
}
};
This is mine productPage where i dispatch addProductReview and passing product id from params and review object:
const [rating, setRating] = useState(0);
const [comment, setComment] = useState('');
const submitHandler = e => {
e.preventDefault();
dispatch(
addProductReview(id, {
rating,
comment,
})
);
};
And this is my productSlice:
export const addProductReview = createAsyncThunk(
'product/review',
async (id, { review }, thunkAPI) => {
try {
const { data } = await axios.post(
`/api/v1/products/${id}/reviews`,
review
);
return data;
} catch (error) {
const message = error.response.data.msg;
return thunkAPI.rejectWithValue(message);
}
}
);
I have no clue why i got error Path comment is required. i pass review object to route.
The issue is with the parameters used in your Thunk payloadCreator. From the documentation...
The payloadCreator function will be called with two arguments:
arg: a single value, containing the first parameter that was passed to the thunk action creator when it was dispatched. This is useful for passing in values like item IDs that may be needed as part of the request. If you need to pass in multiple values, pass them together in an object when you dispatch the thunk, like dispatch(fetchUsers({status: 'active', sortBy: 'name'})).
thunkAPI: an object containing all of the parameters that are normally passed to a Redux thunk function, as well as additional options
Your payloadCreator has three arguments which is incorrect.
Try this instead
export const addProductReview = createAsyncThunk(
'product/review',
async ({ id, ...review }, thunkAPI) => {
try {
const { data } = await axios.post(
`/api/v1/products/${id}/reviews`,
review
);
return data;
} catch (error) {
const message = error.response.data.msg;
return thunkAPI.rejectWithValue(message);
}
}
);
and dispatch it like this
dispatch(addProductReview({ id, rating, comment }));

How to create blockUserTime correctly?

I have security.controller, security.service and VerificationEntity.
So, in security.controller I have checkVerificationCode method in which I am trying to block the user if he has exceeded the allowed number of inputs of the wrong code and create the timestamp of the last failed attempt, and then in security.service I'm saving this blockedTime into the blockedTime column in VerificationEntity.
Problem is, when I'm trying to check code again during this block time, blockedTime is updating again. How can I prevent it and make blockedTime static, in order to further compare it with the current timestamp.
security.controller:
public checkVerificationCode = async (req: Request, res: Response) => {
try {
const { mobilePhone, verificationCode, id } = req.body;
const dataToCheck = await this.securityService.checkCode(mobilePhone);
if (verificationCode !== dataToCheck.verificationCode || id !== dataToCheck.id) {
const newTries = dataToCheck.tries + 1;
const triesLeft = +process.env.MAX_CODE_TRIES - +newTries;
if (triesLeft <= 0) {
const blockedTime = await this.securityService.updateBlockTime(mobilePhone, id);
if (timeDiffInMinutes(blockedTime) <= +process.env.USER_BLOCK_EXPIRATION) {
return res.status(StatusCodes.BAD_REQUEST).json({ blockSeconds: `You still blocked` });
}
return res
.status(StatusCodes.BAD_REQUEST)
.json({ blockSeconds: `You was blocked, you can try again after 10 minutes.` });
}
return res.status(StatusCodes.BAD_REQUEST).json({ msg: 'Verification code is invalid' });
}
if (timeDiffInMinutes(dataToCheck.updatedAt) >= +process.env.CODE_EXPIRATION_TIME) {
return res.status(StatusCodes.BAD_REQUEST).json({ msg: 'Verification code expired!' });
}
await this.securityService.resetTries(mobilePhone, id);
return res.status(StatusCodes.OK).json({ msg: 'Success!' });
} catch (error) {
return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ msg: error.message });
}
};
security.service:
public async updateBlockTime(mobilePhone: string, id: string) {
const { blockedTime } = await getRepository(VerificationEntity).findOne({ mobilePhone: mobilePhone as string, id });
const timestamp = Date.now();
const blockedTimestamp = new Date(timestamp);
await getRepository(VerificationEntity)
.createQueryBuilder()
.update(VerificationEntity)
.set({ blockedTime: blockedTimestamp })
.where({ mobilePhone: mobilePhone as string, id: id as string })
.execute();
return blockedTime;
}

How to handle async/await and promises in node.js confusion

I have checked tons of similiar problems all data, I am so confused. This is returning the same result whether the phone number is in the DB or not. Other versions with async and promises have been crashing the app. Please help
How can I get a value from Firebase realtime DB using the admin SDK and use that value to determine the output. Also it seems abnormally slow at times for some reason
auth.controller.js
const validate = require('../utils/validate');
const { getItem, setItem } = require('../utils/firebase');
const { generatePin } = require('../utils/auth');
const argon2 = require('argon2');
const { error } = require('console');
const { networkInterfaces } = require('os');
const exp = require('constants');
exports.connect = async function (req, res) {
const { phone } = req.body;
if (!phone) {
res.status(400).json({ message: 'Error, phone number is invalid or not registered'})
} else {
if(!validate.phoneNumber(phone)) {
res.status(400).json({ message: 'Error, phone number is invalid or not registered' })
} else {
const result = await generatePin();
item = await setItem('clients', 'phone', phone, 'hash', result.hash)
console.log(item)
if(!item) {
res.status(200).json({ message: 'Success', pin: result.pin})
} else {
res.status(400).json({ message: 'Error, phone number is invalid or not registered' })
}
var currentTime = Date.now();
var expiryTime = currentTime + 60;
setItem('clients', 'phone', phone, 'hashExpiry', expiryTime)
}
}
firebase.js
const { on } = require("events");
var admin = require("firebase-admin");
// Import Admin SDK
const { getDatabase } = require('firebase-admin/database');
const { type } = require("os");
var serviceAccount = require("../fedex-3a42e-firebase-adminsdk-r96f1-7249eaf87b.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://fedex-3a42e-default-rtdb.firebaseio.com/"
});
const db = getDatabase()
function getItem(itemRef, child, val) {
const dbRef = db.ref(itemRef);
dbRef.orderByChild(child).equalTo(val).on("value", (data) => {
return data.val();
});
}
async function setItem(itemRef, child, val, key, pushedVal) {
const value = await getItem(itemRef, child, val);
console.log('val', value)
if(value) {
finallySetItem(value, itemRef, pushedVal);
return true
} else {
return false
}
}
function finallySetItem(data, itemRef, pushedVal) {
console.log(data)
if(data) {
var itemKey = Object.keys(data)[0];
console.log(itemKey)
const dbRef = db.ref(itemRef + '/' + itemKey + '/' + key);
dbRef.set(pushedVal);
}
}
module.exports = { getItem, setItem }
This won't work:
function getItem(itemRef, child, val) {
const dbRef = db.ref(itemRef);
dbRef.orderByChild(child).equalTo(val).on("value", (data) => {
return data.val();
});
}
You're passing your callback to on(), and on() won't do anything with the value you return in there.
More likely you want to use once() and return the value asynchronously from there:
async function getItem(itemRef, child, val) {
const dbRef = db.ref(itemRef);
const data = await dbRef.orderByChild(child).equalTo(val).once("value");
return data.val();
}

What is causing an Unhandled Promise Rejection: undefined is not an object (evalutating '_context.t0.response.data')?

I keep getting an Unhandled Promise Rejection: TypeError: undefined is not an object (evaluating '_context.t0.response.data'). After doing some digging, it seems like my error is coming from this part of my code. It's the authUser function specifically that seems to be causing the problem
import { addError, removeError } from './error';
import { SET_CURRENT_USER } from '../actionTypes';
import api from '../../services/api';
export const setCurrentUser = user => ({
type: SET_CURRENT_USER,
user
});
export const setToken = token => {
api.setToken(token);
};
export const authUser = (path, data) =>
{
return async dispatch => {
try {
const {token, ...user} = await api.call('post', `auth/${path}`, data);
AsyncStorage.setItem('jwtToken', token);
api.setToken(token);
dispatch(setCurrentUser(user));
dispatch(removeError());
} catch (err) {
const error = err.response.data;
dispatch(addError(error.message));
}
}
};
actionTypes.js
export const SET_CURRENT_USER = 'SET_CURRENT_USER'
api.js
import axios from 'axios';
const host = 'http://localhost:4000/api';
export const setToken = token => {
if (token) {
axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
} else {
delete axios.defaults.headers.common['Authorization'];
}
};
export const call = async (method, path, data) => {
const response = await axios[method](`${host}/${path}`, data);
return response.data;
};
export default { setToken, call };
error.js
import {ADD_ERROR, REMOVE_ERROR} from '../actionTypes';
export const addError = error => ({
type: ADD_ERROR,
error
});
export const removeError = () => ({
type: REMOVE_ERROR
});
Error:
Possible Unhandled Promise Rejection (id: 4):
TypeError: undefined is not an object (evaluating '_context.t0.response.data')
_callee$#http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:107239:43
tryCatch#http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:26927:23
invoke#http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:27103:32
tryCatch#http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:26927:23
invoke#http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:27003:30
http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:27015:21
tryCallOne#http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:28865:16
http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:28966:27
_callTimer#http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:32405:17
_callImmediatesPass#http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:32441:19
callImmediates#http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:32659:33
callImmediates#[native code]
__callImmediates#http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:2719:35
http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:2505:34
__guard#http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:2702:15
flushedQueue#http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:2504:21
flushedQueue#[native code]
callFunctionReturnFlushedQueue#[native code]
Also if it helps, I followed the localhost link and the function that is breaking there, tho I did not write this and cannot change it.
var authUser = function authUser(path, data) {
return function _callee(dispatch) {
var _await$api$call, token, user, error;
return _regenerator.default.async(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.prev = 0;
_context.next = 3;
return _regenerator.default.awrap(_api.default.call('post', "auth/" + path, data));
case 3:
_await$api$call = _context.sent;
token = _await$api$call.token;
user = (0, _objectWithoutProperties2.default)(_await$api$call, ["token"]);
AsyncStorage.setItem('jwtToken', token);
_api.default.setToken(token);
dispatch(setCurrentUser(user));
dispatch((0, _error.removeError)());
_context.next = 16;
break;
case 12:
_context.prev = 12;
_context.t0 = _context["catch"](0);
error = _context.t0.response.data;
dispatch((0, _error.addError)(error.message));
case 16:
case "end":
return _context.stop();
}
}
}, null, null, [[0, 12]], Promise);
};
};
The error is coming from the catch block of the authUser function:
export const authUser = (path, data) =>
{
return async dispatch => {
try {
// ... Other existing codes
} catch (err) {
const error = err.response.data;
dispatch(addError(error.message));
}
}
};
For errors thrown by axios, err.response won't always be available, at times when there is no response from the server or there was a problem making the request in the first place, err.response would be undefined. In such cases, you need to handle other sources of errors. You should update the catch logic to handle the possible error cases, the code should be something like this:
catch (err) {
if (err.response) {
// There is an error response from the server
// You can anticipate error.response.data here
const error = err.response.data;
dispatch(addError(error.message));
} else if (err.request) {
// The request was made but no response was received
// Error details are stored in err.reqeust
console.log(err.request);
} else {
// Some other errors
console.log('Error', err.message);
}
}
More details on handling axios error here.

koa2+koa-router+mysql keep returning 'Not Found'

Background
I am using koa2 with some middlewares to build a basic api framework. But when I use "ctx.body" to send response in my router, the client side always receive "Not Found"
My code
./app.js
const Koa = require('koa');
const app = new Koa();
const config = require('./config');
//Middlewares
const loggerAsync = require('./middleware/logger-async')
const bodyParser = require('koa-bodyparser')
const jsonp = require('koa-jsonp')
app.use(loggerAsync())
app.use(bodyParser())
app.use(jsonp());
//Router
const gateway = require('./router/gateway')
app.use(gateway.routes(), gateway.allowedMethods());
app.use(async(ctx, next) => {
await next();
ctx.response.body = {
success: false,
code: config.code_system,
message: 'wrong path'
}
});
app.listen(3000);
./router/gateway.js
/**
* Created by Administrator on 2017/4/11.
*/
const Router = require('koa-router');
const gateway = new Router();
const df = require('../db/data-fetcher');
const config = require('../config');
const moment = require('moment');
const log4js = require('log4js');
// log4js.configure({
// appenders: { cheese: { type: 'file', filename: 'cheese.log' } },
// categories: { default: { appenders: ['cheese'], level: 'error' } }
// });
const logger = log4js.getLogger('cheese');
logger.setLevel('ERROR');
gateway.get('/gateway', async(ctx, next) => {
let time = ctx.query.time;
if (!time) {
ctx.body = {
success: false,
code: config.code_system,
message: 'Please input running times'
}
} else {
try {
let r = await df(`insert into gateway (g_time, g_result, g_date) values (${time}, '',now())`);
return ctx.body = {
success: true,
code: config.code_success
}
} catch (error) {
logger.error(error.message);
}
}
});
module.exports = gateway;
Then a db wrapper(mysql)
./db/async-db.js
const mysql = require('mysql');
const config = require('../config');
const pool = mysql.createPool({
host: config.database.HOST,
user: config.database.USERNAME,
password: config.database.PASSWORD,
database: config.database.DATABASE
})
let query = (sql, values) => {
return new Promise((resolve, reject) => {
pool.getConnection(function (err, connection) {
if (err) {
reject(err)
} else {
connection.query(sql, values, (err, rows) => {
if (err) {
reject(err)
} else {
resolve(rows)
}
connection.release()
})
}
})
})
}
module.exports = query
./db/data-fetcher.js
const query = require('./async-db')
async function performQuery(sql) {
let dataList = await query(sql)
return dataList
}
module.exports = performQuery;
My running result
When I launch server on port 3000 then accesss via http://localhost:3000/gateway?time=5, it always returns "Not found". But as I can see I have already used
return ctx.body = {
success: true,
code: config.code_success
}
to send response. I debugged and found that the database processing was done well, the new data was inserted well.
when I remove that db inserting line, it works well and returns success info.
let r = await df(`insert into gateway (g_time, g_result, g_date) values (${time}, '',now())`);
Is there anything wrong?
Thanks a lot!
Update 2017/04/27
Now I have found the problem. It's due to my custom middleware
const loggerAsync = require('./middleware/logger-async')
Code are like following -
function log( ctx ) {
console.log( ctx.method, ctx.header.host + ctx.url )
}
module.exports = function () {
return function ( ctx, next ) {
return new Promise( ( resolve, reject ) => {
// 执行中间件的操作
log( ctx )
resolve()
return next()
}).catch(( err ) => {
return next()
})
}
}
I changed it to async/await way then everything is working well.
Could anyone please tell me what's wrong with this middleware?
I guess, your problem is the ./db/data-fetcher.js function. When you are calling
let r = await df(`insert ....`)
your df - function should return a promise.
So try to rewrite your ./db/data-fetcher.js like this (not tested):
const query = require('./async-db')
function performQuery(sql) {
return new Promise((resolve, reject) => {
query(sql).then(
result => {
resolve(result)
}
)
}
}
module.exports = performQuery;
Hope that helps.
correct middleware:
function log( ctx ) {
console.log( ctx.method, ctx.header.host + ctx.url )
}
module.exports = function () {
return function ( ctx, next ) {
log( ctx );
return next()
}
}
reason: when resolve involved; promise chain was completed; response has been sent to client. although middleware remained will involved, but response has gone!
try to understand It seems that if you want to use a common function as middleware, you have to return the next function
nodejs(koa):Can't set headers after they are sent

Resources