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

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

Related

How to make kuzzle-device-manager plugin API actions works?

I successfully installed and loaded kuzzle-device-manager in the backend file:
import { Backend } from 'kuzzle';
import { DeviceManagerPlugin } from 'kuzzle-device-manager';
const app = new Backend('playground');
console.log(app.config);
const deviceManager = new DeviceManagerPlugin();
const mappings = {
updatedAt: { type: 'date' },
payloadUuid: { type: 'keyword' },
value: { type: 'float' }
}
deviceManager.devices.registerMeasure('humidity', mappings)
app.plugin.use(deviceManager)
app.start()
.then(async () => {
// Interact with Kuzzle API to create a new index if it does not already exist
console.log(' started!');
})
.catch(console.error);
But when i try to use controllers from that plugin for example device-manager/device with create action i get an error output.
Here is my "client" code in js:
const { Kuzzle, WebSocket } = require("kuzzle-sdk")
const kuzzle = new Kuzzle(
new WebSocket('KUZZLE_IP')
)
kuzzle.on('networkError', error => {
console.error('Network Error: ', error);
})
const run = async () => {
try {
// Connects to the Kuzzle server
await kuzzle.connect();
// Creates an index
const result = await kuzzle.query({
index: "nyc-open-data",
controller: "device-manager/device",
action: "create",
body: {
model: "model-1234",
reference: "reference-1234"
}
}, {
queuable: false
})
console.log(result)
} catch (error) {
console.error(error.message);
} finally {
kuzzle.disconnect();
}
};
run();
And the result log:
API action "device-manager/device":"create" not found
Note: The nyc-open-data index exists and is empty.
We apologize for this mistake in the documentation, the device-manager/device:create method is not available because the plugin is using auto-provisioning until the v2.
You should send a payload to your decoder, the plugin will automatically provision the device if it does not exists https://docs.kuzzle.io/official-plugins/device-manager/1/guides/decoders/#receive-payloads

Jest Test suite failed to run - AssertionError [ERR_ASSERTION]: opts.entryPoint required

We had a test running successfully for our code that uses the notifications-node-client to send emails, eg
import { NotifyClient } from 'notifications-node-client';
import notify from './notify';
import config from '../../../config';
jest.mock('fs');
jest.mock('notifications-node-client');
describe('notify', () => {
const EMAIL = 'user#test.com';
const REGION_GB = 'gb';
const REGION_NI = 'ni';
const feedbackOptions = {
personalisation: {
satisfactionLevel: 'satisfied',
improvements: 'improvements',
},
};
test('for original GB submission sendEmail should be called and true returned', async () => {
NotifyClient.prototype.sendEmail.mockReturnValue({
body: {
id: 1,
},
});
expect(await notify(EMAIL, 'submissionSuccess', REGION_GB)).toBeTruthy();
expect(NotifyClient.prototype.sendEmail).toHaveBeenCalledWith(
config.notify.templates.submissionSuccess.gb,
EMAIL,
undefined,
);
});
...
Having swapped out our Winston logging solution to use #dwp/node-logger, the 'notify' tests now do not run, failing with
● Test suite failed to run
AssertionError [ERR_ASSERTION]: opts.entryPoint required
at new Thrift (node_modules/thriftrw/thrift.js:64:5)
at Object.<anonymous> (node_modules/jaeger-client/src/thrift.js:21:20)
at Object.<anonymous> (node_modules/jaeger-client/src/reporters/remote_reporter.js:15:1)
All of the other test suites in the project still run successfully.
Could anyone point me in the right direction about what change to make?
The code that we're testing is
import { NotifyClient } from 'notifications-node-client';
import config from '../../../config/index';
import { RegionKeys } from '../../../config/types';
import logger from '../../../xxxx/lib/logger';
type TemplateId = 'submissionSuccess' | 'supportingEvidence' | 'feedbackTemplateId';
type FeedbackOptions = {
personalisation: {
satisfactionLevel: string,
improvements: string,
},
reference?: string,
}
const { apiKey, templates, proxy } = config.notify;
export default async function notify(
email: string,
templateId: TemplateId,
region: RegionKeys,
options?: FeedbackOptions,
): Promise<boolean> {
let notifyTemplate;
let notifyApiKey;
let notifyOptions;
try {
if (templateId === 'feedbackTemplateId' && !options) {
throw new Error(`Unable to send email - mismatch between template ID (${templateId}) and options supplied`);
} else if (options && templateId !== 'feedbackTemplateId') {
notifyOptions = {};
} else {
notifyOptions = options;
}
const notifyClient = new NotifyClient(apiKey[region]);
notifyClient.setProxy(proxy);
notifyTemplate = templateId === 'feedbackTemplateId' ? templates.feedbackTemplateId : templates[templateId][region];
logger.debug(`apiKey: ${notifyApiKey}`);
logger.debug(`notify template Id: ${notifyTemplate}`);
logger.debug(`proxy: ${JSON.stringify(proxy)}`);
const response = await notifyClient.sendEmail(notifyTemplate, email, notifyOptions);
if (response.body) {
logger.info(`confirmation email sent to ${email} and relates to message id ${response.body.id}`);
}
return true;
} catch (err: any) {
logger.error(`there was an error sending the message: ${err.message}`);
return false;
}
}

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/

Resources