I'm trying to test that a call to a certain backend route eventually hits the correct controller function in an express app.
I'm attempting to do this using Vitest & supertest - but I'm struggling to mock the controller function.
**metafieldsControllers.js**
// #route POST /metafields/save
// #desc Saves a new value for an app-owned metafield
export const appMetafieldSave = async (req, res, next) => {
<!-- stuff here -->
res.status(201).json({ message: "Save successful!", savedValue });
}
// #route GET /metafields/:namespace/:key
// #desc Gets an app owned metafield by namespace and key
export const appMetafieldRetrieve = async (req, res, next) => {
<!-- stuff here -->
res.status(200).json(appInstallationData.metafield);
}
Things I have tried that haven't worked in my Vitest test file are...
***controllers.test.js***
import request from "supertest";
import { describe, expect, test, vi } from "vitest";
import { serve } from "../../../server/__tests__/serve";
import * as metafieldsControllers from "../metafieldsControllers.js";
describe("metafieldsControllers", async () => {
const { app } = await serve(process.cwd(), false);
// Mock verify-request
vi.mock(`${process.cwd()}/server/middleware/verify-request.js`, () => ({
default: vi.fn(() => (req, res, next) => {
next();
}),
}));
// Mock appMetafieldSave
const controllerSpy = vi.spyOn(metafieldsControllers, "appMetafieldSave");
controllerSpy.mockImplementation(() => {
console.log("I'm running");
});
test("Call to metafields/save route hits the controller", async () => {
const response = await request(app).post("/metafields/save");
console.log("response", response);
expect(controllerSpy).toHaveBeenCalledTimes(1);
});
});
^ This fails with AssertionError: expected "appMetafieldSave" to be called 1 times. Logging out the response shows that the request hit the original un-mocked controller.
***controllers.test.js***
import request from "supertest";
import { describe, expect, test, vi } from "vitest";
import { serve } from "../../../server/__tests__/serve";
describe("metafieldsControllers", async () => {
const { app } = await serve(process.cwd(), false);
// Mock verify-request
vi.mock(`${process.cwd()}/server/middleware/verify-request.js`, () => ({
default: vi.fn(() => (req, res, next) => {
next();
}),
}));
// Mock appMetafieldSave
vi.mock(
`${process.cwd()}/server/controllers/metafieldsControllers.js`,
() => {
return {
appMetafieldSave: vi.fn(() => (req, res, next) => {
res.status(201);
}),
appMetafieldRetrieve: vi.fn(() => (req, res, next) => {
res.status(200);
}),
};
}
);
test("Call to metafields/save route hits the controller", async () => {
const response = await request(app).post("/metafields/save");
console.log("response", response);
expect(response.status).toEqual(201);
});
});
^ This fails with a timeout.
Any ideas what I'm doing wrong?
Related
I would like to get the data from session variable (req.user.username) then use it for posting. I'm using passportjs as authentication. I'm using router. Here is my code:
router.use('/login', passport.authenticate("local-register", async (err, user, info) => {
if (err) {
return next('Error');
}
if (!user) {
return next('Error');
}
req.user = user;
return req.login(user, (error: Error) => {
if (error) {
return next('Error');
}
return req.session.save((erro: Error) => {
if (erro) {
return next('Error');
}
return next();
});
});
})(req, res, next);)
router.get('/', async (req, res) => {
console.log(req.user.username) // working just fine
});
router.post('/upload', async (req, res) => {
const uploaderName = req.user.username // I'm getting undefined
const upload = await database.query('INSERT INTO user WHERE username=$1', [uploaderName])
console.log(uploaderName);
})
So I finally found the answer to the question. For those who will encounter the problem in the future. You just add the session middleware AGAIN on the top of the routes. If your routes are separated to the main server file.
/src/routes/routes.ts -> add again the middleware on top.
const app = router();
app.use(sessions) // -> right here you need to add the middleware again to //access the req.user session variable
app.get('/', async (req, res) => {
console.log(req.user.username) // working just fine
});
app.post('/upload', async (req, res) => {
const uploaderName = req.user.username // I'm getting undefined
const upload = await database.query('INSERT INTO user WHERE username=$1', [uploaderName])
console.log(uploaderName);
})
app.use(
express.json({
verify: (req, res, buf) => {
req.rawBody = buf.toString();
},
}),
);
app.post('/webhook', async (req, res, next) => {
const sig = req.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(req.rawBody, sig, endpoint_secret);
} catch (err) {
return console.log(err)
}
if (event.type === 'invoice.payment_succeeded') {
//...
}
res.send();
});
I tried following this link but I kept getting express.raw is not a function error, I also tried this:
app.use((req, res, next) => {
if (req.originalUrl === '/webhook') {
next();
} else {
express.json()(req, res, next);
}
});
And still got the same error, would really appreciate it if I could get some help.
I'm using Firebase Cloud Functions as my webhook API, as so:
import webhook_app_creator from "express";
import cors from "cors";
// This example uses Express to receive webhooks
//the Cloud Function calls the webhook_app
export const webhook_app = webhook_app_creator();
// The Firebase Admin SDK to access Cloud Firestore.
//const cors = require("cors");
// Automatically allow cross-origin requests
webhook_app.use(cors({ origin: true }));
// build multiple CRUD interfaces:
webhook_app.post("/direct", async (request, response) => {
//send the response early - the only valid response is "received"
await commonHandler(request, response, endpointDirectSecret);
response.json({ received: true });
});
webhook_app.post("/connect", async (request, response) => {
//send the response early - the only valid response is "received"
await commonHandler(request, response, endpointSecret);
response.json({ received: true });
});
const commonHandler = async (request, response, secret) => {
const sig = request.headers["stripe-signature"];
try {
request.fullEvent = stripe.webhooks.constructEvent(
request.rawBody,
sig,
secret
);
} catch (err) {
logger(`Webhook Error: ${err.message}`);
return;
}
return queue_event(request.fullEvent);
};
The Cloud Function couldn't be simpler:
//import functions from "firebase-functions";
import { functions, webhook_app } from "../../../../services";
// Expose Express API as a single Cloud Function:
export default functions.https.onRequest(webhook_app);
Im building a node.js app with express and I got an error in one of my routes, as you can see here the /hola route is below the /:marketId route and when I try to access the /hola route I get 404 Error not found. And the console logs { CastError: Cast to ObjectId failed for value "hola" at path "_id" for model "Market"
const express = require('express');
const router = express.Router();
const Market = require('../models/Market');
const Product = require('../models/Product');
const productRouter = require('./product');
router.use('/:marketId/products', productRouter);
// Description Show all Markets
// #route GET /markets
router.get('/', async (req, res) => {
try {
const markets = await Market.find().lean();
res.render('markets/index', {
markets
})
} catch (err) {
console.error(err);
res.render('error/404');
}
});
// ADD --------------------------------
// description: Show add market page
//#route GET /markets/add
router.get('/add', (req, res) => {
try {
res.render('markets/add');
} catch (err) {
res.render('error/404');
}
});
// description: Process add form
//#route POST /markets
router.post('/', async (req, res) => {
try {
console.log(req.body);
await Market.create(req.body);
res.redirect('markets');
} catch (err) {
console.error(err);
res.render('error/500');
}
});
// END ADD --------------------------------
//Description Show one market
// # route GET /markets/:id
router.get('/:marketId', async (req, res) => {
try {
const market = await Market.findById(req.params.marketId).lean();
const products = await Product.find({market:req.params.marketId}).sort({category: 'asc'})
.lean();
res.render('markets/marketfull', {
market,
products
});
} catch (err) {
console.error(err);
res.render('error/404');
}
});
router.get('/hola', (req, res ) => {
res.send('pika pika');
}); // --> When I access this route i get this error
// { CastError: Cast to ObjectId failed for value "hola" at path "_id" for model "Market"
module.exports = router;
However the code works when the route /hola is written above the route for /:marketId
const express = require('express');
const router = express.Router();
const Market = require('../models/Market');
const Product = require('../models/Product');
const productRouter = require('./product');
router.use('/:marketId/products', productRouter);
// Description Show all Markets
// #route GET /markets
router.get('/', async (req, res) => {
try {
const markets = await Market.find().lean();
res.render('markets/index', {
markets
})
} catch (err) {
console.error(err);
res.render('error/404');
}
});
// ADD --------------------------------
// description: Show add market page
//#route GET /markets/add
router.get('/add', (req, res) => {
try {
res.render('markets/add');
} catch (err) {
res.render('error/404');
}
});
// description: Process add form
//#route POST /markets
router.post('/', async (req, res) => {
try {
console.log(req.body);
await Market.create(req.body);
res.redirect('markets');
} catch (err) {
console.error(err);
res.render('error/500');
}
});
// END ADD --------------------------------
//Description Show one market
// # route GET /markets/:id
router.get('/hola', (req, res ) => {
res.send('pika pika'); // --> pika pika gets sent
});
router.get('/:marketId', async (req, res) => {
try {
const market = await Market.findById(req.params.marketId).lean();
const products = await Product.find({market:req.params.marketId}).sort({category: 'asc'})
.lean();
res.render('markets/marketfull', {
market,
products
});
} catch (err) {
console.error(err);
res.render('error/404');
}
});
module.exports = router;
Can anyone tell me why this happens? It doesn't make sense to me.
I have a router with the following route:
router.post('/login', async (req, res, next) => {
try {
console.log(req.body);
const { username, password } = req.body;
const identity = await userService.getUserInfo(username, password);
if (!identity.authenticated) {
return res.json({});
}
const requiredTenantId = process.env.TENANT_ID;
const tenant = identity.tenants.find((it) => it.id === requiredTenantId);
if (requiredTenantId && !tenant) {
return res.json({});
}
const userResponse = {
...identity,
token: jwt.sign(
identity,
envVars.getVar(envVars.variables.AUTH_TOKEN_SECRET),
{ expiresIn: '2h' }
)
};
return res.json(userResponse);
} catch (err) {
return next(err);
}
});
Which is basically an asynchronous function.
This is the working test sample:
const request = require('supertest');
const user = require('../../routes/user');
describe('Test user login path', () => {
test('If authorized, it should response with authorized JWT token', () => {
request(user).
post('api/user/login/').
send({
username: 'admin',
password: 'admin'
}).
expect(200);
});
});
If I add async before the function call and await before request user:
test('If authorized, it should response with authorized JWT token', async () => {
await request(user).
the test will fail with the following error:
connect ECONNREFUSED 127.0.0.1:80
Can somebody explain why it is like that? Because in the router I'm using the asynchronous route function.
That's because the supertest expects to be given an express application, not a router
You can create a test-purpose app, mount the user route:
const app = express();
app.use(bodyParser.json());
app.use("/api/user", user);
app.listen(3000);
and pass it to the request
await request(app)
.post("/api/user/login")
working example
I'm writing some rendering-code for an Express app, I wish to catch errors and then output them in the function render, but I'm not sure how I'm going to move them from one method to the other.
app.get('/user/makeRider', auth,
(req, res, next) => {
req.user.user.makeRider(req.query)
.catch(error)
.then(render(req, res));
}
);
var render = (req, res) => {
var response = {
params: req.query,
user: req.user.fulluser
};
res.json(response);
},
error = (reason) => {
reason.errors.forEach((error) =>{
console.log(error);
});
return;
};
You can use error function as your last midleware in the chain and simply pass the request to the next chain:
var render = (req, res) => {
var response = {
params: req.query,
user: req.user.fulluser
};
res.json(response);
}
app.get('/user/makeRider', auth,
(req, res, next) => {
req.user.user.makeRider(req.query)
.catch(next)
.then(render(req, res));
}
);
app.use((reason, req, res, next) => {
res.send(reason.errors);
// or you can send them as json: res.status(404).json({errors: reason.errors})
});
Beware of hoisting issue in your code, the variable declarations are hoisted to the top, but not their assignments, error and render function may appear as undefined when accessed from your route.
A quick, but maybe not the most elegant solution would be to add errors as a parameter to your render function, then you could do something like this:
app.get('/user/makeRider', auth,
(req, res, next) => {
req.user.user.makeRider(req.query)
.catch((reason)=>{
render(req, res, reason.errors)
})
.then(render(req, res));
}
);
var render = (req, res, errs) => {
var response = {
params: req.query,
user: req.user.fulluser
};
res.json(response);
};