What would be the best way to use nodemailer with Cypress? I've been playing with the code bellow for the while now but with no avail. I am getting an error "cy.task('sendMail') failed with the following error:
sendAnEmail is not a function
Because this error occurred during a after all hook we are skipping all of the remaining tests."
Thanks for any tips and advices.
//Cypress config file
const { defineConfig } = require("cypress");
const sendAnEmail = require("nodemailer")
module.exports = defineConfig({
pageLoadTimeout: 180000,
e2e: {
setupNodeEvents(on, config) {
on('task', {
sendMail (message) {
return sendAnEmail(message);
}
})
},
},
});
//Nodemailer file
const sendAnEmail = (message) => {
function sendAnEmail()
const nodemailer = require('nodemailer');
const sgTransport = require('nodemailer-sendgrid-transport');
const options = {
auth: {
user: "glorioustester123#outlook.com",
pass: "********."
}
}
const client = nodemailer.createTransport(sgTransport(options));
const email = {
from: 'glorioustester123#outlook.com',
to: 'some.email#gmail.com',
subject: 'Hello',
text: message,
html: '<b>Hello world</b>'
};
client.sendMail(email, function(err, info) {
return err? err.message : 'Message sent: ' + info.response;
});
}
//The Cypress test file
/// <reference types = "cypress" />
after(() => {
cy.task('sendMail', 'This will be output to email address')
.then(result => console.log(result));
})
//zadanie A
it("navstiv stranku a vyhladaj a elementy v casti Framework Support", ()=>{
cy.visit('https://sortablejs.github.io/Sortable/#cloning')
cy.get('.col-6').find('a')
})
//zadanie B
it("navstiv stranku a vyhladaj prvy a element casti v Framework Support", ()=>{
cy.visit('https://sortablejs.github.io/Sortable/#cloning')
cy.get('[href="https://github.com/SortableJS/Vue.Draggable"]')
cy.get('.col-6').contains('a')
//contains najde prvy vyskyt, v tomto pripade to pasuje do zadania
})
//zadanie C
it("navstiv stranku vyhladaj posledny a element v casti Framework Support ", ()=>{
cy.visit('https://sortablejs.github.io/Sortable/#cloning')
cy.get('[href="https://github.com/SortableJS/ember-sortablejs"]')
})
You nodemailer file needs adjusting a bit. The is no export which is why the message sendAnEmail is not a function
const nodemailer = require('nodemailer');
const sgTransport = require('nodemailer-sendgrid-transport');
export function sendAnEmail(message)
const options = {
...
}
const client = nodemailer.createTransport(sgTransport(options));
const email = {
...
};
client.sendMail(email, function(err, info) {
return err? err.message : 'Message sent: ' + info.response;
});
}
Also, in cypress.config.js import it with a relative path
const { defineConfig } = require("cypress");
const sendAnEmail = require("./nodemailer")
and to be a clean-coder, us a different name from the npm package (something like
const sendAnEmail = require("./send-an-email")
Related
**Edit: Re-written with a simple example that works first:
So I've got a test file and 2 modules.
moduleA has a dependency, moduleB
// moduleA.js
const ModuleB = require('./moduleB');
function functionA() {
return 20 + ModuleB.functionB();
};
module.exports = { functionA };
// moduleB.js
const functionB = () => {
return 10;
}
module.exports = { functionB }
My test file stubs out functionB (returned from moduleB) using proxyquire:
const sinon = require('sinon');
const proxyquire = require('proxyquire');
describe('Unit Tests', function() {
it('should work', () => {
const mockedFn = sinon.stub();
mockedFn.returns(30);
const copyModuleA = proxyquire('./moduleA', {
'./moduleB': {
functionB: mockedFn
}
});
console.log(copyModuleA.functionA());
})
});
So it outputs 50 (stubbed functionB 30 + functionA 20)
Now I'm trying to take this example into my code:
moduleA in this case is a file called validation.js. It is dependent on moduleB, in this case a sequelize model, Person, with the function I want to mock: findOne
validation.js exports module.exports = { validateLogin };, a function that calls validate, which returns a function that uses Person.findOne()
So in my mind, as with the simple example, I need to create a stub, point to the validation module in proxyquire, and reference the dependency and its findOne function. Like this:
const stubbedFindOne = sinon.stub();
stubbedFindOne.resolves();
validationModule = proxyquire('../../utils/validation', {
'../models/Person': {
findOne: stubbedFindOne
}
});
This should stub Person.findOne in validation.js. But it doesn't seem to. And I have no idea why.
let validationModule;
describe('Unit Tests', () => {
before(() => {
const stubbedFindOne = sinon.stub();
stubbedFindOne.resolves();
validationModule = proxyquire('../../utils/validation', {
'../models/Person': {
findOne: stubbedFindOne
}
});
})
it.only('should return 422 if custom email validation fails', async() => {
const wrongEmailReq = { body: {email: 'nik#hotmail.com'} };
const res = {
statusCode: 500,
status: (code) => {this.statusCode = code; return this},
};
const validationFn = validationModule.validateLogin();
const wrongEmail = await validationFn(wrongEmailReq, res, ()=>{});
expect(wrongEmail.errors[0].msg).to.be.equal('Custom Authorisation Error');
return;
})
And this is my validation.js file:
const Person = require('../models/Person');
// parallel processing
const validate = validations => {
return async (req, res, next) => {
await Promise.all(validations.map(validation => validation.run(req)));
const errors = validationResult(req);
if (errors.isEmpty()) {
return next();
}
const error = new Error();
error.message = process.env.NODE_ENV === 'development'? 'Validation Failed':'Error';
error.statusCode = !errors.isEmpty()? 422:500;
error.errors = errors.array({onlyFirstError: true});
next(error);
return error;
};
};
const validateLogin = () => {
const validations = [
body('email')
.isString()
// snip
.custom(async (value, {req}) => {
try{
const person = await Person.findOne({ where: { email: value } });
if(!person) return Promise.reject('Custom Authorisation Error');
} catch(err) {
throw err;
}
})
.trim(),
];
return validate(validations);
}
module.exports = {
validateLogin
};
So the code in both the small sample and my app is correct, apart from how I stub the function. It shouldn't resolve or reject anything (I tried both out of desperation). It should return null in order to satisfy the conditional rather than jump to the catch block:
try{
const person = await Person.findOne({ where: { email: value } });
if(!person) return Promise.reject('Custom Authorisation Error');
} catch(err) {
throw err;
}
Hope the simple example helps someone else with proxyquire though
it is showing Unexpected value for STRIPE_SIGNING_SECRET error even after checking it many times in the env file
the terminal shows everything created but it does not reach firebase database I am thinking there is a error in the code
the stripe dashboard also says connected
I am using the forward to local host line in git terminal
webhook code
import { buffer } from "micro";
import * as admin from 'firebase-admin'
//secure a connection to Firebase from backend
const serviceAccount = require('../../../permissions.json');
const app = !admin.apps.length ? admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
})
: admin.app();
// establish connection to stripe
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const endpointSecret = process.env.STRIPE_SIGNING_SECRET;
if (typeof endpointSecret !== "string") {
console.error("Unexpected value for STRIPE_SIGNING_SECRET");
// potentially throw an error here
}
const fulfillOrder = async (session) => {
//console.log('Fulfilling order', session)
return app
.firestore()
.collection("user")
.doc(session.metadata.email)
.collection("orders")
.doc(session.id)
.set({
amount: session.amount_total / 100,
amount_shipping: session.amount_total_details.amount_shipping / 100,
images: JSON.parse(session.metadata.images),
timestamp: admin.firestore.FieldValue.serverTimestamp(),
})
.then(() => {
console.log(`success: order ${session.id} had been added to db`);
});
};
export default async (req, res) =>{
if(req.method === 'post'){
const requestBuffer = await buffer(req);
const payload = requestBuffer.toString();
const sig = req.headers["stripe-signature"];
let event;
// verify that the event posted came from stripe
try{
event = stripe.webhooks.constructEvent(
payload,
sig,
endpointSecret);
} catch (err) {
console.log('ERROR', err.message)
return res.status(400).send(`Webhook error: ${err.message}`)
}
//handle the checkout event
if (event.type === 'checkout.session.completed') {
const session = event .data.object;
//fulfill the order...
return fulfillOrder(session)
.then(() => res.status(200))
.catch((err) => res.status(400).send(`Webhook error: ${err.message}`));
}
}
};
export const config = {
api: {
bodyParser: false,
externalResolver: true,
},
};
firebase rules
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow write: if false;
allow read: if true;
}
}
}
const endpointSecret = process.env.STRIPE_SIGNNING_SECRET;
Typo: STRIPE_SIGNNING_SECRET
To avoid the next issue, fix the other typo:
const sig = req.headers["stripe-signatur"];
stripe-signature
i am initializing a node js app with crucial data for the app to work from a database in index.js.
index.ts
import {getInitialData} from 'initData.ts';
export let APP_DATA: AppData;
export const initializeAppData = async () => {
try {
APP_DATA = (await getInitialData()) as AppData;
if (process.env.NODE_ENV !== 'test') {
initializeMongoose();
startServer();
}
} catch (error) {
console.log(error);
}
};
initData.ts
let dbName: string = 'initialData';
if (process.env.NODE_ENV === 'test') {
dbName = 'testDb';
}
const uri = `${process.env.MONGODB_URI}/?maxPoolSize=20&w=majority`;
export async function getInitialData() {
const client = new MongoClient(uri);
try {
await client.connect();
const database = client.db(dbName);
const configCursor = database
.collection('config')
.find({}, { projection: { _id: 0 } });
const config = await configCursor.toArray();
const aaoCursor = database
.collection('aao')
.find({}, { projection: { _id: 0 } });
const aao = await aaoCursor.toArray();
return { config, aao };
} catch {
(err: Error) => console.log(err);
} finally {
await client.close();
}
}
I'm using this array in another file and import it there.
missionCreateHandler
import { APP_DATA } from '../index';
export const addMissionResources = (
alarmKeyword: AlarmKeyword,
newMission: MissionDocument
) => {
const alarmKeywordObject = APP_DATA?.aao.find(
(el) => Object.keys(el)[0] === alarmKeyword
);
const resourceCommand = Object.values(alarmKeywordObject!);
resourceCommand.forEach((el) => {
Object.entries(el).forEach(([key, value]) => {
for (let ii = 1; ii <= value; ii++) {
newMission.resources?.push({
initialType: key,
status: 'unarranged',
});
}
});
});
};
I'm setting up a mongodb-memory-server in globalSetup.ts for Jest and copy the relevant data to the database from json-files.
globalSetup.ts
export = async function globalSetup() {
const instance = await MongoMemoryServer.create({
instance: { dbName: 'testDb' },
});
const uri = instance.getUri();
(global as any).__MONGOINSTANCE = instance;
process.env.MONGODB_URI = uri.slice(0, uri.lastIndexOf('/'));
process.env.JWT_SECRET = 'testSECRET';
const client = new MongoClient(
`${process.env.MONGODB_URI}/?maxPoolSize=20&w=majority`
);
try {
await client.connect();
const database = client.db('testDb');
database.createCollection('aao');
//#ts-ignore
await database.collection('aao').insertMany(aao['default']);
} catch (error) {
console.log(error);
} finally {
await client.close();
}
};
missionCreateHandler.test.ts
test('it adds the correct mission resources to the array', async () => {
const newMission = await Mission.create({
address: {
street: 'test',
houseNr: 23,
},
alarmKeyword: 'R1',
});
const expected = {
initialType: 'rtw',
status: 'unarranged',
};
addMissionResources('R1', newMission);
expect(newMission.resources[0].initialType).toEqual(expected.initialType);
expect(newMission.resources[0].status).toEqual(expected.status);
});
When runing the test, i get an 'TypeError: Cannot convert undefined or null to object at Function.values ()'. So it seems that the APP_DATA object is not set. I checked that the mongodb-memory-server is set up correctly and feed with the needed data.
When i hardcode the content of APP_DATA in index.ts, the test runs without problems.
So my questions are: How is the best practice to set up initial data in a node js app and where to store it (global object, simple variable and import it in the files where needed)? How can the test successfully run, or is my code just untestable?
Thank you!
so I am building a blockchain in nodejs and when I run the application, my constructor variables pass undefined, although I made it so it passes down variables as an object in the index file. The following relevant files will be shown below...
PubNub for pubsubbing:
const PubNub = require('pubnub');
const credentials = {
publishKey: 'pub-c-274ab4f3-redacted',
subscribeKey: 'sub-c-fe7d959c-redacted',
secretKey: 'sec-c-cannotDisplay'
};
const CHANNELS = {
TEST: 'TEST',
BLOCKCHAIN: 'BLOCKCHAIN',
TRANSACTION: 'TRANSACTION'
};
class PubSub {
constructor({ blockchain, transactionPool, wallet }) {
this.blockchain = blockchain;
this.transactionPool = transactionPool;
this.wallet = wallet;
this.pubnub = new PubNub(credentials);
//where you put uuid
this.pubnub.subscribe({ channels: Object.values(CHANNELS) });
this.pubnub.addListener(this.listener());
}
broadcastChain() {
this.publish({
channel: CHANNELS.BLOCKCHAIN,
message: JSON.stringify(this.blockchain.chain)
});
}
broadcastTransaction(transaction) {
this.publish({
channel: CHANNELS.TRANSACTION,
message: JSON.stringify(transaction)
});
}
subscribeToChannels() {
this.pubnub.subscribe({
channels: [Object.values(CHANNELS)]
});
}
listener() {
return {
message: messageObject => {
const { channel, message } = messageObject;
console.log(`Message received. Channel: ${channel}. Message: ${message}`);
const parsedMessage = JSON.parse(message);
switch(channel) {
case CHANNELS.BLOCKCHAIN:
this.blockchain.replaceChain(parsedMessage, true, () => {
this.transactionPool.clearBlockchainTransactions(
{ chain: parsedMessage.chain }
);
});
break;
case CHANNELS.TRANSACTION:
if (!this.transactionPool.existingTransaction({
inputAddress: this.wallet.publicKey
})) {
this.transactionPool.setTransaction(parsedMessage);
}
break;
default:
return;
}
}
}
}
publish({ channel, message }) {
// there is an unsubscribe function in pubnub
// but it doesn't have a callback that fires after success
// therefore, redundant publishes to the same local subscriber will be accepted as noisy no-ops
this.pubnub.publish({ message, channel });//channel,message
}
broadcastChain() {
this.publish({
channel: CHANNELS.BLOCKCHAIN,
message: JSON.stringify(this.blockchain.chain)
});
}
broadcastTransaction(transaction) {
this.publish({
channel: CHANNELS.TRANSACTION,
message: JSON.stringify(transaction)
});
}
}
const testPubSub = new PubSub()
{
testPubSub.publish({channel: CHANNELS.TEST, message: 'testing'});
}
module.exports = PubSub;
Main index:
const bodyParser = require('body-parser');
const express = require('express');
const request = require('request');
const path = require('path');
const Blockchain = require('./blockchain');
const PubSub = require('./app/pubsub');
const TransactionPool = require('./wallet/transaction-pool');
const Wallet = require('./wallet');
const TransactionMiner = require('./app/transaction-miner');
const PubSubNub = require('./app/pubsub.pubnub');
//127.0.0.1:6379
const isDevelopment = process.env.ENV === 'development';
//TRY PUBNUB (comment out)
/*const REDIS_URL = isDevelopment ?
'redis://127.0.0.1:6379' : //try 6379 19289
'redis://h:p602b6838e89da65c8c4d29a6a4f954452d1ece59c10b27a29ebf9808721cb8e2#ec2-35-153-115-238.compute-1.amazonaws.com:9819'//19289
*/
const DEFAULT_PORT = 3000;
const ROOT_NODE_ADDRESS =
`http://localhost:${DEFAULT_PORT}`;
const app = express();
const blockchain = new Blockchain();
const transactionPool = new TransactionPool();
const wallet = new Wallet();
//const pubsub = new PubSub({ blockchain, transactionPool, redisUrl: REDIS_URL });//redis
const pubsub = new PubSubNub({ blockchain, transactionPool, wallet }); // for PubNub //change back to PubSub if issues arise
const transactionMiner = new TransactionMiner({ blockchain, transactionPool, wallet, pubsub });
app.use(bodyParser.json());
app.use(express.static(path.join(__dirname, 'client/dist')));
//endpoint
app.get('/api/blocks', (req, res) => {
res.json(blockchain.chain);
});
app.get('/api/blocks/length', (req, res) => {
res.json(blockchain.chain.length);
});
app.get('/api/blocks/:id', (req, res) => {
const { id } = req.params;
const { length } = blockchain.chain;
const blocksReversed = blockchain.chain.slice().reverse();
let startIndex = (id-1) * 5;
let endIndex = id * 5;
startIndex = startIndex < length ? startIndex : length;
endIndex = endIndex < length ? endIndex : length;
res.json(blocksReversed.slice(startIndex, endIndex));
});
app.post('/api/mine', (req, res) => {
const { data } = req.body;
blockchain.addBlock({ data });
pubsub.broadcastChain();
res.redirect('/api/blocks');
});
app.post('/api/transact', (req, res) => {
const { amount, recipient } = req.body;
let transaction = transactionPool
.existingTransaction({ inputAddress: wallet.publicKey });
try {
if (transaction) {
transaction.update({ senderWallet: wallet, recipient, amount });
} else {
transaction = wallet.createTransaction({
recipient,
amount,
chain: blockchain.chain
});
}
} catch(error) {
return res.status(400).json({ type: 'error', message: error.message });
}
transactionPool.setTransaction(transaction);
pubsub.broadcastTransaction(transaction);
res.json({ type: 'success', transaction });
});
app.get('/api/transaction-pool-map', (req, res) => {
res.json(transactionPool.transactionMap);
});
app.get('/api/mine-transactions', (req, res) => {
transactionMiner.mineTransactions();
res.redirect('/api/blocks');
});
app.get('/api/wallet-info', (req, res) => {
const address = wallet.publicKey;
res.json({
address,
balance: Wallet.calculateBalance({ chain: blockchain.chain, address })
});
});
app.get('/api/known-addresses', (req, res) => {
const addressMap = {};
for (let block of blockchain.chain) {
for (let transaction of block.data) {
const recipient = Object.keys(transaction.outputMap);
recipient.forEach(recipient => addressMap[recipient] = recipient);
}
}
res.json(Object.keys(addressMap));
});
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'client/dist/index.html'));
});
const syncWithRootState = () => {
request({ url: `${ROOT_NODE_ADDRESS}/api/blocks` }, (error, response, body) => {
if (!error && response.statusCode === 200) {
const rootChain = JSON.parse(body);
console.log('replace chain on a sync with', rootChain);
blockchain.replaceChain(rootChain);
}
});
request({ url: `${ROOT_NODE_ADDRESS}/api/transaction-pool-map` }, (error, response, body) => {
if (!error && response.statusCode === 200) {
const rootTransactionPoolMap = JSON.parse(body);
console.log('replace transaction pool map on a sync with', rootTransactionPoolMap);
transactionPool.setMap(rootTransactionPoolMap);
}
});
};
if (isDevelopment) {
const walletFoo = new Wallet();
const walletBar = new Wallet();
const generateWalletTransaction = ({ wallet, recipient, amount }) => {
const transaction = wallet.createTransaction({
recipient, amount, chain: blockchain.chain
});
transactionPool.setTransaction(transaction);
};
const walletAction = () => generateWalletTransaction({
wallet, recipient: walletFoo.publicKey, amount: 5
});
const walletFooAction = () => generateWalletTransaction({
wallet: walletFoo, recipient: walletBar.publicKey, amount: 10
});
const walletBarAction = () => generateWalletTransaction({
wallet: walletBar, recipient: wallet.publicKey, amount: 15
});
for (let i=0; i<20; i++) {
if (i%3 === 0) {
walletAction();
walletFooAction();
} else if (i%3 === 1) {
walletAction();
walletBarAction();
} else {
walletFooAction();
walletBarAction();
}
transactionMiner.mineTransactions();
}
}
let PEER_PORT;
if (process.env.GENERATE_PEER_PORT === 'true') {
PEER_PORT = DEFAULT_PORT + Math.ceil(Math.random() * 1000);
}
const PORT = process.env.PORT || PEER_PORT || DEFAULT_PORT;
app.listen(PORT, () => {
console.log(`listening at localhost:${PORT}`);
if (PORT !== DEFAULT_PORT) {
syncWithRootState();
}
});
Error log:
main#goldengates.club [~/public_html/Cypher-Network]# npm run start
> Cypher-Network#1.0.0 start /home/main/public_html/Cypher-Network
> npm run build-client & node index.js
> Cypher-Network#1.0.0 build-client /home/main/public_html/Cypher-Network
> npm run clean && parcel build client/src/index.html --out-dir client/dist
> Cypher-Network#1.0.0 clean /home/main/public_html/Cypher-Network
> rm -rf .cache client/dist
/home/main/public_html/Cypher-Network/app/pubsub.pubnub.js:16
constructor({ blockchain, transactionPool, wallet }) {
^
TypeError: Cannot destructure property 'blockchain' of 'undefined' as it is undefined.
at new PubSub (/home/main/public_html/Cypher-Network/app/pubsub.pubnub.js:16:17)
at Object.<anonymous> (/home/main/public_html/Cypher-Network/app/pubsub.pubnub.js:99:20)
at Module._compile (internal/modules/cjs/loader.js:1158:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1178:10)
at Module.load (internal/modules/cjs/loader.js:1002:32)
at Function.Module._load (internal/modules/cjs/loader.js:901:14)
at Module.require (internal/modules/cjs/loader.js:1044:19)
at require (internal/modules/cjs/helpers.js:77:18)
at Object.<anonymous> (/home/main/public_html/Cypher-Network/index.js:10:19)
at Module._compile (internal/modules/cjs/loader.js:1158:30)
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! Cypher-Network#1.0.0 start: `npm run build-client & node index.js`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the Cypher-Network#1.0.0 start script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
npm ERR! A complete log of this run can be found in:
npm ERR! /home/main/.npm/_logs/2020-03-27T18_49_30_710Z-debug.log
⠋ Building...lscpu: failed to determine number of CPUs: /sys/devices/system/cpu/possible: No such file or directory
✨ Built in 17.71s.
client/dist/src.a02dd135.js.map ⚠️ 1.17 MB 160ms
client/dist/src.a02dd135.js 501.63 KB 17.02s
client/dist/logo.04580eb6.png 50.84 KB 4.37s
client/dist/src.e852c4ed.css.map 957 B 4ms
client/dist/index.html 454 B 457ms
client/dist/src.e852c4ed.css 454 B 4.86s
If there is a need for me to share inherited files, give notice.
Here:
class PubSub {
constructor({ blockchain, transactionPool, wallet }) {
and here:
const testPubSub = new PubSub()
So, for this specific constructor call, the destructuring in the PubSub constructor amounts to
const { blockchain, transactionPool, wallet } = undefined;
And you can't do that, neither with undefined nor with null. You need to either, call new PubSub(/*...*/) with correct parameters, or maybe use default parameter values.
Importing certain function from some folder which has needed module with this function doesn't work.
I'm using nodemailer for sending emails. I have 3 different folders with modules. The problem is in importing (require) email sending function to current module from another one. It becomes undefined and error is myFunc is not a function.
I'm doing pretty simple things like requiring function from folder with index.js which includes needed function. But it becomes undefined when I try to use it.
services/mailTransport.js
const nodemailer = require('nodemailer');
const mailTransporter = nodemailer.createTransport({
host: 'smtp.gmail.com',
port: 587,
secure: false,
auth: {
user: 'test#test.com',
pass: 'myPassword'
}
});
module.exports = mailTransporter;
services/index.js
const mailTransporter = require("./mailTransporter");
module.exports = { mailTransporter }
utils/mailTemplate.js
const { mailTransporter } = require("../services");
const sendEmail = function (obj, msg) {
return new Promise( (res, rej) => {
let mailOptions = {
from: 'test#test.com',
to: `${obj.to}`,
subject: `${obj.subject}`,
text: "plain text",
html: "<b>" + msg + "</b>"
};
mailTransporter.sendMail(mailOptions, (error, info) => {
mailTransporter.close();
if (error) {
rej(error);
}
console.log('Message sent: %s', info.messageId);
res(info.messageId);
});
})
}
module.exports = { sendEmail };
And finally I want to use it here in projects/emails.js
const { sendEmail } = require("../utils/mailTemplate");
const { vendorNotificationMessage } = require("../utils/emailMessages");
async function notifyVendors(steps) {
try {
for(let step of steps) {
if(step.vendor) {
const message = vendorNotificationMessage(step);
step.to = step.vendor.email;
step.subject = "Step cancelling notification!";
await sendEmail(step, message);
}
}
} catch(err) {
console.log(err);
console.log("Error in notifyVendors");
}
}
module.exports = { notifyVendors };
I expect that email will be sent using that sendEmail function. But it stops with the error TypeError: sendEmail is not a function.
The correct syntax for exporting something from a module is
exports.globalObjectName = localObjectName
So in your first file the export statement should look like this
exports.mailTransporter = mailTransporter
I do not think that you need {} when you use module.exports and require. Try module.exports = sendEmail ; and const sendEmail = require("../utils/mailTemplate");