How can i mock the EventHubConsumerClient callback args with Jest - node.js

I am creating an event hub consumer using the example code from https://learn.microsoft.com/en-us/azure/event-hubs/event-hubs-node-get-started-send
How should I mock the events and context which are passed a arguments to the callback?
const { EventHubConsumerClient, earliestEventPosition } = require("#azure/event-hubs");
const { ContainerClient } = require("#azure/storage-blob");
const { BlobCheckpointStore } = require("#azure/eventhubs-checkpointstore-blob");
const connectionString = "EVENT HUBS NAMESPACE CONNECTION STRING";
const eventHubName = "EVENT HUB NAME";
const consumerGroup = "$Default"; // name of the default consumer group
const storageConnectionString = "AZURE STORAGE CONNECTION STRING";
const containerName = "BLOB CONTAINER NAME";
async function main() {
// Create a blob container client and a blob checkpoint store using the client.
const containerClient = new ContainerClient(storageConnectionString, containerName);
const checkpointStore = new BlobCheckpointStore(containerClient);
// Create a consumer client for the event hub by specifying the checkpoint store.
const consumerClient = new EventHubConsumerClient(consumerGroup, connectionString, eventHubName, checkpointStore);
// Subscribe to the events, and specify handlers for processing the events and errors.
const subscription = consumerClient.subscribe({
processEvents: async (events, context) => {
if (events.length === 0) {
console.log(`No events received within wait time. Waiting for next interval`);
return;
}
for (const event of events) {
console.log(`Received event: '${event.body}' from partition: '${context.partitionId}' and consumer group: '${context.consumerGroup}'`);
}
// Update the checkpoint.
await context.updateCheckpoint(events[events.length - 1]);
},
processError: async (err, context) => {
console.log(`Error : ${err}`);
}
},
{ startPosition: earliestEventPosition }
);
// After 30 seconds, stop processing.
await new Promise((resolve) => {
setTimeout(async () => {
await subscription.close();
await consumerClient.close();
resolve();
}, 30000);
});
}
main().catch((err) => {
console.log("Error occurred: ", err);
});

I assume that you want to know about the types of these two? The processEvents callback has the following signature
type ProcessEventsHandler = (
events: ReceivedEventData[],
context: PartitionContext
) => Promise<void>
Here's the ref doc for ReceivedEventData: https://learn.microsoft.com/en-us/javascript/api/#azure/event-hubs/receivedeventdata?view=azure-node-latest
and ref doc for PartitionContext: https://learn.microsoft.com/en-us/javascript/api/#azure/event-hubs/partitioncontext?view=azure-node-latest
Not too familar with jest. Here's what I tried:
async function processEvents(events, context) {
if (events.length === 0) {
console.log(`No events received within wait time. Waiting for next interval`);
return;
}
for (const event of events) {
console.log(`Received event: '${event.body}' from partition: '${context.partitionId}' and consumer group: '${context.consumerGroup}'`);
}
// Update the checkpoint.
await context.updateCheckpoint(events[events.length - 1]);
}
test('empty events', async () => {
const context = {
updateCheckpoint: jest.fn()
};
await processEvents([], context);
expect(context.updateCheckpoint.mock.calls.length).toBe(0);
});
test('non-empty events', async () => {
const context = {
updateCheckpoint: jest.fn()
};
await processEvents([{ body: "hello"}], context);
console.dir(context.updateCheckpoint.mock);
expect(context.updateCheckpoint.mock.calls.length).toBe(1);
});

Related

Async Firebase Cloud Function trigger - What to return on catch block?

I have written the trigger below and I'm not sure what I should return in case a catch block is called. I know that Firebase docs say that triggers should always return a Promise...
exports.sendPushNotificationForNewMessage = functions.firestore.document("messages/{messageId}").onCreate(async (snap, context) => {
const message = snap.data()
const chatRoomId = message.chatRoomId
const senderId = message.user.id
const senderUsername = message.user.username
try {
const chatRoom = await admin.firestore().collection("chatRooms").doc(chatRoomId).get()
const receiverId = chatRoom.data().userIds.find(userId => userId != senderId)
const receiver = await admin.firestore().collection("users").doc(receiverId).get()
const deviceToken = receiver.data().deviceToken
if (deviceToken) {
const payload = {
notification: {
title: "popster",
body: `New DM from ${senderUsername} 💬`,
badge: "1",
sound: "pop.m4a"
},
data: {
}
}
console.log(payload);
return admin.messaging().sendToDevice(deviceToken, payload)
} else {
return null
}
} catch (error) {
return null
}
})
The async function wraps your response in a Promise so here your return type is Promise<MessagingDevicesResponse | null> and that will terminate the Cloud Function.
I'm not sure what I should return in case a catch block is called.
Background functions do not return any value/error to client so you can just return null;.
Also checkout this Firecast for more information.

clearInterval of a external function not working - Node.JS

I have a setInterval function that's been called in another function, and I need to stop it when the proccess is done. I tried to set this setInterval function as a variable and call clearInterval, but the interval keeps running
const createInterval = (visibilityTimeout, startDateTime, message) => {
setInterval(() => {
const currentDateTime = moment().valueOf();
const timeDifference = (visibilityTimeout * 1000) - (currentDateTime - startDateTime);
if (timeDifference >= 600000) {
return;
}
if (timeDifference < 494983) {
const params = {
QueueUrl: 'http://localhost:4566/000000000000/test-queue2',
ReceiptHandle: message.ReceiptHandle,
VisibilityTimeout: visibilityTimeout,
};
sqs.changeMessageVisibility(params, (err, data) => {
if (err) logger.error(err, err.stack);
else logger.info(data);
});
// eslint-disable-next-line no-param-reassign
visibilityTimeout += 300;
}
}, 5000);
};
module.exports = async (message) => {
const startDateTime = moment().valueOf();
const {
noteId,
} = JSON.parse(message.Body);
logger.info(`Processing message [noteId=${noteId}]`);
try {
const note = await TestSessionNote.findById(noteId);
const testSession = await TestSession.findById(note.test_session_id);
logger.info(`Downloading video [key=${testSession.video_key}]`);
const isProcessing = true;
const interval = createInterval(500, startDateTime, message, isProcessing);
await sleep(20000);
clearInterval(interval);
logger.info(`Finished processing message [noteId=${noteId}]`);
} catch (ex) {
await TestSessionNote.update(noteId, { status: 'transcribe_error' });
logger.error(`Error processing message [noteId=${noteId}]`, ex);
}
};
I know that if i create a var test = setInterval(() => {console.log('blabla')}, 500) and call clearInterval(test) it works, but i don't know how can i do this calling a function
I think that you have to return from createInterval function the intervalId and after that it should work.
Can you check what value has your intervalId right now, with your current implementation?
https://developer.mozilla.org/en-US/docs/Web/API/setInterval
"The returned intervalID is a numeric, non-zero value which identifies the timer created by the call to setInterval(); this value can be passed to clearInterval() to cancel the interval."

AMQP + NodeJS wait for channel

I have a service in FeathersJS that initiates a connection to RabbitMQ, the issue is how to await for a channel to be ready before receiving requests:
class Service {
constructor({ amqpConnection, queueName }) {
this.amqpConnection = amqpConnection;
this.queueName = queueName;
this.replyQueueName = queueName + "Reply"
}
async create(data, params) {
new Promise(resolve => {
if (!this.channel) await this.createChannel();
channel.responseEmitter.once(correlationId, resolve);
channel.sendToQueue(this.queueName, Buffer.from(data), {
correlationId: asyncLocalStorage.getStore(),
replyTo: this.replyQueueName,
});
});
}
async createChannel() {
let connection = this.amqpConnection();
let channel = await connection.createChannel();
await channel.assertQueue(this.queueName, {
durable: false,
});
this.channel = channel;
channel.responseEmitter = new EventEmitter();
channel.responseEmitter.setMaxListeners(0);
channel.consume(
this.replyQueueName,
(msg) => {
channel.responseEmitter.emit(
msg.properties.correlationId,
msg.content.toString("utf8")
);
},
{ noAck: true }
);
}
....
}
Waiting for the channel to be created during a request seems like a waste. How should this be done "correctly"?
Feathers services can implement a setup method which will be called when the server is started (or you call app.setup() yourself):
class Service {
async setup () {
await this.createChannel();
}
}

Payment Options View Controller (for Stripe) Not Showing Up

I am fairly new to this. I have used cloud functions and firebase to integrate stripe into my iOS project. Here is my code for the payment options (in node.js).
exports.addPaymentMethodDetails = functions.firestore
.document('/stripe_customers/{userId}/payment_methods/{pushId}')
.onCreate(async (snap, context) => {
try {
const paymentMethodId = snap.data().id;
const paymentMethod = await stripe.paymentMethods.retrieve(
paymentMethodId
);
await snap.ref.set(paymentMethod);
// Create a new SetupIntent so the customer can add a new method next time.
const intent = await stripe.setupIntents.create({
customer: paymentMethod.customer,
});
await snap.ref.parent.parent.set(
{
setup_secret: intent.client_secret,
},
{ merge: true }
);
return;
} catch (error) {
await snap.ref.set({ error: userFacingMessage(error) }, { merge: true });
await reportError(error, { user: context.params.userId });
}
})
Here is my code in Xcode:
``let customerContext = STPCustomerContext(keyProvider: StripeApi)
var paymentContext: STPPaymentContext!
init() {
self.paymentContext = STPPaymentContext(customerContext: customerContext)
super.init(nibName: nil, bundle: nil)
self.paymentContext.delegate = self
self.paymentContext.hostViewController = self
self.paymentContext.paymentAmount = 5000 // This is in cents, i.e. $50 USD
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
#IBAction func paymentMethodClicked(_ sender: Any) {
paymentContext.pushPaymentOptionsViewController()
}
func setupStripeConfig() {
let config = STPPaymentConfiguration.shared()
config.additionalPaymentOptions = .default
config.requiredBillingAddressFields = .none
let customerContext = STPCustomerContext(keyProvider: StripeApi)
paymentContext = STPPaymentContext(customerContext: customerContext, configuration: config, theme: .default())
paymentContext.delegate = self
paymentContext.hostViewController = self
When I run the simulator of this and click the button to pull up payment options, nothing happens and my console (in Xcode) reads "INTERNAL". Can someone please show me how to bring up the Payment Options View Controller, or if there is something I need to or add/fix in order to do so? Also here is my node.js code for getting an ephemeral key:
exports.createEphemeralKey = functions.https.onCall(async (data, context) => {
const customerID = data.customer_id;
const stripeVersion = data.stripe_version;
const uid = context.auth.uid;
if (uid === null) {
console.log('Illegal access attempt due to unauthenticated user');
throw new functions.https.HtppsError('permission-denied', 'Illegal access attempt')
}
let key = await stripe.ephemeralKeys.create(
{customer: '{{CUSTOMER_ID}}'},
{stripe_version: '{{API_VERSION}}'}
);
return stripe.ephemeralKeys.create(
{customer: customerID},
{stripe_version: stripeVersion}).then((key) => {
return key
}).catch((err) => {
console.log(err)
throw new functions.https.HttpsError9('internal', 'Unable to create ephemeral key.')
});
});

RabbitMQ have an exclusive consumer consume message serially

I have a scenario that on a given topic I need to consume each message one by one, do some async task and then consume the next one. I am using rabbitmq and amqp.node.
I was able to achieve this with a prefetch of 1. Which of course is not an actual solution since this would lock the whole channel and the channel have multiple topics.
So far this is my producer:
const getChannel = require("./getChannel");
async function run() {
const exchangeName = "taskPOC";
const url = "amqp://queue";
const channel = await getChannel({ url, exchangeName });
const topic = "task.init";
let { queue } = await channel.assertQueue(topic, {
durable: true
});
const max = 10;
let current = 0;
const intervalId = setInterval(() => {
current++;
if (current === max) {
clearInterval(intervalId);
return;
}
const payload = JSON.stringify({
foo: "bar",
current
});
channel.sendToQueue(queue, Buffer.from(payload), { persistent: true });
}, 3000);
}
run()
.then(() => {
console.log("Running");
})
.catch(err => {
console.log("error ", err);
});
And this is my consumer
const getChannel = require("./getChannel");
async function run() {
const exchangeName = "taskPOC";
const url = "amqp://queue";
const channel = await getChannel({ url, exchangeName });
channel.prefetch(1);
const topic = "task.init";
const { queue } = await channel.assertQueue(topic, {
durable: true
});
channel.bindQueue(queue, exchangeName, topic);
let last = new Date().getTime();
channel.consume(
queue,
msg => {
const now = new Date().getTime();
console.log(
" [x] %s %s:'%s' ",
msg.fields.routingKey,
Math.floor((now - last) / 1000),
msg.content.toString()
);
last = now;
setTimeout(function() {
channel.ack(msg);
}, 10000);
},
{ exclusive: true, noAck: false }
);
}
run()
.then(() => {
console.log("Running");
})
.catch(err => {
console.log("error ", err);
});
Is there any way on RabbitMQ to do that or I would need to handle this on my app?
Thanks.
You can use the consumer prefetch setting (see https://www.rabbitmq.com/consumer-prefetch.html). In the case of amqp node, you set this option using the prefetch function:
channel.prefetch(1, false); // global=false
In this case, each consumer on the channel will have a prefetch of 1. If you want to have different configurations for each consumer, you should create more channels.
Hope this helps.

Resources