hapijs testing with server.inject error catched - node.js

i am trying to test hapijs with server.inject
/// <reference path="../../typings/index.d.ts" />
import * as chai from "chai";
let assert = chai.assert;
import server from "../../src/server";
import UserController from '../../src/controllers/userController';
import UserRepository from '../../src/libs/repository/mongo/userRepository';
import {IUser, IUserActivation, IUserCreate} from "../../src/libs/repository/interfaces";
describe("routes/user", function() {
const userController = new UserController(server, new UserRepository());
// ========================== [ ACTIVATE ] ==========================
it.only("/activate: should activate a user", function(done) {
let user: IUserActivation = {
'_id': '1234566737465',
'token': '123234523542345'
};
let url = '/api/users/' + user._id + '/' + user.token;
const request = {
method: 'PUT',
url: url,
payload: user
};
server.inject(request).then((response) => {
let res = JSON.parse(response.payload);
//assert.strictEqual(res.success, true, '/users/{id}/{token}')
chai.expect(res.success).to.deep.equal(false);
chai.expect(res.success).to.deep.equal(true);
done();
}).catch((error) => {
console.log(error.message);
});
});
});
The response.success attribute is true. So normally the test should fail because of chai.expect(res.success).to.deep.equal(false);.
But the test fails with the message: Error: timeout of 2000ms exceeded. Ensure the done() callback is being called in this test.
When removing the catch clause it also fails with the timeout error.
If I add done() to the end of the catch-clause the test is passing. That is wrong behavior, because the test should fail.
What can i do to get the expected behavior? thanks in advance.

The problem would be that server.inject returns a promise. As long as your using a recent version of Mocha, it can handle that but you don't need to worry about calling done() and rather return the server.inject.
describe("routes/user", function() {
const userController = new UserController(server, new UserRepository());
// ========================== [ ACTIVATE ] ==========================
it.only("/activate: should activate a user", function() { //No done needed
let user: IUserActivation = {
'_id': '1234566737465',
'token': '123234523542345'
};
let url = '/api/users/' + user._id + '/' + user.token;
const request = {
method: 'PUT',
url: url,
payload: user
};
//Return the promise
return server.inject(request).then((response) => {
let res = JSON.parse(response.payload);
//assert.strictEqual(res.success, true, '/users/{id}/{token}')
chai.expect(res.success).to.deep.equal(false);
chai.expect(res.success).to.deep.equal(true);
}); //No catch needed.
});
});

Related

Why am I only getting Mailgun.js error in Cloud Run?

I'm trying to send an email using Mailgun's npm client - Mailgun.js.
When sending in development mode, everything works correctly. But when I upload the Node server to Cloud Run, something breaks.
Here is the code in the sendEmail helper file:
import formData from 'form-data';
import Mailgun from 'mailgun.js';
const sendEmail = async ({ to, subject, text, attachment, scheduledDate }) => {
const mailgun = new Mailgun(formData);
const mg = mailgun.client({
username: 'api',
key: process.env.MAILGUN_KEY,
url: 'https://api.eu.mailgun.net'
});
const data = {
from: `<myemail#mydomain.com>`,
to,
subject,
text
};
if (attachment) {
data.attachment = attachment;
}
if (scheduledDate) {
data['o:deliverytime'] = new Date(scheduledDate).toUTCString();
}
try {
const result = await mg.messages.create(process.env.MAILGUN_DOMAIN, data);
if (result.status && result.status !== 200) {
throw ({ code: result.status, message: result.message });
}
return true;
} catch(err) {
console.log(err);
return { error: err };
}
};
export default sendEmail;
And then in another file:
import { Router } from 'express';
import generateInvoicePDF from '../helpers/generateInvoicePDF.js';
import sendEmail from '../helpers/sendEmail.js';
const router = Router();
router.post('/email', async (req, res, next) => {
try {
const file = await generateInvoicePDF(invoice);
if (file?.error) {
throw ({ code: pdf.error.code, message: pdf.error.message });
}
const email = await sendEmail({
to: 'testemail#example.com',
subject: 'Invoice',
text: 'Test',
attachment: { filename: 'Invoice', data: file }
});
if (email?.error) {
throw ({ code: email.error.code, message: email.error.message });
}
res.status(200).json({ success: true });
} catch(err) {
next(err);
}
});
export default router;
The error I get when in production mode in Cloud Run's logs is:
TypeError: fetch failed
at Object.processResponse (node:internal/deps/undici/undici:5575:34)
at node:internal/deps/undici/undici:5901:42
at node:internal/process/task_queues:140:7
at AsyncResource.runInAsyncScope (node:async_hooks:202:9)
at AsyncResource.runMicrotask (node:internal/process/task_queues:137:8) {
cause: TypeError: object2 is not iterable
at action (node:internal/deps/undici/undici:1661:39)
at action.next (<anonymous>)
at Object.pull (node:internal/deps/undici/undici:1709:52)
at ensureIsPromise (node:internal/webstreams/util:172:19)
at readableStreamDefaultControllerCallPullIfNeeded (node:internal/webstreams/readablestream:1884:5)
at node:internal/webstreams/readablestream:1974:7
}
Why the hell does it work in development mode on my local machine, but not when uploaded to Cloud Run?
For anyone struggling with something similar - I eventually figured out the problem.
On my local machine, where everything was working as expected, I'm using Node v16.15.0, whereas in the Dockerfile, I had specified
FROM node:latest
and therefore Cloud Run was using a newer version, which led to the problems...
I've now deployed using version 16.15.0 and everything works fine

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.

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");
});

Unit Testing Express with mocha/chai/sinon - how do I test my res.send object shape?

I am unit testing the individual components that lead up to an API response, in other words, I'm testing it independently of a route since every route runs through this component
I need to test that the function responsible for sending my express response is the correct shape, but without sending an actual HTTP request I can't figure out how to test it.
Here is my component
'use strict'
const moment = require('moment')
module.exports = (req, res, payload) => {
try {
let data = []
if (payload.token) data.push({ token: payload.token })
data.push({ [payload.resource]: payload.data })
res.send({
status: 'OK',
recordCount: payload.data.length,
startTimestamp: req.start.toDate(),
endTimestamp: moment().toDate(),
timeTaken: moment().toDate().getTime() - req.start.toDate().getTime(),
data: data
})
} catch (error) {
return res.status(500).json({
errors: [{
location: 'n/a',
param: 'n/a',
msg: 'something happened when generating the response'
}]
})
}
}
here is my current test ...
const chai = require('chai')
const sinonChai = require('sinon-chai')
const { mockReq, mockRes } = require('sinon-express-mock')
const moment = require('moment')
const present = require('../../src/lib/present')
chai.use(sinonChai)
describe('unit test the present lib method', () => {
it('should return the expected shape', (done) => {
const req = mockReq({
start: moment().toDate(),
body: {}
})
const res = mockRes()
const shape = present(req, res, {
resource: 'empty_array',
data: []
})
shape.should.have.own.property('data') // doesnt work
// AssertionError: expected { Object (append, attachement, ...) } to have own property 'data'
done()
})
})
To properly test schema of response you need to do E2E test, which requires you, to send an API call.
If you want to test just logic, inside of the route, you can always extract it to some service, and just test this service.
You can read the following article: https://www.freecodecamp.org/news/how-to-mock-requests-for-unit-testing-in-node-bb5d7865814a/

Babel 5.8.38 ignore await in Node.js

I have problem with Babel 5.8.38 and await in Node.js
My code seems like that:
/**
* Imports
*/
import {signature} from '../core/antpool';
import log from '../core/logging';
import config from '../config';
import {sendLog as sendEmailLog, EmailTemplate} from '../core/email';
import {rethinkdb, Decorators as DBDecorators} from '../core/db';
import request from 'request';
const tables = {
AntpoolAccount: 'CronAntpoolAccount'
};
class Antpool {
#DBDecorators.table(tables.AntpoolAccount)
static async account(coin) {
var nonce = Date.now();
request.post({
url: config.antpool.baseUrl + '/api/account.htm',
json: true,
form: {
key: config.antpool.key,
nonce: nonce,
signature: signature(nonce),
coin: coin
}
}, function (err, httpResponse, body) {
if (err || httpResponse.statusCode != 201) {
log.error(err, '[CRON][Antpool][Account] Connection problem');
sendEmailLog(EmailTemplate.LOG, {
message: '[CRON][Antpool][Account] Connection problem'
});
return false;
}
var out = JSON.parse(body);
if (out.code != 0) {
log.error(err, '[CRON][Antpool][Account] Error response('+out.code+'): '+out.message);
sendEmailLog(EmailTemplate.LOG, {
message: '[CRON][Antpool][Account] Error response('+out.code+'): '+out.message
});
return false;
}
// Add to database
let obj = {
earn24: out.data.earn24Hours,
earnTot: out.data.earnTotal,
paidOut: out.data.paidOut,
balance: out.data.balance,
createdAt: new Date()
};
// Insert into database
let insert = await this.table.insert(obj).run();
if(insert) {
return true;
} else {
return false;
}
});
}
}
/**
* Exports
*/
export {Antpool};
What I get is only error which have problem with await.
SyntaxError: .../antpool.js: Unexpected token (59:22)
// Insert into database
let insert = await this.table.insert(obj).run();
I am thinking what can be solution for accept the await. Is it little weird because in other parts of code await works well.
Not sure what the problem exactly is, but spent about two days finding the issue.
I am calling scripts with:
/**
* Automatically hook babel into all node requires.
*/
require('babel/register')({
optional: ['es7.asyncFunctions', 'es7.classProperties', 'es7.decorators']
});
/**
* Start application worker.
*/
require('./src/worker');
Any help is very appreciated.
The keyword await can be used only in functions that are marked as async. Function in which you are trying to use await:
, function (err, httpResponse, body) {
...
is not marked as async and therefore it can't be compiled.
More info: MDN article on async/await

Resources