xUnit test async event handler - c#

I have a class that uses Azure Service Bus and it basically sends an email by putting a message to a queue. When I write an integration tests I need to make sure I can receive the message that's been sent (no, I'm not testing Service Bus). So, here's the code for that:
[Fact]
public async Task PutEmailIntoTheQueue()
{
IlgQueueEmailInfo actual = new IlgQueueEmailInfo("from#address.com", "to#address.com", "subject", "test body", MessageBodyType.Text,
"email1#domain.com",
"email2#domain.com");
ServiceBusConnectionStringBuilder sbcb =
new ServiceBusConnectionStringBuilder(SERVICE_BUS_ENDPOINT_S, QUEUE_NAME_S, SAS_KEY_NAME, EMAIL_TESTS_SAS);
QueueClient receiveClient = new QueueClient(sbcb, ReceiveMode.ReceiveAndDelete);
bool hasBeenCalled = false;
//
// Check values here
//
async Task ReceiveMessageHandler(Message message, CancellationToken cancellationToken)
{
Output.WriteLine("Received Message\n");
Assert.True(message.Label != null && message.ContentType != null &&
message.Label.Equals(IlgEmailQueue.MESSAGE_LABEL_S, StringComparison.InvariantCultureIgnoreCase) &&
message.ContentType.Equals("application/json", StringComparison.InvariantCultureIgnoreCase));
byte[] body = message.Body;
string msgJson = Encoding.UTF8.GetString(body);
Output.WriteLine($"json: {msgJson}\n");
IlgQueueEmailInfo emailInfo = JsonConvert.DeserializeObject<IlgQueueEmailInfo>(msgJson);
Assert.NotNull(emailInfo);
Output.WriteLine("emailInfo is not NULL");
Assert.Equal(actual, emailInfo);
Output.WriteLine($"emailInfo equals to actual: {actual == emailInfo}\n");
Output.WriteLine("Setting hasBeenCalled to True");
hasBeenCalled = true;
await receiveClient.CompleteAsync(message.SystemProperties.LockToken);
}
receiveClient.RegisterMessageHandler(
ReceiveMessageHandler,
new MessageHandlerOptions(LogMessageHandlerException) {AutoComplete = true, MaxConcurrentCalls = 1});
await _emailQueue.QueueForSendAsync(actual.FromAddress, actual.ToAddresses[0], actual.Subj, actual.Body, MessageBodyType.Text, actual.CcAddress,
actual.BccAddress);
await Task.Delay(TimeSpan.FromSeconds(5));
Assert.True(hasBeenCalled);
}
But when I run it I get an exception saying something like "There's no currently active test". What is the best way of dealing with this kind of tests?

You are testing Azure Service Bus in a way that you're sending a message on er the wire and receive it. Looking at the code, you want to verify message is contracted in a certain way. Rather than going through the whole send / receive phase, you could short-circuit it by just testing the Message constructed by your code. Alternatively, create a plugin to intercept the message just before it's sent. In case you insist on sending/receiving, your code must not exist until assertion is either executed or there's a timeout. And that's due to the fact that message handler is a synchronous API that will continue execution of the test method before the message has a change to be processed by the callback. await Task.Delay(TimeSpan.FromSeconds(5)) is not guaranteed to be sufficient.

Related

Kafka Producer can't connect to servers and will not throw any exception

I have an extremely simple setup for sending message to Kafka:
var producerConfig = new ProducerConfig
{
BootstrapServers = "www.example.com",
SecurityProtocol = SecurityProtocol.SaslSsl,
SaslMechanism = SaslMechanism.ScramSha512,
SaslUsername = _options.SaslUsername,
SaslPassword = _options.SaslPassword,
MessageTimeoutMs = 1
};
var producerBuilder = new ProducerBuilder<Null, string>(producerConfig);
using var producer = producerBuilder.Build();
producer.Produce("Some Topic", new Message<Null, string>()
{
Timestamp = Timestamp.Default,
Value = "hello"
});
Before, this code was working fine. Today it has decided to stop working and I'm trying to figure out why. I'm trying to get the Producer to throw an exception when failing to deliver a message, but it never seems to crash. Even when I fill in a wrong username and password, the producer still doesn't crash. Not even a logline in my local output window. How can I debug my Kafka connection when the producer never shows any problems?
You can add SetErrorHandler() to the ProducerBuilder. It would look like this:
var producerBuilder = new ProducerBuilder<Null, string>(producerConfig)
.SetErrorHandler(errorMessageString => .....);
Set a breakpoint in that lambda and you can break on errors.
Produce is asynchronous and not blocking, function signature is
void Produce(string topic, Message<TKey, TValue> message, Action<DeliveryReport<TKey, TValue>> deliveryHandler = null)
In order to verify that a message was delivered without error
you can add a delivery report handler function e.g.
private void DeliveryReportHandler(DeliveryReport<int, T> deliveryReport)
{
if (deliveryReport.Status == PersistenceStatus.NotPersisted)
{
_logger.LogError($"Failed message delivery: error reason:{deliveryReport.Error?.Reason}");
_messageWasNotDelivered = true;
}
}
_messageWasNotDelivered = false;
_producer.Produce(topic,
new Message<int, T>
{
Key = key,
Value = entity
},
DeliveryReportHandler)
_producer.Flush(); // Wait until all outstanding produce requests and delivery report callbacks are completed
if(_messageWasNotDelivered ){
// handle non delivery
}
This code can be trivially adjusted for batch producing like this
_messageWasNotDelivered = false;
foreach(var entity in entities){
_producer.Produce(topic,
new Message<int, T>
{
Key = entity.Id,
Value = entity
},
DeliveryReportHandler)
}
_producer.Flush(); // Wait until all outstanding produce requests and delivery report callbacks are completed
if(_messageWasNotDelivered ){
// handle non delivery
}

Azure Service Bus Retry Options Not Working

With no luck, I tried configuring my ServiceBusClient to retry a message with a Fixed Delay of 10 seconds. I also tried Exponential retries configuration. However, the code is always retrying the message within a second or 2 and completely ignoring the configuration. It even ignores the MaxRetries and only retries 10 times, the value configured in Azure Portal for the queue. What am I doing wrong?
I am using The Azure.Messaging.ServiceBus library, NuGet package 7.0.0.
The code:
ServiceBusClient client = new ServiceBusClient(serviceBusConnectionString, new ServiceBusClientOptions()
{
RetryOptions = new ServiceBusRetryOptions()
{
Mode = ServiceBusRetryMode.Fixed,
Delay = TimeSpan.FromSeconds(10),
MaxDelay = TimeSpan.FromMinutes(3),
MaxRetries = 30
}
});
ServiceBusProcessor processor = client.CreateProcessor(queueName, new ServiceBusProcessorOptions());
// throwing an exception in MyMessageHandlerAsync on purpose
// to test out the retries configuration
processor.ProcessMessageAsync += MyMessageHandlerAsync;
// The uncaught exception causes this method to execute.
// Processing is attempted 10 times with
// virtually no delay between each attempt.
// After the 10th attempt, the message goes to deadletter,
// which is expected.
processor.ProcessErrorAsync += MyErrorHandler;
I'm adding more to this question after receiving the 1st response:
Currently, MyMessageHandlerAsync is:
private async Task MyMessageHandlerAsync(EventArgs eventArgs)
{
var args = (ProcessMessageEventArgs)eventArgs;
var body = args.Message.Body.ToString();
// ...
// process body
// ...
await args.CompleteMessageAsync(args.Message);
}
How should I change the method's contents to retry a non-transient ServiceBusException? Please help provide the code where the TODOs are below:
private async Task MyMessageHandlerAsync(EventArgs eventArgs)
{
var args = (ProcessMessageEventArgs)eventArgs;
try
{
var body = args.Message.Body.ToString();
// ...
// process body
// ...
await args.CompleteMessageAsync(args.Message);
}
catch (ServiceBusException sbe)
{
if (sbe.IsTransiet)
{
// TODO: Is it correct that the exponential retry will work
// here? The one defined in the ServiceBusClient.
// So, no code is needed here, just throw.
throw;
}
else
{
// TODO: for non-transient, this is where the
// options in the ServiceBusClient don't apply.
// Is that correct? How do I do an
// exponential retry here?
}
}
catch (Exception e)
{
// TODO: same problem as else in first catch.
}
}
ServiceBusRetryOptions is intended to be used by the ASB client when there are transient errors that are not bubbled up to your code right away, i.e. an internal retry mechanism built into the client to perform retries on your behalf before exception is raised.
Use retry policy to specify to the ASB client how to deal with transient errors prior to giving up, not how many times a message handler throws error:

botFramework v4 how to handle dialog response after LUIS call

I have a bot written in C# that is using LUIS to determine intents. I have a method that makes a call to the LUIS service and then looks for an 'Open_Case' intent. The model has a CaseNumber entity defined which may or may not be included in the response from the LUIS service.
If the response doesn't have a case number entity I start a dialog to ask the user for the case number.
Once I have a case number I then want to return a card with case information.
Here's the code I have:-
/// <summary>
/// Dispatches the turn to the requested LUIS model.
/// </summary>
private async Task DispatchToLuisModelAsync(ITurnContext context, string appName, DialogContext dc, CancellationToken cancellationToken =
default (CancellationToken)) {
var result = await botServices.LuisServices[appName].RecognizeAsync(context, cancellationToken);
var intent = result.Intents ? .FirstOrDefault();
string caseNumber = null;
if (intent ? .Key == "Open_Case") {
if (!result.Entities.ContainsKey("Case_CaseNumber")) {
var dialogResult = await dc.BeginDialogAsync(CaseNumberDialogId, null, cancellationToken);
} else {
caseNumber = (string)((Newtonsoft.Json.Linq.JValue) result.Entities["Case_CaseNumber"].First).Value;
var cardAttachment = botServices.CaseInfoServices.LookupCase(caseNumber);
var reply = context.Activity.CreateReply();
reply.Attachments = new List < Attachment > () {
cardAttachment
};
await context.SendActivityAsync(reply, cancellationToken);
}
}
}
What I'm struggling with is where the code send the card response should sit.
In the code I currently have I send the card if the number was returned in the LUIS response, but if there was no number and I start the dialog then I only get access to the number either in the final step of the dialog or in the dialog result in the root turn handler. I've currently duplicated the reply inside the final step in the dialog, but it feels wrong and inelegant.
I'm sure there must be a way that I can collect the number from LUIS or the dialog and THEN send the response from a single place instead of duplicating code.
Any suggestions gratefully received...
I came to the conclusion that I need to put the code that displays the card into a method on the bot class, then call it from the else in code snippet and also from the turn handler when the dialogTurnStatus is equal to Complete

Bot Framework Context Wait not waiting for next message

I'm trying to build a Dialog using the Microsoft Bot Framework which helps users consult purchase order status (currently, just a mock). I am using a LuisDialog which, when it detects the "ConsultPO" intent, it's supposed to ask for the user's 'customer id' and wait a follow up message from the user. However, it keeps going back to the start of the Luis Dialog and processing the intent instead of resuming from the waited method. This is the intent's code, which runs correctly:
[LuisIntent("ConsultPO")]
public async Task POIntent(IDialogContext context, LuisResult result)
{
string PO = "";
foreach (var entity in result.Entities)
{
if (entity.Type == "purchaseOrder")
PO = entity.Entity;
}
if (PO.Length != 0)
{
po_query = PO;
}
await context.PostAsync("Ok, can you confirm your customer id and I'll check for you?");
context.Wait(confirmCustomer_getPO);
}
This is the code I would expect to be executed after the user responds with a follow up message:
public async Task confirmCustomer_getPO(IDialogContext context, IAwaitable<object> argument)
{
await context.PostAsync("DEBUG TEST");
IMessageActivity activity = (IMessageActivity)await argument;
customer_query = activity.Text;
if (po_query.Length > 0)
{
PurchaseOrder po = POservice.findPO(po_query, customer_query);
await buildSendResponse(po, context);
//more non relevant code
When I answer to the bot's inquiry after context.Wait(confirmCustomer_getPO) is executed, it just goes into LUIS then runs the code respective to "None" intent. The message "DEBUG TEST" is never sent.
Why is "confirmCustomer_getPO" never getting called?
EDIT:
I added a debug message in the StartAsync method. I'm not sure whether this is supposed to happen but it pops up every time I send a message to the bot, which makes me believe the Dialog is simply restarting every time I message the bot:
public class EchoDialog : LuisDialog<object>
{
public EchoDialog() : base(new LuisService(new LuisModelAttribute(
ConfigurationManager.AppSettings["LuisAppId"],
ConfigurationManager.AppSettings["LuisAPIKey"],
domain: ConfigurationManager.AppSettings["LuisAPIHostName"])))
{
}
public override Task StartAsync(IDialogContext context)
{
context.PostAsync("I'm in startAsync");
return base.StartAsync(context);
}
Local debugging shows no exceptions are occurring and that any breakpoint in the waited method is never reached, although the context.Wait call does happen.
I figured out the issue myself after fighting with it for a while. The issue was with the bot store. I was using an InMemoryDataStore which was not working - switching to TableBotDataStore fixed the problem. The issue with the DataStore meant that states weren't being saved so my "waits" and "forwards" were not being saved into the dialog stack - any new incoming message was sent to the RootDialog.
Broken - not working while this was in global.asax.cs:
Conversation.UpdateContainer(
builder =>
{
builder.RegisterModule(new AzureModule(Assembly.GetExecutingAssembly()));
var store = new InMemoryDataStore(); // volatile in-memory store
builder.Register(c => store)
.Keyed<IBotDataStore<BotData>>(AzureModule.Key_DataStore)
.AsSelf()
.SingleInstance();
});
GlobalConfiguration.Configure(WebApiConfig.Register);
As soon as I updated store to:
var store = new TableBotDataStore(ConfigurationManager.AppSettings["AzureWebJobsStorage"]);
Having a valid "AzureWebJobsStorage" setting in web.config from my application settings in Azure, the problem was fixed without any other changes in the code.

My 'myQueueItem' in my Azure function producing a zero value when it gets triggered

I'm passing messages to my Azure service bus queue like this, where the 'MessageId' value is a string of a number ex. '345'. I see it in the queue in my Azure portal dashboard and when I look at the message in Service Bus Explorer I see the message id as the eventId I'm passing it.
Message message = new Message
{
MessageId = eventId.ToString(), // passing it a value of '123'
ScheduledEnqueueTimeUtc = enqueuedTime
};
var sendCodeSequence = await queueClient.ScheduleMessageAsync(message, new DateTimeOffset(enqueuedTime));
But when my function gets triggered I'm getting an exception saying the myQueueItem from the function is either 0 or null, depending on which function I'm calling.
Here is the first couple of lines of code for one of the functions that are giving me this problem. The event is always null and throws an exception because myQueueItem is always null or 0.
I had this working before, I thought, but now I think changes were made and throwing exceptions each time my functions get triggered!
Question - Is myQueueItem not the messageId I'm passing it when I create and put the message on the queue?
[FunctionName("CancelEvent")]
public static void Run([ServiceBusTrigger("canceleventqueue", AccessRights.Manage, Connection = "events2017_RootManageSharedAccessKey_SERVICEBUS")]string myQueueItem, TraceWriter log, ExecutionContext context)
{
try
{
log.Info($"Cancel event started id: {myQueueItem}");
var eventId = Convert.ToInt32(myQueueItem);
using (var dbContext = new EventContext(myDatabaseConnectionString))
{
var event = dbContext.Events.Where(i => i.EventId == eventId).FirstOrDefault();
if (Event == null)
throw new Exception("no event with this id: " + myQueueItem);
myQueueItem is the message itself. string myQueueItem isn't really useful not if you have used a brokered message or service bus message and added other information you want to use in the function. You should replace string myQueueItem with Microsoft.Azure.ServiceBus.Message myQueueItem. Also review the implementation in this post for some other useful information on serializing/deserializing the message if neccessary: Azure ServiceBus Message Serialization/Deserialization
I solved it by passing in 'string messageId' as a parameter in the function. I guess Azure messages are used differently then BrokeredMessages.

Categories

Resources