Matcher error: received value must be a mock function - node.js

I am trying to check the number of times a function has been called and I am receiving this error
expect(waitForCallStatus(callService, currentCall, CallStatus.Held, delay, retries, nextAttempt)).toHaveBeenCalledTimes(2);
The error -
expect(received).toHaveBeenCalledTimes(expected)
Matcher error: received value must be a mock or spy function
Here is my test file - In the test I am trying to check the recursion behaviour if in first attempt the status isn't matched it should try again
describe('lib.call-status-service.spec.ts', () => {
const callService = getMockCallService();
let holdCall: Call;
let currentCall: Call;
const delay = 300;
const retries = 5;
beforeEach(() => {
jest.clearAllMocks();
holdCall = getMockTenfoldCall({
_id: '1234',
pbxCallId: '1234',
status: CallStatus.Held,
});
currentCall = getMockTenfoldCall({
_id: '4321',
pbxCallId: '4321',
status: CallStatus.Connected,
});
(waitForCallStatus as jest.Mock).mockResolvedValue({});
});
describe('waitCallForStatus', () => {
it('should check if the fetched call status matches the expected status incase of holdCall', () => {
const status = 'Held';
(callService.findCall as jest.Mock).mockResolvedValue([holdCall]);
expect(holdCall.status).toEqual(status);
expect(waitForCallStatus(callService, currentCall, CallStatus.Held)).resolves.toHaveReturnedWith(holdCall);
});
it('Should throw an error if current retry is equal to total number of retries', () => {
const status = 'Held';
(callService.findCall as jest.Mock).mockResolvedValue([currentCall]);
expect(currentCall.status).not.toEqual(status);
const nextAttempt = 5;
expect(waitForCallStatus(callService, currentCall, CallStatus.Held, delay, retries, nextAttempt))
.rejects.toThrowError('Max Retries Reached');
});
it('Should check again if in first attemp fetched call status doesnt match the expected status', () => {
const status = 'Held';
(callService.findCall as jest.Mock).mockResolvedValue([currentCall]);
expect(currentCall.status).not.toEqual(status);
const nextAttempt=1;
expect(waitForCallStatus(callService, currentCall, CallStatus.Held, delay, retries, nextAttempt)).toHaveBeenCalledTimes(2);
});
});
});
Here is my function:-
callService: CallService,
call: Call,
status: CallStatus,
delay = 300,
retries = 5,
currentTry = 0,
) :Promise<any> {
if (currentTry === retries) {
throw TfApiError.badRequest('Max retries reached');
}
await Bluebird.delay(delay);
const query = {
_id: call._id,
pbxCallId: call.pbxCallId,
};
const updatedCall = await callService.findCall(query);
if (updatedCall.status === status) {
return call;
}
const nextAttempt = currentTry + 1;
return waitForCallStatus(callService, updatedCall, status, delay, retries, nextAttempt);
}
What am I doing wrong?

Related

how to use await instead of then in promise?

How to correctly resolve a Promise.all(...), I'm trying that after resolving the promise which generates a set of asynchronous requests (which are simple database queries in supabase-pg SQL) I'm iterating the result in a forEach , to make a new request with each of the results of the iterations.
But, try to save the result that it brings me in a new array, which prints fine in the console, but in the response that doesn't work. It comes empty, I understand that it is sending me the response before the promise is finished resolving, but I don't understand why.
In an answer to a previous question I was told to use await before the then, but I didn't quite understand how to do it.
What am I doing wrong?
export const getReportMonthly = async(req: Request & any, res: Response, next: NextFunction) => {
try {
let usersxData: UsersxModalidadxRolxJob[] = [];
let data_monthly: HoursActivityWeeklySummary[] = [];
let attendance_schedule: AttendanceSchedule[] = [];
let time_off_request: TimeOffRequestRpc[] = [];
let configs: IndicatorConfigs[] = [];
const supabaseService = new SupabaseService();
const promises = [
supabaseService.getSummaryWeekRpcWihoutFreelancers(req.query.fecha_inicio, req.query.fecha_final).then(dataFromDB => {
data_monthly = dataFromDB as any;
}),
supabaseService.getUsersEntity(res).then(dataFromDB => {
usersxData = dataFromDB as any;
}),
supabaseService.getAttendaceScheduleRpc(req.query.fecha_inicio, req.query.fecha_final).then(dataFromDB => {
attendance_schedule = dataFromDB as any;
}),
supabaseService.getTimeOffRequestRpc(req.query.fecha_inicio, req.query.fecha_final).then(dataFromDB => {
time_off_request = dataFromDB as any;
}),
supabaseService.getConfigs(res).then(dataFromDB => {
configs = dataFromDB;
}),
];
let attendanceInMonthly = new Array();
await Promise.all(promises).then(() => {
attendance_schedule.forEach(element => {
let start_date = element.date_start.toString();
let end_date = element.date_end.toString();
supabaseService.getTrackedByDateAndIDArray(start_date, end_date).then(item => {
console.log(item);
attendanceInMonthly.push(item);
});
});
})
res.json(attendanceInMonthly)
} catch (error) {
console.log(error);
res.status(500).json({
title: 'API-CIT Error',
message: 'Internal server error'
});
}
If you await a promise you could write the return of this in a variable and work with this normaly.
So instead of your current code you could use the following changed code:
export const getReportMonthly = async(req: Request & any, res: Response, next: NextFunction) => {
try {
let usersxData: UsersxModalidadxRolxJob[] = [];
let data_monthly: HoursActivityWeeklySummary[] = [];
let attendance_schedule: AttendanceSchedule[] = [];
let time_off_request: TimeOffRequestRpc[] = [];
let configs: IndicatorConfigs[] = [];
const supabaseService = new SupabaseService();
const promises = [
supabaseService.getSummaryWeekRpcWihoutFreelancers(req.query.fecha_inicio, req.query.fecha_final).then(dataFromDB => {
data_monthly = dataFromDB as any;
}),
supabaseService.getUsersEntity(res).then(dataFromDB => {
usersxData = dataFromDB as any;
}),
supabaseService.getAttendaceScheduleRpc(req.query.fecha_inicio, req.query.fecha_final).then(dataFromDB => {
attendance_schedule = dataFromDB as any;
}),
supabaseService.getTimeOffRequestRpc(req.query.fecha_inicio, req.query.fecha_final).then(dataFromDB => {
time_off_request = dataFromDB as any;
}),
supabaseService.getConfigs(res).then(dataFromDB => {
configs = dataFromDB;
}),
];
const resolvedPromises = await Promise.all(promises)
const attendanceInMonthly = await Promise.all(
resolvedPromises.map(
async (element) => {
let start_date = element.date_start.toString();
let end_date = element.date_end.toString();
return supabaseService.getTrackedByDateAndIDArray(start_date, end_date)
}
)
)
console.log(attendanceInMonthly) // this should be your finaly resolved promise
res.json(attendanceInMonthly)
} catch (error) {
console.log(error);
res.status(500).json({
title: 'API-CIT Error',
message: 'Internal server error'
});
}
Something like this should your code looks like. I am not sure if this solves exactly your code because your code has some syntax errors wich you have to solve for you.
If I understand correctly, you launch a few requests, among which one (getAttendaceScheduleRpc, which assigns attendance_schedule) is used to launch some extra requests again, and you need to wait for all of these (including the extra requests) before returning?
In that case, the immediate issue is that you perform your extra requests in "subqueries", but you do not wait for them.
A very simple solution would be to properly separate those 2 steps, somehow like in DerHerrGammler's answer, but using attendance_schedule instead of resolvedPromises as input for the 2nd step:
let attendanceInMonthly = new Array();
await Promise.all(promises);
await Promise.all(attendance_schedule.map(async (element) => {
let start_date = element.date_start.toString();
let end_date = element.date_end.toString();
const item = await supabaseService.getTrackedByDateAndIDArray(start_date, end_date);
console.log(item);
attendanceInMonthly.push(item);
});
res.json(attendanceInMonthly);
If you are really looking to fine tune your performance, you could take advantage of the fact that your extra requests depend only on the result of one of your initial requests (getAttendaceScheduleRpc), so you could launch them as soon as the latter is fullfilled, instead of waiting for all the promises of the 1st step:
let attendance_schedule: AttendanceSchedule[] = [];
let attendanceInMonthly = new Array();
const promises = [
supabaseService.getAttendaceScheduleRpc(req.query.fecha_inicio, req.query.fecha_final).then(dataFromDB => {
attendance_schedule = dataFromDB as any;
// Immediately launch your extra (2nd step) requests, without waiting for other 1st step requests
// Make sure to return when all new extra requests are done, or a Promise
// that fullfills when so.
return Promise.all(attendance_schedule.map(async (element) => {
let start_date = element.date_start.toString();
let end_date = element.date_end.toString();
const item = await supabaseService.getTrackedByDateAndIDArray(start_date, end_date);
console.log(item);
attendanceInMonthly.push(item);
});
}),
// etc. for the rest of 1st step requests
];
await Promise.all(promises);
res.json(attendanceInMonthly);

Batch commit been called before batch finishes

I'm getting "Cannot modify a WriteBatch that has been committed." in this snippet of code. Although, I'm sure why batch.commit() is not waiting for the forEach finishes.
const db = admin.firestore();
const batch = db.batch();
const channelIds = [];
const messages = data
.map((item) => {
if (!item || !item.phone_number)
return null;
const msg = pupa(message, item);
if (!channelIds.includes(item.channel.id))
channelIds.push(item.channel.id);
return {
...item,
message: msg
};
})
.filter((msg) => msg);
logger.info(`Creating messages/${messageId}/sms entries. [Count = ${messages.length}]`);
// From all channels included in the messages array, it fetchs its remaining sms credits.
channelIds.forEach(async (channelId) => {
const subscriptionDetails = (await admin.firestore()
.collection('channels')
.doc(channelId)
.collection('subscription')
.doc('details')
.get()).data();
const creditsRemaining = subscriptionDetails.limits.snapshot.sms_notifications - subscriptionDetails.limits.used.sms_notifications;
// Sends messages according to its respective channel ID and channel remaining credits.
messages
.filter((item) => item.channel.id === channelId)
.slice(0, creditsRemaining)
.forEach((msg) => {
batch.set(db.collection('messages')
.doc(messageId)
.collection('sms')
.doc(), {
phone_number: msg.phone_number,
message: msg.message
});
});
});
await batch.commit();
EDIT: I fixed this issue by wrapping the forEach in a Promise. Thanks!
It seems you're missing an await before batch.set(db.collection('messages')..., which means that your await batch.commit() gets run before all batch.set() calls have completed.

Axios, request: cannot ignore socket hang up (ECONNRESET) error

I'm working on a web scraping software using axios and libraries on node.js. Here is part of the code.
The problem is, I cannot pass through Error: socket hang up error specifically. When other errors happend, for example, Error: getaddrinfo ENOTFOUND, Error: Request failed with status code 404, Error: Request failed with status code 403, Error: unable to verify the first certificate, the script pass through these errors and proceed the next URLs with no issue. However, seemingly some site causes Error: socket hang up error and the error makes the script exit with exit code of 0 (success).
I have no clue what is happening around the error, why the only error causes the script end, and no detailed error info or errored exit code. How can I prevent the script from exiting because of the error? Thansk.
async function getData( url, rank="-1" ){
var obj ;
let source = CancelToken.source();
setTimeout(() => {
source.cancel();
}, 20000);
await axios
.get( url , { cancelToken: source.token, timeout: 20000 })
.then((response) => {
if (response.status === 200){
const html = response.data;
const $ = cheerio.load(html);
obj = {
rank: rank ,
url: url,
title: title = $('title').text(),
keywords: keywords = $("meta[name='Keywords']").attr('content'),
description: description = $("meta[name='Description']").attr('content'),
h1: Array.from($('h1 ')).map(a => $(a).text() ),
h2: Array.from($('h2 ')).map(a => $(a).text() ),
status: 0
}
}
})
.catch((error) => {
if (error.code === "ECONNRESET") {
console.log("Timeout occurs");
// return;
}
});
if (! obj){
obj = {
rank: rank,
url: url,
status: 1
}
}
return obj ;
}
// calling the above function in batch something like this
// I doubt following code HAS the problem but just in case
... // some code here
promises = [];
websitelists_obj["url"].forEach((item, i) => {
promises.push(
function(){
return new Promise((resolve, reject) => {
console.log("Started:", item)
data = getData(websitelists_obj["url"][i], websitelists_obj["rank"][i])
resolve(
data
)
})
.then((data) => { console.log("Completed:", item); return data; })
}
)
});
... // some code here
while (promises.length) {
array = promises.splice(0, 20).map(f => f());
result = await Promise.all( array );
obj = obj.concat(result);
}

How to assert on callback when using jest.useFakeTimers

I'm trying to understand how to correctly unit test a custom debounce method we have:
// function we want to test
function debounce(func, wait = 100) {
let timeout;
return (...args) => {
clearTimeout(timeout);
timeout = setTimeout(() => {
func.apply(this, args);
}, wait);
};
}
Here is the Jest unit test that is failing:
describe('DebounceExample', () => {
beforeAll(jest.useFakeTimers);
afterAll(jest.useRealTimers);
it('should debounce', () => {
// given
const data = 'Chuck Norris is faster than you think';
const debounceTime = 5000;
const callback = jest.fn();
const debouncedFunction = debounce(callback, debounceTime);
// when
debouncedFunction(data);
// then
expect(callback).not.toHaveBeenCalled();
// when
jest.runAllTimers(); // jest.advanceTimersByTime(debounceTime);
// then
expect(callback).toHaveBeenCalled();
});
});
Failure:
Error: expect(jest.fn()).toHaveBeenCalled()
Expected number of calls: >= 1
Received number of calls: 0 Jest
I've also tried the solution here: https://stackoverflow.com/a/52638566/704681 without success
Workaround: the only way I'm able to test it so far:
it('should denounce - workaround', (done) => {
// given
const data = 'Chuck Norris is faster than you think';
const debounceTime = 5000;
const callback = jest.fn((param) => {
// then
expect(param).toEqual(data);
done();
});
const debouncedFunction = debounce(callback, debounceTime);
// when
debouncedFunction(data);
// then
expect(callback).not.toHaveBeenCalled();
// when (see 'then' inside callback implementation)
jest.runAllTimers(); // jest.advanceTimersByTime(debounceTime);
});

stub never called with sinon and nodejs using chai-as-promised

i'm facing a issue with my unit test, stuck completely, the code is simple, please need to understand what's going on, my stub is never called, the set seems to be correct, here the code:
let strategy = fixtures.load('strategy')
chai.use(chaiAsPromised)
describe.only('Spawn Order Job', () => {
let getPositionsStub, createJobStub, daoStub,sandbox
beforeEach(()=>{
sandbox = sinon.createSandbox()
daoStub = sandbox.stub(dao, 'updateActiveOrders').resolves(true) //async
getPositionsStub = sandbox.stub(strategyModule, 'getPositions') //sync
createJobStub = sandbox.stub(helpers, 'createJob') //sync
createJobStub.returns(true)
getPositionsStub.resolves([{fake:'t'}, {fake:'t'}])
})
afterEach(()=>{
sandbox.restore()
})
//OK
it('Should failed with no param, type error context', ()=> {
const promise = spawnOrderJob()
expect(promise).to.be.rejectedWith(TypeError)
})
//OK
it('Should throw error timeout order', () => {
getPositionsStub.resolves([{fake:'t'}, {fake:'t'}])
strategy.lastDateOrder = new Date()
const ctx = { state: {strategy, dashboard, position:null}}
const action = {b: true, s: false}
const promise = spawnOrderJob(action, ctx)
expect(getPositionsStub.called).to.be.true
expect(daoStub.called).to.be.false
expect(createJobStub.called).to.be.false
expect(promise).to.be.rejectedWith(ORDER_ERROR, 'Timeout between order not expired.')
})
//KO stub never called
it.only('Should pass validation on buy', () => {
strategy.lastDateOrder = 0
const ctx = { state: {strategy, dashboard, position: null }}
const action = {b: true, s: false}
const promise = spawnOrderJob(action, ctx)
expect(promise).to.be.fulfilled
expect(getPositionsStub.called).to.be.true //ok
expect(createJobStub.called).to.be.true //never callled ????
expect(daoStub.called).to.be.true //never called ????
})
})
Want to understand what's going now there, the call are correct imo, running with mocha 5.2
Helpers.js : function is described as follow:
async function spawnOrderJob(action, ctx) {
try {
const { strategy, dashboard, position } = ctx.state
const {b, s} = action
//check in case strategy context
if (strategy) {
//pass validation buy contnext
if (b) {
//this stub is working
const positions = await strategyModule.getPositions(ctx)
const { maxPosition } = strategy.allocatedBTC
const { activeOrders, maxActiveOrders, timeBetweenOrder, lastDateOrder } = strategy
debug('Active orders:', strategy.activeOrders)
debug('Position:', positions.length)
if (activeOrders >= maxActiveOrders)
throw new ORDER_ERROR('Max active orders reach.')
if (positions.length + activeOrders >= maxPosition)
throw new ORDER_ERROR('Max positions reach.')
if (!timeoutExpired(lastDateOrder, timeBetweenOrder))
throw new ORDER_ERROR('Timeout between order not expired.')
//increment active orders counter
//stub fail, but not called at all
await dao.updateActiveOrders(strategy, true)
}
//Sell context
if (s) {
if (!position)
throw new ORDER_ERROR('No position to sell')
}
}
//stub fail, but called internally
return createJob(constants.DASHBOARD_CREATE_ORDER, {
orderType: b ? 'BUY' : 'SELL',
title: `Strategy create order ( ${ b ? 'BUY' : 'SELL'} )`,
strategy,
dashboard,
position
})
} catch (e) {
throw e
}
}
function createJob(name, data){
//shortcut queue.create (kue.js)
return queue.c(name,data)
}
module.exports = {
createJob,
spawnOrderJob
}
DAO
const updateActiveOrders = async (strategy, increment) => {
try {
const s = await model.findOne({_id: strategy._id})
if (!s) throw new Error('Strategy not found.')
s.activeOrders = increment ? s.activeOrders+1 :s.activeOrders-1
s.lastDateOrder = new Date()
return await s.save()
}catch(e){
throw e
}
}
module.exports = {updateActiveOrders}

Resources