Mocking (actually, spying on) Sentry in a jest test in a Node.js project - jestjs

I'm trying to mock (well really, spyOn) Sentry with jest in a Node.js project and it is not working.
The /debug-sentry route just throws an exception, and interactively it correctly sends an exception to Sentry. When I try to mock it like this, though, the expect...toHaveBeenCalled fails. Am I not mocking this right? Suggestions?
import * as Sentry from '#sentry/node';
...
describe('failure modes', () => {
it('send an exception to Sentry if exception thrown', async () => {
const mock = jest.spyOn(Sentry, 'captureException');
const res = await request(server).get('/debug-sentry').expect('Content-Type', /text/).expect(500);
expect(mock).toHaveBeenCalled(); // This does not succeed, not sure why...
...
The code under test (I just added the /debug-sentry route to my server temporarily for testing):
import express from 'express';
import { Express } from 'express-serve-static-core';
import { requestLogger } from '#/utils/request_logger';
import {
initialize_exception_reporting,
initialize_error_reporting,
send_exception_message
} from '#/utils/exception_reporting';
export async function createServer(): Promise<Express> {
const server = express();
initialize_exception_reporting(server);
server.use(requestLogger);
// error customization, if request is invalid
// eslint-disable-next-line #typescript-eslint/no-explicit-any,#typescript-eslint/no-unused-vars
server.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => {
res.status(err.status).json({
error: {
type: 'request_validation',
message: err.message,
errors: err.errors,
},
});
});
server.get("/debug-sentry", (req, res) => {
throw new Error("My first Sentry error!");
});
initialize_error_reporting(server);
return server;
}
and the code setting up Sentry:
import Express from 'express';
import * as Sentry from "#sentry/node";
import * as Tracing from "#sentry/tracing";
export function initialize_exception_reporting(server: Express.Express) {
Sentry.init({
integrations: [],
});
server.use(Sentry.Handlers.requestHandler());
}
export function initialize_error_reporting(server: Express.Express) {
server.use(Sentry.Handlers.errorHandler());
}
export function send_exception_message(message: string) {
Sentry.captureMessage(message);
}

Related

how to prevent file upload when body validation fails in nestjs

I have the multipart form to be validated before file upload in nestjs application. the thing is that I don't want the file to be uploaded if validation of body fails.
here is how I wrote the code for.
// User controller method for create user with upload image
#Post()
#UseInterceptors(FileInterceptor('image'))
create(
#Body() userInput: CreateUserDto,
#UploadedFile(
new ParseFilePipe({
validators: [
// some validator here
]
})
) image: Express.Multer.File,
) {
return this.userService.create({ ...userInput, image: image.path });
}
Tried so many ways to turn around this issue, but didn't reach to any solution
Interceptors run before pipes do, so there's no way to make the saving of the file not happen unless you manage that yourself in your service. However, another option could be a custom exception filter that unlinks the file on error so that you don't have to worry about it post-upload
This is how I created the whole filter
import { isArray } from 'lodash';
import {
ExceptionFilter,
Catch,
ArgumentsHost,
BadRequestException,
} from '#nestjs/common';
import { Request, Response } from 'express';
import * as fs from 'fs';
#Catch(BadRequestException)
export class DeleteFileOnErrorFilter implements ExceptionFilter {
catch(exception: BadRequestException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const status = exception.getStatus();
const getFiles = (files: Express.Multer.File[] | unknown | undefined) => {
if (!files) return [];
if (isArray(files)) return files;
return Object.values(files);
};
const filePaths = getFiles(request.files);
for (const file of filePaths) {
fs.unlink(file.path, (err) => {
if (err) {
console.error(err);
return err;
}
});
}
response.status(status).json(exception.getResponse());
}
}

TypeError: Router.use() requires a middleware function but got a undefined with two middlewares involved

I have to run test on my ts-node app and i am having this problem when running the test, for context the app has routes that has to pass two middlewares, one of authorization and another of valid request i have this config route
const router = express.Router();
router.use('/cars', requiredAuthorization, requiredHeadersValidator, cars);
router.use('/healthcheck', healthcheck());
export default router;
and i get this error while running the test:
● Test suite failed to run
TypeError: Router.use() requires a middleware function but got a undefined
8 | const cars = require('../routes/cars');
9 |
> 10 | router.use('/cars', requiredAuthorization, requiredHeadersValidator, cars);
| ^
11 | router.use('/healthcheck', healthcheck());
12 |
13 | export default router;
at Function.use (node_modules/express/lib/router/index.js:458:13)
at Object.<anonymous> (src/web-server/routes/index.ts:10:8)
at Object.<anonymous> (src/web-server/index.ts:7:1)
at Object.<anonymous> (src/web-server/middlewares/required-authorization.ts:3:1)
at Object.<anonymous> (src/web-server/middlewares/__tests__/required-authorization.test.ts:4:1)
i have tried almost every answer here and in other parts but none of them seems to help (in every file of routing there is a export default router line)
Update
The middlewares are:
import { Request, Response, NextFunction } from 'express';
import { StatusCodes } from 'http-status-codes';
import { autho } from '../index';
const requiredAuthorization = async (req: Request, res: Response, next: NextFunction) => {
if (!req.headers.authorization) {
return res.status(StatusCodes.FORBIDDEN)
.json({
errors: [{
status: StatusCodes.FORBIDDEN, detail: 'Unauthorized'
}]
});
}
let idToken;
if (req.headers.authorization && req.headers.authorization.startsWith('Bearer ')) {
idToken = req.headers.authorization.split('Bearer ')[1];
} else {
return res.status(StatusCodes.FORBIDDEN)
.json({
errors: [{
status: StatusCodes.FORBIDDEN, detail: 'Unauthorized'
}]
});
}
try {
await autho.auth().verifyIdToken(idToken);
next();
return;
} catch (error) {
return res.status(StatusCodes.FORBIDDEN)
.json({
errors: [{
status: StatusCodes.FORBIDDEN, detail: 'Unauthorized'
}]
});
}
};
export default requiredAuthorization;
and:
import { Request, Response, NextFunction } from 'express';
import { ReasonPhrases, StatusCodes } from 'http-status-codes';
const requiredHeadersValidator = (req: Request, res: Response, next: NextFunction) => {
if (req.header('Content-Type') !== 'application/vnd.api+json') {
return res.status(StatusCodes.UNSUPPORTED_MEDIA_TYPE)
.json({
errors: [{
status: StatusCodes.UNSUPPORTED_MEDIA_TYPE, detail: ReasonPhrases.UNSUPPORTED_MEDIA_TYPE
}]
});
}
if (req.header('Accept') !== 'application/vnd.api+json') {
return res.status(StatusCodes.NOT_ACCEPTABLE)
.json({
errors: [{
status: StatusCodes.NOT_ACCEPTABLE, detail: ReasonPhrases.NOT_ACCEPTABLE
}]
});
}
next();
};
export default requiredHeadersValidator;
the imports:
import * as express from 'express';
import * as healthcheck from 'express-healthcheck';
import requiredAuthorization from '../middlewares/required-authorization';
import requiredHeadersValidator from '../middlewares/required-headers-validator';
import cars from './cars';
the cars.ts file:
import * as express from 'express';
import { BrandController } from '../controllers/brand-controller';
import { ModelController } from '../controllers/model-controller';
import { VehicleController } from '../controllers/vehicle-controller';
const router = express.Router();
// brands
router.get('/brands', BrandController.findAll);
router.get('/brands/:id', BrandController.findById);
router.get('/brands/:id/models', BrandController.findModelsByBrandId);
router.post('/brands', BrandController.create);
router.patch('/brands/:id', BrandController.updateBrandById);
router.delete('/brands/:id', BrandController.deleteBrandById);
// models
router.get('/models', ModelController.findAll);
router.get('/models/:id', ModelController.findById);
router.get('/models/:id/brands', ModelController.findBrandByModelId);
router.post('/models', ModelController.create);
router.patch('/models/:id', ModelController.updateModelById);
router.delete('/models/:id', ModelController.deleteModelById);
// vehicles
router.get('/:plate', VehicleController.findByPlate);
export default router;
i got this resolved with a coworker by accident, his IDE autocompleted the file with the imports in different order, to be more precise the requiredAuthorization was the last import in the router and in the test and there the test passed without problem.
import * as express from 'express';
import * as healthcheck from 'express-healthcheck';
import requiredHeadersValidator from '../middlewares/required-headers-validator';
import cars from './cars';
import requiredAuthorization from '../middlewares/required-authorization';
Like this the error dissapeared (the tests were made with Jest), i suppose this could help to someone in the future to considerate the import order in case of some strange behaviour
P.D: if someone has some idea of why the order affected the behavior of this, i would be happy to know it

Nestjs on AWS Lambda (Serverless Framework) | How to access the event parameter?

I'm hosting a Nestjs application on AWS Lambda (using the Serverless Framework).
Please note that the implementation is behind AWS API Gateway.
Question: How can I access to event parameter in my Nest controller?
This is how I bootstrap the NestJS server:
import { APIGatewayProxyHandler } from 'aws-lambda';
import { NestFactory } from '#nestjs/core';
import { AppModule } from './app.module';
import { Server } from 'http';
import { ExpressAdapter } from '#nestjs/platform-express';
import * as awsServerlessExpress from 'aws-serverless-express';
import * as express from 'express';
let cachedServer: Server;
const bootstrapServer = async (): Promise<Server> => {
const expressApp = express();
const adapter = new ExpressAdapter(expressApp);
const app = await NestFactory.create(AppModule, adapter);
app.enableCors();
await app.init();
return awsServerlessExpress.createServer(expressApp);
}
export const handler: APIGatewayProxyHandler = async (event, context) => {
if (!cachedServer) {
cachedServer = await bootstrapServer()
}
return awsServerlessExpress.proxy(cachedServer, event, context, 'PROMISE')
.promise;
};
Here is a function in one controller:
#Get()
getUsers(event) { // <-- HOW TO ACCESS event HERE?? This event is undefined.
return {
statusCode: 200,
body: "This function works and returns this JSON as expected."
}
I'm struggling to understand how I can access the event paramenter, which is easily accessible in a "normal" node 12.x Lambda function:
module.exports.hello = async (event) => {
return {
statusCode: 200,
body: 'In a normal Lambda, the event is easily accessible, but in NestJS its (apparently) not.'
};
};
Solution:
Add AwsExpressServerlessMiddleware to your setup during bootstrap:
const awsServerlessExpressMiddleware = require('aws-serverless-express/middleware')
app.use(awsServerlessExpressMiddleware.eventContext())
Note: The app.use should be before app.init()
Now the event and context object can be accessed:
var event = req.apiGateway.event;
var context = req.apiGateway.context;
Credits: This answer on SO

Using Service in Express Router

I am pretty new in the NodeJS but I would like to learn something new. I came from .NET fancy dependency injection, inversion of controll, microservice shiny world so I am trying write some service in TypeScript based on my previous experiences.
I am using express and express router to create some api. I have some methods in router which handles api calls and I want to use some kind of service object for data retrieving and manipulation.
I inject the service into the router using constructor injection but if I want to use my service it throws an error:
TypeError: Cannot read property 'layoutService' of undefined
I understood that the methods were called withouth context so I added .bind(this) to the each method regsitration and it works, but I dont know if it is the best way how to do it.
Does anyone have a better idea?
simplified server.ts
import express, { Router } from "express";
// inversion of controll
import container from "./ioc";
import { TYPE } from "./constants";
import IMyService from "./abstract/IMyService";
// import routers
import MyRouter from "./api/MyRouter";
app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
const router: Router = express.Router();
const myRouter: MyRouter = new MyRouter(container.get<IMyService>(TYPE.IMyService));
app.use("/", router);
app.use("/api/v1/layouts", layoutRouter.router);
MyRouter.ts
import IMyService from "./abstract/IMyService";
import { Router, Request, Response } from "express";
import { inject } from "inversify";
import { TYPE } from "../constants";
export default class MyRouter {
public readonly router: Router;
private readonly myService: IMyService;
constructor(
#inject(TYPE.IMyService) myService: IMyService
) {
this.myService = myService;
this.router = Router();
this.routes();
}
public GetAll(req: Request, res: Response): void {
this.myService.getAll()
.then(data => {
const status: number = res.statusCode;
res.json({ status, data });
})
.catch(err => {
const status: number = res.statusCode;
res.json({ status, err });
});
}
public GetOne(req: Request, res: Response): void {
const id: string = req.params.id;
this.myService.getOne(new ObjectID(id))
.then(data => {
const status: number = res.statusCode;
res.json({ status, data });
})
.catch(err => {
const status: number = res.statusCode;
res.json({ status, err });
});
}
routes(): void {
this.router
.get("/", this.GetAll)
.get("/:id", this.GetOne);
}
}
If you define your function with the arrow syntax (ES6), it will "bind" the context to it automatically and you won't need to bind them. But it will depends on your use case (ou might need to bind a different context)

socket io on sails js as API and node+react as Frontend

I have an API build using sailsjs and a react redux attach to a nodejs backend, and i am trying to implement socket.io for a realtime communication, how does this work?
is it
socket.io client on the react side that connects to a socket.io server on its nodejs backend that connects to a socket.io server on the API
socket.io client on the react side and on its nodejs backend that connects to a socket.io server on the API
i have tried looking around for some answers, but none seems to meet my requirements.
to try things out, i put the hello endpoint on my API, using the sailsjs realtime documentation, but when i do a sails lift i got this error Could not fetch session, since connecting socket has no cookie (is this a cross-origin socket?) i figure that i need to pass an auth code inside the request headers Authorization property.
Assuming i went for my #1 question, and by using redux-socket.io,
In my redux middleware i created a socketMiddleware
import createSocketIoMiddleware from 'redux-socket.io'
import io from 'socket.io-client'
import config from '../../../config'
const socket = io(config.host)
export default function socketMiddleware() {
return createSocketIoMiddleware(
socket,
() => next => (action) => {
const { nextAction, shuttle, ...rest } = action
if (!shuttle) {
return next(action)
}
const { socket_url: shuttleUrl = '' } = config
const apiParams = {
data: shuttle,
shuttleUrl,
}
const nextParams = {
...rest,
promise: api => api.post(apiParams),
nextAction,
}
return next(nextParams)
},
)
}
and in my redux store
import { createStore, applyMiddleware, compose } from 'redux'
import createSocketIoMiddleware from 'redux-socket.io'
...
import rootReducers from '../reducer'
import socketMiddleware from '../middleware/socketMiddleware'
import promiseMiddleware from '../middleware/promiseMiddleware'
...
import config from '../../../config'
export default function configStore(initialState) {
const socket = socketMiddleware()
...
const promise = promiseMiddleware(new ApiCall())
const middleware = [
applyMiddleware(socket),
...
applyMiddleware(promise),
]
if (config.env !== 'production') {
middleware.push(DevTools.instrument())
}
const createStoreWithMiddleware = compose(...middleware)
const store = createStoreWithMiddleware(createStore)(rootReducers, initialState)
...
return store
}
in my promiseMiddleware
export default function promiseMiddleware(api) {
return () => next => (action) => {
const { nextAction, promise, type, ...rest } = action
if (!promise) {
return next(action)
}
const [REQUEST, SUCCESS, FAILURE] = type
next({ ...rest, type: REQUEST })
function success(res) {
next({ ...rest, payload: res, type: SUCCESS })
if (nextAction) {
nextAction(res)
}
}
function error(err) {
next({ ...rest, payload: err, type: FAILURE })
if (nextAction) {
nextAction({}, err)
}
}
return promise(api)
.then(success, error)
.catch((err) => {
console.error('ERROR ON THE MIDDLEWARE: ', REQUEST, err) // eslint-disable-line no-console
next({ ...rest, payload: err, type: FAILURE })
})
}
}
my ApiCall
/* eslint-disable camelcase */
import superagent from 'superagent'
...
const methods = ['get', 'post', 'put', 'patch', 'del']
export default class ApiCall {
constructor() {
methods.forEach(method =>
this[method] = ({ params, data, shuttleUrl, savePath, mediaType, files } = {}) =>
new Promise((resolve, reject) => {
const request = superagent[method](shuttleUrl)
if (params) {
request.query(params)
}
...
if (data) {
request.send(data)
}
request.end((err, { body } = {}) => err ? reject(body || err) : resolve(body))
},
))
}
}
All this relation between the middlewares and the store works well on regular http api call. My question is, am i on the right path? if i am, then what should i write on this reactjs server part to communicate with the api socket? should i also use socket.io-client?
You need to add sails.io.js at your node server. Sails socket behavior it's quite tricky. Since, it's not using on method to listen the event.
Create sails endpoint which handle socket request. The documentation is here. The documentation is such a pain in the ass, but please bear with it.
On your node server. You can use it like
import socketIOClient from 'socket.io-client'
import sailsIOClient from 'sails.io.js'
const ioClient = sailsIOClient(socketIOClient)
ioClient.sails.url = "YOUR SOCKET SERVER URL"
ioClient.socket.get("SAILS ENDPOINT WHICH HANDLE SOCKET", function(data) {
console.log('Socket Data', data);
})

Resources