Added in a pre-requisite for the endpoint to validate that the client information being passed is legit or it will throw an error. The clientProfileValidation.clientProfileValidation method receives the request object and returns a profile object that gets attached to the request.pre.
When trying to update my route unit test, I get the below error.
Unhandled rejection occurred. One of your test may have failed silently.
TypeError: Cannot read properties of undefined (reading 'routes')
This is a nodejs api using HAPI framework. When I remove the pre from the route, the test passes. I attempted to mock the clientProfileValidation method but its not working as expected.
Route
const drayageRampRecommendation = {
method: 'POST',
path: '/endpoint',
handler: async (request, h) => {
try {
const resp = await rampHandler.rampRecommendation(request);
return h.response(resp).code(201);
} catch (error) {
return handleError(error).toBoom();
}
},
config: {
pre: [
{
method: clientProfileValidation.clientProfileValidation,
assign: 'profile'
}
],
payload: {
allow: ['application/json', 'application/*+json']
}
}
};
Unit Test:
Using the Tape and Test Double Libraries for testing
test('drayage/recommend-ramps route: should return 201 when successfully processed', async (t) => {
beforeEachRampRecommendation();
const options = {
method: 'POST',
url: '/endpoint',
payload: recommendRampFixture,
headers: { authorization: 'Bearer 123' },
auth: {
credentials: { user: 'test', clientId: 'testClient' },
strategy: 'default'
}
};
const testProfile = {
_id: 'testId',
auth0ClientName: 'test client'
};
td.when(clientProfileValidation.clientProfileValidation(), {
ignoreExtraArgs: true
}).thenReturn(testProfile);
td.when(recommendRampHandler.rampRecommendation(), {
ignoreExtraArgs: true
}).thenReturn('');
const server = await buildServer(routes);
const response = await server.inject(options);
t.equal(response.statusCode, 201, 'Should return 201 status code');
td.reset();
t.end();
});
Related
I have this error in the console:
react_devtools_backend.js:4012 A non-serializable value was detected in an action, in the path: `meta.arg.config.adapter`. Value: ƒ xhrAdapter(config) {
return new Promise(function dispatchXhrRequest(resolve, reject) {
var requestData = config.data;
var requestHeaders = config.headers;
var responseType = config.resp…
Take a look at the logic that dispatched this action:
{type: '[GET] dataGrid/runTask/rejected', payload: undefined, meta: {…}, error: {…}}
error
:
{name: 'Error', message: 'Request failed with status code 400', stack: 'Error: Request failed with status code 400\n at …tp://localhost:3000/static/js/bundle.js:208909:7)'}
meta
:
{arg: {…}, requestId: 'XNHo_e78g2enuXNwLe_pQ', rejectedWithValue: false, requestStatus: 'rejected', aborted: false, …}
payload
:
undefined
type
:
"[GET] dataGrid/runTask/rejected"
[[Prototype]]
:
Object
can anyone tell me where is the problem because the backend works well.
and the part of code that is mentioned is:
const requestConfig = {
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
};
export const getReportsList = createAsyncThunk(
'\[GET\], dataGrid/reportsList',
async (\_) = \ > {
const response = await getData(ENDPOINTS.all_reports)
return response.data
}
)
I found out that the problem is related to the headers.
I call 2 times the headers so in headers I had the another one!
simply after that the error solved.
You could Modify Your Code like this.
const requestConfig = {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
}
export const getReportsList = createAsyncThunk(
'[GET], dataGrid/reportsList',
async (_, {getData, ENDPOINTS}) => {
const response = await getData(ENDPOINTS.all_reports, requestConfig)
return response.data
}
)
Note : The getData function and ENDPOINTS object need to be imported and provided to the createAsyncThunk middleware as dependencies in order to use them within the thunk.
I am following the stripe docs about validating webhooks and despite I do everything as they do i keep getting 400 error. BTW in the docs here https://stripe.com/docs/webhooks/signatures they don't return from the catch blok but here https://stripe.com/docs/webhooks/quickstart they do, so I assyme that the correct option is to return from it? And back to my main problem I have no idea what am I missing here this is my code:
import { NextApiHandler } from "next";
import { Stripe } from "stripe";
import { apolloClient } from "../../graphql/apolloClient";
import {
UpdateOrderDocument,
UpdateOrderMutation,
UpdateOrderMutationVariables,
} from "../../generated/graphql";
import type { StripeWebhookEvents } from "../../stripeEvents";
const stripeWebhookHandler: NextApiHandler = (req, res) => {
const webhookSignature = req.headers["stripe-signature"];
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
const stripeSecret = process.env.STRIPE_SECRET_KEY;
if (!stripeSecret || !webhookSignature || !webhookSecret) {
return res.status(500).json({ error: "Stripe credential not provided" });
}
const stripe = new Stripe(stripeSecret, { apiVersion: "2020-08-27" });
let event;
try {
event = stripe.webhooks.constructEvent(
req.body,
webhookSignature,
webhookSecret
) as StripeWebhookEvents;
} catch (err: unknown) {
return res
.status(400)
.send(`Webhook Error: ${err instanceof Error && err.message}`);
}
switch (event.type) {
case "charge.succeeded":
apolloClient.mutate<UpdateOrderMutation, UpdateOrderMutationVariables>({
mutation: UpdateOrderDocument,
variables: {
id: {
id: event.data.object.metadata.cartId,
},
data: {
stripeCheckoutId: event.data.object.id,
email: event.data.object.receipt_email,
},
},
});
break;
}
res.status(204).end();
};
export default stripeWebhookHandler;
and I thought that maybe next has maybe different shape of req.headers or req.body and I am not sure abouut req body, headers seem to be in tact however. At least the signature seems to be extracted correctly. Dhose are test data of course:
{
webhookSignature: 't=1658224240,v1=a3f574b3e6c3a02eb86308e5e43f3d0a96664098ee5dd58859fc94e96693fc50,v0=ef29de87716d9d318d6ad960f028fd5960618c853ff686bd44e261aaa2368f3b',
webhookSecret: 'whsec_0d8a54d09bf221f7c5c77ca7a3fca4b988ccd9e49d8a31d7c91f854025503fe4',
stripeSecret: 'sk_test_51Kuvp4KsMpogemXo9vUcgihi1vK4dlof76OL4EcYhmVgN8r81tl7r0rSsqWgOtXxYnZPJlo6S2KA0gFWZmyBQIbS00ABzicwum',
headers: {
host: 'localhost:3000',
'user-agent': 'Stripe/1.0 (+https://stripe.com/docs/webhooks)',
'content-length': '2818',
accept: '*/*; q=0.5, application/xml',
'cache-control': 'no-cache',
'content-type': 'application/json; charset=utf-8',
'stripe-signature': 't=1658224240,v1=a3f574b3e6c3a02eb86308e5e43f3d0a96664098ee5dd58859fc94e96693fc50,v0=ef29de87716d9d318d6ad960f028fd5960618c853ff686bd44e261aaa2368f3b',
'accept-encoding': 'gzip'
}
is there something I do incorrectly here? Thanks a lot
I have set up JWT to be set in localstorage whenever someone logins or registers. And it works, I can see the token in localstorage. But when I set the token in the headers with axios, node.js in the backend can`t find the token. Like it does not exists. I have checked it in the front end, I get logs of the token in the headers. And also when I request from postman it works. Here is the code.
setAuthToken function = {
const instance = axios.create({
baseURL: "https://localhost:5000",
});
if (token) {
instance.defaults.headers.common["x-auth-token"] = `${token}`;
console.log(instance.defaults.headers.common["x-auth-token"]);
} else {
delete instance.defaults.headers.common["x-auth-token"];
}
}
const loadUser = async () => {
if (localStorage.token) setAuthToken(localStorage.token);
console.log(localStorage.token);
try {
const res = await axios.get("/api/users");
console.log(res);
dispatch({ type: USER_LOADED, payload: res.data });
} catch (err) {
console.log(err.response.data.msg);
dispatch({ type: AUTH_ERROR });
}
The request comes to the await axios statement and goes to catch so error is in the request.
Here is the backend code
// Get current user
router.get("/", auth, async (req, res) => {
try {
const user = await User.findById(req.user.id);
res.status(200).json({ user });
} catch (err) {
console.log(err);
res.status(500).json({ msg: `Server Error` });
}
});
auth middleware function here = {
const token = req.headers["x-auth-token"];
console.log(token, "token in auth.js");
console.log(req.headers, "req.header");
if (!token) {
return res.status(401).json({ msg: `Access denied.` });
}
try {
const decoded = jwt.verify(token, config.get("jwtSecret"));
req.user = decoded.user;
next();
} catch (err) {
res.status(401).json({ msg: `Token is not valid` });
}
}
I`m new to backend develoment and axios. Can someone help me please. Thank you
Here are the console.logs
Logs
Logs
Little update, it looks like there is a problem with proxy, I am using my own backend api, and also movie data base api. So maybe thats why I cant set headers? Here are new logs:
config: Object { url: "/api/users", method: "get", timeout: 0, … }
data: "Proxy error: Could not proxy request /api/users from localhost:3000 to http://localhost:5000/ (ECONNREFUSED)."
headers: Object { connection: "keep-alive", date: "Wed, 05 May 2021 13:18:05 GMT", "keep-alive": "timeout=5", … }
request: XMLHttpRequest { readyState: 4, timeout: 0, withCredentials: false, … }
status: 500
statusText: "Internal Server Error
I think the issue is because you are setting you are setting up your instance wrongly
set up your instance in a new file config.js -
import Axios from 'axios';
const baseURL = "http://localhost:5000";
const axiosInstance = Axios.create({
baseURL: baseURL,
});
axiosInstance.interceptors.request.use(
function (config) {
const token = localStorage.getItem('token');
if (token) {
config.headers['Authorization'] = 'Bearer ' + token;
}
return config;
},
function (error) {
return Promise.reject(error);
}
);
export default axiosInstance;
now when making any api request instead of using axios use axiosInstance eg-
axiosInstance.get('/something').then(res => console.log(res)).catch(err => console.log(err))
I'm having trouble sending a photo, in a PUT route, be it with Axios or Fetch, anyway, my Nodejs backend is configured and the whole upload process works normal testing by Insonmia, but in React Native It does not work properly.
BACKEND - CONTROLLER FOR UPDATING USER DATA
async updateUserData(req: Request, res: Response, next: NextFunction) {
const { id } = req.params;
const { username, status } = req.body as IBodyData;
try {
const userExists = await knex('tb_user')
.where('id', Number(id))
.first();
if (!userExists) {
return res.status(400).json({ error: 'User does not exist.' });
}
const serializedUserInfo = {
photo: `${process.env.BASE_URL}/uploads/${req.file.filename}`,
username,
status,
};
const updateInfo = await knex('tb_user')
.update(serializedUserInfo)
.where('id', Number(id));
if (!updateInfo) {
return res.status(400).json({ error: 'Error updating data.' });
}
return res.json(updateInfo);
} catch(err) {
return res.status(500).json({ error: 'Error on updating user.' });
}
}
BACKEND - ROUTE
routes.put(
'/updateuser/:id',
multer(multerConfig).single('userphoto'),
UserController.updateUserData
);
CONFIG MULTER
export default {
fileFilter: (req: Request, file, cb) => {
const allowedMimes = [
'image/jpeg',
'image/jpg',
'image/pjpeg',
'image/png',
];
if (!allowedMimes.includes(file.mimetype)) {
return cb(new Error('Invalid file type.'));
}
return cb(null, true);
},
limits: {
fileSize: 2 * 1024 * 1024,
},
storage: multer.diskStorage({
destination: resolve(__dirname, '..', '..', 'uploads'),
filename: (req: Request, file, cb) => {
const filename = `${randomBytes(6).toString('hex')}-${file.originalname}`;
return cb(null, filename);
}
})
} as Options;
REACT NATIVE - FUNCTION FOR GET DATA OF PHOTO AND SET ON STATE
function handleUploadImage(image: IImagePickerResponse) {
if (image.error) {
return;
}
if (image.didCancel) {
return;
}
if (!image.uri) {
return;
}
setSelectedImage({
fileName: image.fileName,
fileSize: image.fileSize,
type: image.type,
uri: image.uri,
});
}
REACT NATIVE - FUNCTION FOR SEND DATA IN API
async function handleSaveChange() {
try {
const formData = new FormData();
formData.append('userphoto', {
uri:
Platform.OS === 'android'
? selectedImage?.uri
: selectedImage?.uri?.replace('file://', ''),
type: selectedImage.type,
name: selectedImage.fileName,
});
formData.append('username', usernameInput);
formData.append('status', statusInput);
console.log(formData);
await api.put(`/updateuser/${userData?.id}`, formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
});
// const updateUser = await fetch('http://192.168.2.8:3333/updateuser/1', {
// method: 'POST',
// headers: {
// 'Content-Type': 'multipart/form-data',
// },
// body: formData,
// })
// .then((response) => {
// response.json();
// })
// .then((response) => {
// console.log(response);
// })
// .catch((err) => console.log(err));
return Toast.success('Informações alteradas com sucesso.');
} catch (err) {
const {error} = err.response.data;
return Toast.error(error);
}
}
obs: A note to take into account is that in the formData.append ('userphoto', value) part if the value has sent a normal json object, the request is neither made nor the Network Error error even IP address being correct the port too, anyway, and if I use formData.append ('userphoto', JSON.stringfy(value)) the request is made normally but in the backend the photo arrives as undefined and the rest of fields are sent and received normal.
I really tried several things and I couldn't, I changed the type of the PUT method to POST without success, add 'Accept': 'application/json' without success, as I said, the normal sending by Insomnia is working.
Finally, here is the lib configuration I used to get the photo:
REACT NATIVE IMAGE PICKER
ImagePicker.showImagePicker(
{
title: 'Selecione uma imagem',
takePhotoButtonTitle: 'Tirar foto',
chooseFromLibraryButtonTitle: 'Escolher da galeria',
cancelButtonTitle: 'Cancelar',
noData: true,
// storageOptions: {
// skipBackup: true,
// path: 'images',
// cameraRoll: true,
// waitUntilSaved: true,
// },
},
handleUploadImage,
)
SOLVED
Apparently it was a problem with the version of React Native, I am currently using version 0.62.XX
To resolve, just comment line number 43 in the file:
android/app/src/debug/java/com/mobile/ReactNativeFlipper.java
The code is this:
builder.addNetworkInterceptor(new FlipperOkhttpInterceptor (networkFlipperPlugin));
How do I write the Joi schema for a file that I am sending as a response?
My route returns this return h.file(filename, { mode: 'attachment'}).code(201); and well, the content-dispostion response header is attachment; filename=entries.csv.
I can maybe check the object structure of the response that's going out but is there a way Joi provides a property to check for files in the response?
Here's the Github issue you might wanna track
I misunderstood the question - it was about validating response headers, not request ones.
Short answer: it cannot be done.
Long answer:
Based on hapijs 17.5.3 https://hapijs.com/api#-routeoptionsresponseoptions it seemed doable with a function:
server.route({
method: 'GET',
path: '/file',
options: {
handler: (request, h) => {
return h.file('foobar.csv', { mode: 'attachment'}).code(201);
},
response: {
schema: async (value, options) => {
console.log('validating response:', value);
}
}
}
});
But this approach doesn't work.
It's not supported by hapijs, you'll get an exception from line 151: https://github.com/hapijs/hapi/blob/76fcd7fa97747c92501b912d64db459d7172cb26/lib/validation.js
which is:
if (!response.isBoom &&
request.response.variety !== 'plain') {
throw Boom.badImplementation('Cannot validate non-object response');
}
here's how you can validate headers on requests:
'use strict';
const Joi = require('joi');
const ErrorHandler = require('../handlers/errorHandler');
const fileUploadValidator = {
config: {
validate: {
params: {
env: Joi.string().min(2).max(10).required()
},
query: {
id: Joi.number().integer().min(0).required()
},
headers: Joi.object({
'x-request-id': Joi.string().guid().required(),
'content-disposition': Joi.string().regex(/attachment;\s*filename=.+\.csv/gi).insensitive().required()
}).options({ allowUnknown: true }),
failAction: ErrorHandler.apply_genericHandler
}
}
};
module.exports = fileUploadValidator;
Route definition:
server.route({
method: 'POST',
path: '/{env}/v1/fileUpload',
handler: FileUploadHandler.apply,
options: FileUploadValidator.config
});
you may need to tweak it a bit. I've built it based on your question.