How to call external function in jest - node.js

I'm New to unit test and trying to test my controller method.my project architecture design is as follow
Controller->Service->Model.
My test scenarios :
Pass correct parameters to controller method and test success response
Pass Invalid parameters to controller method and test error response
When i going to test scenario 1 ,according to my understanding i want to mock my programService and it return values.I have write test as follow and got errors.
I would really appreciate some one can fix this
ProgramsController.js
const ProgramService = require('../../services/program/programService');
class ProgramsController {
constructor() {
this.programService = new ProgramService();
}
async subscribe(req, res) {
try {
const { userId, uuid, msisdn, body: { programId } } = req;
const data = { userId, programId, msisdn, uuid }
const subscribe = await this.programService.subscribeUser(data);
res.json({
status: true,
message: 'Success',
friendly_message: constant.MSG.SUBSCRIPTION,
data: subscribe
})
} catch (error) {
res.status(500)
.json({
status: false,
message: 'Fail',
friendly_message: constant.MSG.SUBSCRIPTION_FAIL
})
}
}
}
ProgramService.js
class ProgramService {
constructor() {
this.subscriber = new Subscriber();
this.subsciberProgram = new SubsciberProgram()
}
async subscribeUser(data) {
try {
const { msisdn, userId, programId, uuid } = data;
...
return subscribedData;
} catch (error) {
throw error;
}
}
}
module.exports = ProgramService;
test.spec.js
const ProgramsService = require('../src/services/program/programService')
const ProgramsController = require('../src/controllers/programs/programsController')
const programController = new ProgramsController()
const programsService = new ProgramsService()
beforeAll(() => {
db.sequelize.sync({ force: true }).then(() => { });
});
const mockRequest = (userId, uuid, msisdn, body) => ({
userId,
uuid,
msisdn,
body,
});
const mockResponse = () => {
const res = {};
res.status = jest.fn().mockReturnValue(res);
res.json = jest.fn().mockReturnValue(res);
return res;
};
const serviceRecord = { userId: 1, programId: 1, msisdn: '56768382967', uuid: '46651a19-3ef1-4149-818e-9bd8a5f359ef' };
const fakeServiceReturn = { program_id: 1, amount: 5, no_of_questions: 10 }
describe('Subscribe', () => {
test('should return 200', async () => {
const req = mockRequest(
1,
'56768382967',
'46651a19-3ef1-4149-818e-9bd8a5f359ef',
{ 'programId': 1 }
);
const res = mockResponse();
const spy = jest.spyOn(programsService, 'subscribeUser').mockImplementation(() => serviceRecord);
await programController.subscribe(req, res);
expect(programsService.subscribeUser()).toHaveBeenCalledWith(fakeServiceReturn);
expect(res.status).toHaveBeenCalledWith(500);
expect(res.json).toHaveBeenCalledWith({
status: true,
message: 'Success',
friendly_message: 'successfull get data',
data : { program_id: 1, amount: 5, no_of_questions: 10 }
});
spy.mockRestore();
});
});
how can i mock programService.subscribeUser and test success response?

This mock should return a promise:
jest.spyOn(programsService, 'subscribeUser').mockImplementation(() => Promise.resolve(serviceRecord));

Related

Trouble with return statement in Node Async Await

Trying to write a query for DynamoDB and learn promises etc. The console.log(resp.Items) returns the object that I am looking for so I think my query is formatted correctly. I get a status 200 back with an empty object.
I have read up for a few days and tried to implement various changes to the code by nothing is returning the object resp.Items. I am guessing the function is returning before the const is updated with the data but I am not sure why the console.log works.
const AWS = require('aws-sdk')
const dynamodb = new AWS.DynamoDB()
const getTechs = async () => {
try {
const resp = await dynamodb
.query({
ExpressionAttributeValues: {
':tech': { S: 'TECH#' },
},
KeyConditionExpression: 'PK = :tech',
TableName: process.env.TABLE_NAME,
ScanIndexForward: true,
})
.promise()
console.log(resp.Items)
if (!resp.Items) {
return {
error: 'No Techs in the DB',
}
}
return {
tech: resp.Items,
}
} catch (error) {
console.log('Error retrieving Tech List')
console.log(error)
return {
error: 'Could not retrieve Tech List',
}
}
}
handler func
const { makeHandler } = require('./utils')
const { getTechs } = require('../data')
// const { Tech } = require('../entities')
const inputSchema = {
type: 'object',
properties: {
pathParameters: {
type: 'object',
properties: {
tech: { type: 'string' },
},
required: ['tech'],
},
},
required: ['pathParameters'],
}
const handler = async (event) => {
const { techs, error } = await getTechs()
const statusCode = error ? 500 : 200
const body = error ? JSON.stringify({ error }) : JSON.stringify({ techs })
return {
statusCode,
body,
}
}
module.exports.handler = makeHandler({ handler })
executeTransactWrite func
const executeTransactWrite = async ({ tech, params }) => {
const transactionRequest = tech.transactWriteItems(params)
let cancellationReasons
transactionRequest.on('extractError', (response) => {
try {
cancellationReasons = JSON.parse(
response.httpResponse.body.toString()
).CancellationReasons
} catch (err) {
// suppress this just in case some types of errors aren't JSON parseable
console.error('Error extracting cancellation error', err)
}
})
return new Promise((resolve, reject) => {
transactionRequest.send((err, response) => {
if (err) {
err.cancellationReasons = cancellationReasons
return reject(err)
}
return resolve(response)
})
})
}
module.exports = {
executeTransactWrite,
makehandler func
const middy = require('middy')
const {
jsonBodyParser,
validator,
httpErrorHandler,
} = require('middy/middlewares')
const makeHandler = ({ handler, inputSchema }) =>
middy(handler)
.use(jsonBodyParser())
.use(validator({ inputSchema }))
.use(httpErrorHandler())
module.exports = { makeHandler }

Jest Mock Implementation is not working, instead original function is being called

I am trying to test an API by mocking the database function, but the imported function is being called instead of the mocked one.
Here are the code snippets
const supertest = require('supertest');
const axios = require('axios');
const querystring = require('querystring');
const { app } = require('../app');
const DEF = require('../Definition');
const tripDb = require('../database/trip');
const request = supertest.agent(app); // Agent can store cookies after login
const { logger } = require('../Log');
describe('trips route test', () => {
let token = '';
let companyId = '';
beforeAll(async (done) => {
// do something before anything else runs
logger('Jest starting!');
const body = {
username: process.env.EMAIL,
password: process.env.PASSWORD,
grant_type: 'password',
client_id: process.env.NODE_RESOURCE,
client_secret: process.env.NODE_SECRET,
};
const config = {
method: 'post',
url: `${process.env.AUTH_SERV_URL}/auth/realms/${process.env.REALM}/protocol/openid-connect/token`,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
data: querystring.stringify(body),
};
const res = await axios(config);
token = res.data.access_token;
done();
});
const shutdown = async () => {
await new Promise((resolve) => {
DEF.COM.RCLIENT.quit(() => {
logger('redis quit');
resolve();
});
});
// redis.quit() creates a thread to close the connection.
// We wait until all threads have been run once to ensure the connection closes.
await new Promise(resolve => setImmediate(resolve));
};
afterAll(() => shutdown());
test('post correct data', async (done) => {
const createTripMock = jest.spyOn(tripDb, 'addTrip').mockImplementation(() => Promise.resolve({
pk: `${companyId}_trip`,
uid: '1667561135293773',
lsi1: 'Kotha Yatra',
lsi2: companyId,
name: 'Kotha Yatra',
companyId,
origin: {
address: 'Goa, India',
location: {
lat: 15.2993265,
lng: 74.12399599999999,
},
},
destination: {
address: 'Norway',
location: {
lat: 60.47202399999999,
lng: 8.468945999999999,
},
},
path: [
{
lat: 15.2993265,
lng: 74.12399599999999,
},
{
lat: 60.47202399999999,
lng: 8.468945999999999,
},
],
isDeleted: false,
currentVersion: 1,
geofences: [],
}));
const response = await request.post('/api/trips').set('Authorization', `Bearer ${token}`).send(tripPayload);
expect(createTripMock).toHaveBeenCalled();
expect(response.status).toEqual(200);
expect(response.body.status).toBe('success');
done();
});
});
The database function:
const addTrip = (trip) => {
// const uid = trip.uid ? trip.uid : (Date.now() * 1000) + Math.round(Math.random() * 1000);
const uid = (Date.now() * 1000) + Math.round(Math.random() * 1000);
const item = {
pk: `${trip.companyId}_trip`,
uid: `v${trip.version ? trip.version : 0}#${uid}`,
lsi1: trip.name,
lsi2: trip.companyId,
name: trip.name,
companyId: trip.companyId,
origin: trip.origin,
destination: trip.destination,
path: trip.path,
isDeleted: false,
};
if (!trip.version || trip.version === 0) {
item.currentVersion = 1;
} else {
item.version = trip.version;
}
if (trip.geofences) item.geofences = trip.geofences;
const params = {
TableName: TN,
Item: item,
ConditionExpression: 'attribute_not_exists(uid)',
};
// console.log('params ', params);
return new Promise((resolve, reject) => {
ddb.put(params, (err, result) => {
// console.log('err ', err);
if (err) {
if (err.code === 'ConditionalCheckFailedException') return reject(new Error('Trip id or name already exists'));
return reject(err);
}
if (!trip.version || trip.version === 0) {
const newItem = { ...item };
delete newItem.currentVersion;
newItem.version = 1;
newItem.uid = `v1#${item.uid.split('#')[1]}`;
const newParams = {
TableName: TN,
Item: newItem,
ConditionExpression: 'attribute_not_exists(uid)',
};
// console.log('new params ', newParams);
ddb.put(newParams, (v1Err, v1Result) => {
// console.log('v1 err ', v1Err);
if (v1Err) return reject(v1Err);
item.uid = item.uid.split('#')[1];
return resolve(item);
});
} else {
item.uid = item.uid.split('#')[1];
return resolve(item);
}
});
});
};
module.exports = {
addTrip,
};
I was mocking the above database function when I was making a request to add API, instead, the original function is being called and I was getting the result that I had written in the mock Implementation.
What should I do to just mock the result ,when the function is called and no implementation of the original function should happen.
Even this did not give an error
expect(createTripMock).toHaveBeenCalled();
Still the database function call is happening
I tried using mockReturnValue, mockReturnValueOnce, mockImplemenationOnce but not luck.
Can anyone help me with this?

Error with sending an arrays and an object in a GET API and getting them in the reducer

I'm working on a project where I need to send an arrays and an object from the backend (nodejs) through a GET api to the frontend (reactjs) and have both of those be accessible in my reducer. I have never done this, and I'm not sure if I'm going about it the right way. I am currently getting an error saying that totalPages from this line: export const orderMineListReducer = (state = {orders:[], totalPages}, action) => { is not defined. I would really appreciate any help or advice on how to go about sending a GET api with an arrays and an object and receiving an arrays and an object in the reducer. Thank you!
Below, I have included what I have tried to do so far:
Backend:
orderRouter.js
orderRouter.get(
'/mine',
isAuth,
expressAsyncHandler(async (req, res) => {
const page = req.query.page || 1;
const perPage = 20
const orders = await Order.find({ user: req.user._id }).skip(page * perPage).limit(perPage);
const total = await Order.countDocuments();
const totalPages = Math.ceil(total / perPage).toString();
res.status(200).send({
data:
[orders],
totalPages,
});
}),
);
Frontend
orderReducer.js
export const orderMineListReducer = (state = {orders:[], totalPages}, action) => {
switch (action.type) {
case ORDER_MINE_LIST_REQUEST:
return { ...state, loading: true };
case ORDER_MINE_LIST_SUCCESS:
return { ...state, loading: false, orders: action.payload.orders, totalPages: action.payload.totalPages,};
case ORDER_MINE_LIST_FAIL:
return { ...state, loading: false, error: action.payload };
default:
return state;
}
};
orderActions.js
export const listOrderMine = (page) => async (dispatch, getState) => {
dispatch({ type: ORDER_MINE_LIST_REQUEST });
const {
userSignin: { userInfo },
} = getState();
try {
const { data } = await Axios.get(`${BASE_URL}/api/orders/mine?page=${page}`, {
headers: {
Authorization: `Bearer ${userInfo.token}`,
},
});
dispatch({ type: ORDER_MINE_LIST_SUCCESS, payload: data });
} catch (error) {
const message = error.response && error.response.data.message ? error.response.data.message : error.message;
dispatch({ type: ORDER_MINE_LIST_FAIL, payload: message });
}
};
I've also tried just having
res.status(200).send({
orders,
totalPages,
});
instead of res.status(200).send({data: { orders, totalPages,}});
with my reducer like so:
export const orderMineListReducer = (state = { data: {} }, action) => {
switch (action.type) {
case ORDER_MINE_LIST_REQUEST:
return { ...state, loading: true };
case ORDER_MINE_LIST_SUCCESS:
return { ...state, loading: false, data: action.payload,};
case ORDER_MINE_LIST_FAIL:
return { ...state, loading: false, error: action.payload };
default:
return state;
}
};
however in my OrderHistoryScreen.js where I have
const orderMineList = useSelector((state) => state.orderMineList);
const { loading, data, error,} = orderMineList;
const dispatch = useDispatch();
useEffect(() => { dispatch(listOrderMine());
}, [dispatch]);
I am getting undefined for console.log(data.orders) and empty {} for console.log(data).
Your response has this scheme:
{
data: {
orders,
totalPages
}
}
Axios.get will resolve to an object with this schema:
{
data: {
data: {
orders,
totalPages
}
},
status: 200,
statusText: 'OK',
...
}
So you need to change the destructuring or dispatch data.data like this:
dispatch({ type: ORDER_MINE_LIST_SUCCESS, payload: data.data });
Check the Axios documentation on the response schema: https://axios-http.com/docs/res_schema

API Only sends 1 chunk of metadata when called

I have a problem with my API that sends metadata when called from my smart contract of website. Its NFT tokens and my database is postgres and API is node.js
The problem is when I mint 1 NFT metadata works perfect, but if I mint 2 or more it will only ever send 1 chunk of data? So only 1 NFT will mint properly and the rest with no data?
Do I need to set a loop function or delay? Does anyone have any experience with this?
Any help would be much appreciated.
Below is the code from the "controller" folder labeled "nft.js"
const models = require("../../models/index");
const path = require("path");
const fs = require("fs");
module.exports = {
create_nft: async (req, res, next) => {
try {
const dir = path.resolve(__dirname + `../../../data/traitsfinal.json`);
const readCards = fs.readFileSync(dir, "utf8");
const parsed = JSON.parse(readCards);
console.log("ya data ha final ??", parsed);
parsed.forEach(async (item) => {
// return res.json(item)
let newNft = await models.NFT.create({
name: item.Name,
description: item.Description,
background: item.Background,
body: item.Body,
mouth: item.Mouth,
eyes: item.Eyes,
head_gear: item.Head_Gear,
tokenId: item.tokenId,
image: item.imagesIPFS,
});
});
return res.json({
data: "nft created",
error: null,
success: true,
});
} catch (error) {
console.log("server error", error.message);
next(error);
}
},
get_nft: async (req, res, next) => {
try {
const { id } = req.params;
// console.log("id ?????????",id)
// console.log("type of ",typeof(id))
// const n=Number(id)
// console.log("type of ",typeof(id))
const nft = await models.NFT.findByPk(id);
if (!nft) {
throw new Error("Token ID invalid");
}
if (!nft.isMinted) {
throw new Error("Token not minted");
}
console.log(nft);
// }
const resObj = {
name: nft.name,
description: nft.description,
image: `https://gateway.pinata.cloud/ipfs/${nft.image}`,
attributes: [
{ trait_type: "background", value: `${nft.background}` },
{ trait_type: "body", value: `${nft.body}` },
{ trait_type: "mouth", value: `${nft.mouth}` },
{ trait_type: "eyes", value: `${nft.eyes}` },
{ trait_type: "tokenId", value: `${nft.tokenId}` },
{
display_type: "number",
trait_type: "Serial No.",
value: id,
max_value: 1000,
},
],
};
return res.json(resObj);
} catch (error) {
console.log("server error", error.message);
next(error);
}
},
get_nft_all: async (req, res, next) => {
try {
// console.log("id ?????????",id)
// console.log("type of ",typeof(id))
// const n=Number(id)
// console.log("type of ",typeof(id))
const nft = await models.NFT.findAndCountAll({
limit: 10
});
// console.log(nft);
if (!nft) {
throw new Error("Token ID invalid");
}
// if (nft.isMinted) {
// throw new Error("Token not minted");
// }
// console.log(nft);
// }
var resObjarr = [];
for (var i = 0; i < nft.rows.length; i++) {
resObj = {
name: nft.rows[i].name,
description: nft.rows[i].description,
image: `https://gateway.pinata.cloud/ipfs/${nft.rows[i].image}`,
attributes: [
{ trait_type: "background", value: `${nft.rows[i].background}` },
{ trait_type: "body", value: `${nft.rows[i].body}` },
{ trait_type: "mouth", value: `${nft.rows[i].mouth}` },
{ trait_type: "eyes", value: `${nft.rows[i].eyes}` },
{ trait_type: "tokenId", value: `${nft.rows[i].tokenId}` },
{
display_type: "number",
trait_type: "Serial No.",
value: nft.rows[i].id,
max_value: 1000,
},
],
};
resObjarr.push(resObj);
}
console.log(JSON.stringify(resObjarr))
return res.json(resObjarr);
} catch (error) {
console.log("server error", error.message);
next(error);
}
},
mint: async (req, res, next) => {
try {
const { id } = req.params;
const updated = await models.NFT.findByPk(id);
if (!updated) {
throw new Error("NFT ID invalid");
}
if (updated.isMinted) {
throw new Error("NFT Already minted");
}
updated.isMinted = true;
updated.save();
return res.json({
data: "Token minted successfully",
error: null,
success: true,
});
} catch (error) {
console.log("server error", error.message);
next(error);
}
},
};
Below is from the routes folder.
const router = require("express").Router();
const auth=require("../middleware/auth")
const {
create_nft,
get_nft,
get_nft_all,
mint
} = require("../controller/nft");
router.post(
"/create",
create_nft
);
router.get(
"/metadata/:id",
get_nft
);
router.get(
"/metadata",
get_nft_all
);
router.put(
"/mint/:id",
mint
);
module.exports = router;
Looking your code,you may having some kind of asyncrhonous issue in this part:
parsed.forEach(async (item) => {
// return res.json(item)
let newNft = await models.NFT.create({
name: item.Name,
description: item.Description,
background: item.Background,
body: item.Body,
mouth: item.Mouth,
eyes: item.Eyes,
head_gear: item.Head_Gear,
tokenId: item.tokenId,
image: item.imagesIPFS,
});
});
Because .forEach is a function to be used in synchronous context and NFT.create returns a promise (that is async). So things happens out of order.
So one approach is to process the data first and then perform a batch operation using Promise.all.
const data = parsed.map(item => {
return models.NFT.create({
name: item.Name,
description: item.Description,
background: item.Background,
body: item.Body,
mouth: item.Mouth,
eyes: item.Eyes,
head_gear: item.Head_Gear,
tokenId: item.tokenId,
image: item.imagesIPFS,
})
})
const results = await Promise.all(data)
The main difference here is Promise.all resolves the N promises NFT.create in an async context in paralell. But if you are careful about the number of concurrent metadata that data may be too big to process in parallel, then you can use an async iteration provided by bluebird's Promise.map library.
const Promise = require('bluebird')
const data = await Promise.map(parsed, item => {
return models.NFT.create({
name: item.Name,
description: item.Description,
background: item.Background,
body: item.Body,
mouth: item.Mouth,
eyes: item.Eyes,
head_gear: item.Head_Gear,
tokenId: item.tokenId,
image: item.imagesIPFS,
})
})
return data

mtproto/core Telegram replay to another Channel

i want collect news from different channels and echo them in a second channel, with the code i can read channels(not all but most).
I stuck now on the echo problem and have no clue about how i can do this, mtproto is completely new to me, thanks.
Im using the following code i have from another stackoverflow question.
const { MTProto, getSRPParams } = require('#mtproto/core');
const prompts = require('prompts');
const api_id = 'xxxxx';
const api_hash = 'xxxxx';
async function getPhone() {
return (await prompts({
type: 'text',
name: 'phone',
message: 'Enter your phone number:'
})).phone
}
async function getCode() {
// you can implement your code fetching strategy here
return (await prompts({
type: 'text',
name: 'code',
message: 'Enter the code sent:',
})).code
}
async function getPassword() {
return (await prompts({
type: 'text',
name: 'password',
message: 'Enter Password:',
})).password
}
const mtproto = new MTProto({
api_id,
api_hash,
});
function startListener() {
console.log('[+] starting listener')
mtproto.updates.on('updates', ({ updates }) => {
const newChannelMessages = updates.filter((update) => update._ === 'updateNewChannelMessage').map(({ message }) => message) // filter `updateNewChannelMessage` types only and extract the 'message' object
for (const message of newChannelMessages) {
// printing new channel messages
console.log(`[${message.to_id.channel_id}] ${message.message}`)
}
});
}
// checking authentication status
mtproto
.call('users.getFullUser', {
id: {
_: 'inputUserSelf',
},
})
.then(startListener) // means the user is logged in -> so start the listener
.catch(async error => {
// The user is not logged in
console.log('[+] You must log in')
const phone_number = await getPhone()
mtproto.call('auth.sendCode', {
phone_number: phone_number,
settings: {
_: 'codeSettings',
},
})
.catch(error => {
if (error.error_message.includes('_MIGRATE_')) {
const [type, nextDcId] = error.error_message.split('_MIGRATE_');
mtproto.setDefaultDc(+nextDcId);
return sendCode(phone_number);
}
})
.then(async result => {
return mtproto.call('auth.signIn', {
phone_code: await getCode(),
phone_number: phone_number,
phone_code_hash: result.phone_code_hash,
});
})
.catch(error => {
if (error.error_message === 'SESSION_PASSWORD_NEEDED') {
return mtproto.call('account.getPassword').then(async result => {
const { srp_id, current_algo, srp_B } = result;
const { salt1, salt2, g, p } = current_algo;
const { A, M1 } = await getSRPParams({
g,
p,
salt1,
salt2,
gB: srp_B,
password: await getPassword(),
});
return mtproto.call('auth.checkPassword', {
password: {
_: 'inputCheckPasswordSRP',
srp_id,
A,
M1,
},
});
});
}
})
.then(result => {
console.log('[+] successfully authenticated');
// start listener since the user has logged in now
startListener()
});
})

Resources