NODE.JS(TYPESCRIPT) Property 'req' does not exist on type 'NodeModule' - node.js

I am having issues using this.status and this.req in my subfile
I initialize the route index like this
router.use(response);
my index file is below
import {Request,Response,NextFunction} from 'express'
module.exports = function(req:Request, res:Response, next:NextFunction){
const responseTypes = {
unprocessable: require('./unprocessable')
};
res = {...res, ...responseTypes};
next();
};
here is my unprocessable.ts file
import log from '../logger'
import queue from '../queue'
module.exports = function (data, message) {
log.warn('Sending unprocessable entity response: ', data, message || 'unprocessable entity');
const req = this.req;
const res = this;
// Dump it in the queue
const response = { response: { status: 'error', data: data, message: message ? message : 'unprocessable entity' } };
response.requestId = req.requestId;
queue.add('logResponse', response);
if (data !== undefined && data !== null) {
if (Object.keys(data).length === 0 && JSON.stringify(data) === JSON.stringify({})) {
data = data.toString();
}
}
if (data) {
this.status(422).json({ status: 'error', data: data, message: message ? message : 'unprocessable entity' });
} else {
this.status(422).json({ status: 'error', message: message ? message : 'unprocessable entity' });
}
};
It complains about the following in the unprocessable.ts file
Property 'status' does not exist on type 'NodeModule' if I use this.status
Property 'req' does not exist on type 'NodeModule' if I use this.req
I have no idea how to solve it as I am new to typescript

Typescript does for the most part not know what you refer to when you are using the this keyword.
You can however tell typescript what you mean by this, e.g:
function someFunction(this: object) {
// do something with this
}
In your case, this refers to an object that extends Response from express so what you could do is:
const { Response } = require('express');
interface IModifiedResponse extends Response {
// define your properties here
unprocessable: (data: object, message: string) => void
}
function unprocessable(this: IModifiedResponse, data: object, message: string) {
// use this as in your function
}
However I do not know what this.req refers to as Response does not have a req property. See ExpressJS docs
Hope this answers helps :).

Related

Typescript check propery for unknow type

I have an api request and for error statement. I want to return message from payload.
But message object can change depending on error. For example, payload object can be
{ message: 'Not Authorized', type: 'service.not_authorized' }
or
{
"errors": [
{
"category": "AUTHENTICATION_ERROR",
"code": "UNAUTHORIZED",
"detail": "Authorization code is expired.
}
]
}
I can't create interfaces for all possible payloads.
In JS, basically I can write this :
if(payload && payload.message){
return payload.message
}
if(payload && payload.errors){
const message = payload.errors.length>0 ? payload.errors[0].detail : 'Error'
return message;
}
Now I can try to do this with Typescript
import axios from "axios";
interface IApiError{
category:string,
code:string,
detail:string
}
const CustomError=<T>(e:T):string=>{
let message="";
if(axios.isAxiosError(e)){
const payload = e.response?.data; // payload:unknown
if(payload){
if(typeof payload === 'object'){
if(payload.hasOwnProperty('message')){
message=payload.message;
// Error : Property 'message' does not exist on type 'object'.
}
else if(payload.hasOwnProperty('errors')){
const errorsArray:IApiError[]=payload.errors;
message=errorsArray[0].detail;
// Error : Property 'errors' does not exist on type 'object'.
}
}
}
}
return message;
}
How can I solve this?
Edit
I found this topic. But according this we should create interfaces and typeguard for every possible payload option.
I guess, I found a solution. Based on this answer I created a function.
// Basically it takes 3 arguments
// o -> Object that we don't know what is look like
// prop -> The name of prop that we are looking for
// type -> A variable with the same type with our prop
// Because I didn't find to send type to method in typescript
// (for example string,number,boolean)
const getProp = (o: unknown, prop: string, type:any): any => {
// Checks whether the object contains the requested prop
const p = (o as any)[prop]
// If this field instance of requested type
if (typeof p === typeof type) {
return p;
}
return undefined;
}
Let's test this for different scenarios.
let obj:unknown={ message: 'Not Authorized', type: 'service.not_authorized' }
let obj2:unknown={
"errors": [
{
"category": "AUTHENTICATION_ERROR",
"code": "UNAUTHORIZED",
"detail": "Authorization code is expired."
}
]
}
// In first object I looking for message field and it should be string
let res=getProp(obj,"message","");
res ? console.log(res) : console.log("Not Found");
// console result -> Not Authorized
// In second object I'm intentionally sending the wrong type
// There is a field with name 'errors' but it's type is array
res=getProp(obj2,"errors","");
res && res.length>0 ? console.log(res) : console.log("Not Found");
// console result -> Not Found
// I send true types
res=getProp(obj2,"errors",[]);
res && res.length>0 ? console.log(res[0].detail) : console.log("Not Found");
// console result -> Authorization code is expired.

How to test files and json data at the same time with jest?

I have a post request with express that upload a file and some data to the mongodb:
// Routes
Router.post('/api/training', validator(createVideoSchema, 'body'), uploadVideo, createVideoHandler);
// Route Handlers
async function createVideoHandler (req: Request, res: Response, next: NextFunction) {
try {
const dataToCreate = {
...req.body,
url: req.file?.path,
mimetype: req.file?.mimetype
};
const data = await service.create(dataToCreate);
response(req, res, data, 201);
} catch (error) {
next(error);
}
}
the body must be validate by joi using the following schema:
import Joi from 'joi';
const title = Joi.string().email().min(5).max(255);
const description = Joi.string().min(5).max(255);
const thumbnail = Joi.string().min(5).max(255);
const tags = Joi.array().items(Joi.string().min(5).max(100));
const createVideoSchema = Joi.object({
title: title.required(),
description: description.required(),
thumbnail: thumbnail.required(),
tags: tags.required(),
});
export { createVideoSchema };
Then I am creating a test to verify I am receiving a 201 status code:
it('should have a 201 status code', async () => {
const response = await request(app).post(route)
.set('Accept', 'application/json')
.field('title', data.title)
.field('description', data.description)
.field('thumbnail', data.thumbnail)
.field('tags', data.tags)
.attach('video', Buffer.from('video'), { filename: 'video.mp4' });
expect(response.status).toBe(201);
});
For some reason the validation middleware throws me a 400 error saying that the data is missing:
Error: "title" is required. "description" is required. "thumbnail" is required. "tags" is required
I tried to send the data using .set('Accept', 'multipart/form-data') but it throws me the same error.
I guess this error has to do with the way I send the data, but I don't fully understand.
You typically should not call a live API from a test. Instead you should mock the different possibly API response scenarios and be sure your code handles the different possibilities correctly. Ideally you'll also have a client class of some kind to place direct calls to your API inside a class that can easily be mocked.
For example, you could mock the endpoint response for valid data with something like:
export class VideoClient {
async createVideo(data) {
const response = await request(app).post(route) // Whatever url points to your API endpoint
.set('Accept', 'application/json')
.field('title', data.title)
.field('description', data.description)
.field('thumbnail', data.thumbnail)
.field('tags', data.tags)
.attach('video', Buffer.from('video'), { filename: 'video.mp4' });
if (response.status.ok) {
return { response, message: 'someGoodResponseMessage'};
}
return { response, message: 'someErrorOccurred' };
}
}
Then in your test you can mock your client call:
import { VideoClient } from './clients/VideoClient.js'; // or whatever path you saved your client to
const goodData = { someValidData: 'test' };
const badData = {someBadData: 'test' };
const goodResponse = {
response: { status: 201 },
message: 'someGoodResponseMessage'
}
const badResponse = {
response: { status: 400 },
message: 'someErrorOccurred'
}
it('should have a 201 status code', async () => {
VideoClient.createVideo = jest.fn().mockReturnValue(goodResponse);
const results = await VideoClient.createVideo(goodData);
expect(results.response.status).toBe(201);
expect(results.message).toEqual('someGoodResponseMessage');
});
it('should have a 400 status code', async () => {
VideoClient.createVideo = jest.fn().mockReturnValue(badResponse);
const results = await VideoClient.createVideo(badData);
expect(results.response.status).toBe(400);
expect(results.message).toEqual('someErrorOccurred');
});
This is by no means a working test or exhaustive example, but demonstrating the idea that you really should not call your API in your tests, but instead call mock implementations of your API to handle how your client code responds in different situations.

Got TS 2739 error while returning value from promise. Type 'Promise<any>' is missing the following properties from type

Currently I'm writing code for request login to server and receive it's session data and send to global context.
Basic principle is 1)Request and get Promise 2)Validate fetch result itself and response status. 3)Provide value to external component. And I'm working on 1) and 2).
But I got an error about data typing Type 'Promise<any>' is missing the following properties from type 'SessionInfo': userEmail, userName, sessionToken, duets(2739) at code that returns result data to external components. Despite of strict data typing(Maybe I think), I'm not sure why linter says Promise not Promise>. I think TS fails to assert it's type.
When I run very similar code with Javascript(without typing), it works in past. I'm not sure why this happens and I don't know what's wrong. Can you check my code?
Codes are below, there's 4 files -- interface definition file related to User, interface definition for handling response json, Actual request fetch, Response validation and evaluation.
When I checked linting at return res.data at actionHandler.ts, linter succeed to predict it's type. res.data is ResponseSet<SessionInfo>.data?: userTypes.SessionInfo as linter said.
In userTypes.ts
export interface SessionInfo {
userEmail: string,
userName: string,
sessionToken: string,
due: number,
}
In commonTypes.ts
export interface ResponseSet<T> { // Response wrapper when it returns with code 200
statusCode: ResponseStatusCode, // ResponseStatusCode is custom typed status code not for request it self.
data?: T,
description?: string,
};
In userReq.ts
const login = async (email: string, password: string): Promise<commonTypes.ResponseSet<userTypes.SessionInfo>> => {
try {
const request: Request = new Request(
composeUri(`user/login`, { email, password }),
{
method: 'GET',
headers: { 'Content-type': 'application/json' },
mode: 'cors',
}
);
const response: Response = await fetch(request);
if (response.status != 200) throw response.status;
return await response.json();
} catch {
return {
statusCode: 1,
};
}
}
In actionHandler.ts
export const doLogin = (email: string, password: string): userTypes.SessionInfo => {
const result: userTypes.SessionInfo = userReq.login(email, password)
.then(res => {
if (res.statusCode != 0) throw new Error(res.description || 'UnknownError');
return res.data;
})
.catch(err => {
return null;
});
return result;
}
Where I got an error is const result:.... I got Type 'Promise<any>' is missing the following properties from type 'SessionInfo': userEmail, userName, sessionToken, due ts(2739). I'm not sure why it is recognized as 'Promise` despite of strict type definition of my code.
the issue is that result isn't SessionInfo. It is a Promise of it.
const result: Promisse<userTypes.SessionInfo | null>;
doLogin is async due to used promise, you should follow async await inside and it can't return userTypes.SessionInfo, result will be a promise.
export const doLogin = async (email: string, password: string): Promise<userTypes.SessionInfo | null> => {
try {
const result: commonTypes.ResponseSet<userTypes.SessionInfo> = await userReq.login(email, password);
if (res.statusCode != 0) throw new Error(res.description || 'UnknownError');
} catch (e) {
return null;
}
return res.data;
}
// somewhere in the code (async env)
await doLogin(email, password);

Mock multiple api call inside one function using Moxios

I am writing a test case for my service class. I want to mock multiple calls inside one function as I am making two API calls from one function. I tried following but it is not working
it('should get store info', async done => {
const store: any = DealersAPIFixture.generateStoreInfo();
moxios.wait(() => {
const request = moxios.requests.mostRecent();
request.respondWith({
status: 200,
response: store
});
const nextRequest = moxios.requests.at(1);
nextRequest.respondWith({
status: 200,
response: DealersAPIFixture.generateLocation()
});
});
const params = {
dealerId: store.dealerId,
storeId: store.storeId,
uid: 'h0pw1p20'
};
return DealerServices.retrieveStoreInfo(params).then((data: IStore) => {
const expectedOutput = DealersFixture.generateStoreInfo(data);
expect(data).toMatchObject(expectedOutput);
});
});
const nextRequest is always undefined
it throw error TypeError: Cannot read property 'respondWith' of undefined
here is my service class
static async retrieveStoreInfo(
queryParam: IStoreQueryString
): Promise<IStore> {
const res = await request(getDealerStoreParams(queryParam));
try {
const locationResponse = await graphQlRequest({
query: locationQuery,
variables: { storeId: res.data.storeId }
});
res.data['inventoryLocationCode'] =
locationResponse.data?.location?.inventoryLocationCode;
} catch (e) {
res.data['inventoryLocationCode'] = 'N/A';
}
return res.data;
}
Late for the party, but I had to resolve this same problem just today.
My (not ideal) solution is to use moxios.stubRequest for each request except for the last one. This solution is based on the fact that moxios.stubRequest pushes requests to moxios.requests, so, you'll be able to analyze all requests after responding to the last call.
The code will look something like this (considering you have 3 requests to do):
moxios.stubRequest("get-dealer-store-params", {
status: 200,
response: {
name: "Audi",
location: "Berlin",
}
});
moxios.stubRequest("graph-ql-request", {
status: 204,
});
moxios.wait(() => {
const lastRequest = moxios.requests.mostRecent();
lastRequest.respondWith({
status: 200,
response: {
isEverythingWentFine: true,
},
});
// Here you can analyze any request you want
// Assert getDealerStoreParams's request
const dealerStoreParamsRequest = moxios.requests.first();
expect(dealerStoreParamsRequest.config.headers.Accept).toBe("application/x-www-form-urlencoded");
// Assert graphQlRequest
const graphQlRequest = moxios.requests.get("POST", "graph-ql-request");
...
// Assert last request
expect(lastRequest.config.url).toBe("status");
});

GraphQL Resolver returns error "Cannot read forEach of Undefined"

I have a graphql endpoint that I'm running a query against, and I'm building my resolver that is massaging the data before returning to the client. My query is this:
query getTransactions($transID: String!, $confidence: Float) {
transactions(parentID: $transID, confidence: $confidence) {
id
childrens {
id
name
email
phone
age
connectionInfo {
type
confidence
}
}
name
email
phone
age
}
}
and my resolver is currently looking like this:
const getTransactions = (args: any): any => {
const { parentID, confidence } = args;
const trxs = transactions.filter(t => {
return t.id === parentID;
});
let finalChildrens: any[] = [];
trxs.forEach(t => {
finalChildrens.concat(filteredChildren(t));
});
trxs.concat(finalChildrens);
return trxs;
};
const filteredChildren = (t: any): any[] => {
log.debug({ typeCheck: typeof t.childrens, children: t.childrens });
let outputChildren: any[] = [];
if (typeof t.childrens !== undefined) {
t.childrens.forEach((c1: any) => {
if (typeof c1.childrens !== undefined) {
outputChildren.concat(filteredChildren(c1));
outputChildren.push(c1);
} else {
outputChildren.push(c1);
}
});
return outputChildren;
} else {
return ['no child'] as any[];
}
};
The issue I'm facing is that I'm continually getting this error either in the client or graphiql is this:
"Cannot read property 'forEach' of undefined"
I want to say that it has to do with either the forEach in filteredChildren or inside the resolver itself. I'm going through these "gymnastics" in order to get a flat array that is retrieved recursively from the underlying data. How would someone check the array to see if it's filled or not? (or in this case, if the array exists at all?)
The condition typeof t.childrens !== undefined is always true. You should either use typeof t.childrens !== "undefined" or t.childrens !== undefined.

Resources