Create Bot for Microsoft Graph with DevOps 5: Function Test

I already added unit testing, but to complete entire test, I need function test.

Function Test for Bot Framework

There are several ways to achieve this, but I see many people utilizes Direct Line, so I will follow them. Direct Line is one of channels Bot Connecter supports, and you can connect to the bot via command line, thus it is perfect fit for testing.

Publish O365 Bot

To use Direct Line, I need to publish the bot first. I will create two App Services, one for production and another for test.

1. Right click the O365Bot project and click [Publish]

image

2. Select Azure App Service and create new, or use existing if you already provision some apps.

image

3. Enter name, Subscription, etc to complete the publish.

image

4. Once publishing completed, then click [Create new profile] to add another profile.

image

5. Do the same for testing environment.

image

Register the bot

Now you can register your bot to Bot Connector.

1. Sign in at https://dev.botframework.com by using Microsoft Account.

2. Click [Create a bot]

image

3. Click [Register], and fill the form. For [Messaging endpoint], use https as protocol and do not forget to add api/messages at the end.

4. Note the Microsoft App Id and password while you register the bot.

5. Once registered, add Direct Line as an additional channel.

image

6. There is a default site, but if you want to add another, then click [Add new site], enter name and done. This is just for organizing purpose.

image

7. Click [Show] to confirm the secret key. Then click Done.

image

8. Do the same for testing environment.

Update Application setting of App Services

As I have two environments, prod and test, I cannot hard-code the values in web.config. Thus I set the values to App Service directly.

1. Login to https://portal.azure.com

2. Select the App Service,

image

3. Go to Application Settings and create [MicrosoftAppId], [MicrosoftAppPassword], [ActiveDirectory.RedirectUrl] keys. Then enter appropriate values. For ActiveDirectory.RedirectUrl, use https://<your azure web app>/api/OAuthCallback.

image

4. Do the same for another environment, too.

Update Azure AD Application settings

In addition to localhost, we have two more redirect URLs.

1. Go to application which you registered to Azure AD previously.

2. Open Reply URLs and add new addresses.

image

Add Function Test Project

1. Open the O365Bot solution in Visual Studio and add new project.

2. Select [Unit Test Project] and name it as O365Bot.FunctionTests

image

3. Add Direct Line NuGet Package.

image

4. Add DirectLineHelper.cs file and replace the code with following. This class contains Direct Line class usage.

 using Microsoft.Bot.Connector.DirectLine;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
using System.Linq;

namespace O365Bot.FunctionTests
{
    public class DirectLineHelper
    {
        private TestContext testContext;

        private string conversationId;
        private string userId;
        private string watermark;

        private DirectLineClient client;

        public DirectLineHelper(TestContext testContext)
        {
            client = new DirectLineClient(testContext.Properties["DirectLineSecret"].ToString());
            userId = testContext.Properties["UserId"].ToString();
            conversationId = client.Conversations.StartConversation().ConversationId;
            watermark = null;
        }

        public List<Activity> SentMessage(string text)
        {
            Activity activity = new Activity()
            {
                Type = ActivityTypes.Message,
                From = new ChannelAccount(userId, userId),
                Text = text
            };
            client.Conversations.PostActivity(conversationId, activity);
            var reply = client.Conversations.GetActivities(conversationId, watermark);

            watermark = reply.Watermark;
            return reply.Activities.Where(x => x.From.Id != userId).ToList();
        }
    }
}

5. Then add runsetting file. Add two XML file and name it as Prod.runsettings and Test.runsettings.

image image

6. Replace the code for .runsettings file and put appropriate values.

<?xml version="1.0" encoding="utf-8" ?>
<RunSettings>
<TestRunParameters>
<Parameter name="DirectLineSecret" value="__YourSecret__" />
<Parameter name="BotId" value="__BotId__" />
<Parameter name="UserId" value="__UserId__" />
</TestRunParameters>
</RunSettings>

7. Rename UnitTest1.cs to FunctionTest1.cs and replace the code.

 using System;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace O365Bot.FunctionTests
{
    [TestClass]
    public class FunctionTest1
    {
        public TestContext TestContext { get; set; }

        [TestMethod]
        public void Function_ShouldReturnEvents()
        {
            DirectLineHelper helper = new DirectLineHelper(TestContext);
            var toUser = helper.SentMessage("get appointments");
            Assert.IsTrue(toUser.First().Text.Equals("2017-05-31 12:00-2017-05-31 13:00: dummy event"));
        }
    }
}

How to handle authentication

So far so good. But how we should handle authentication (AuthBot) from Direct Line? The easiest way to workaround this is to authenticate once by using same user Id. Then AuthBot will re-use the token.

1. Add a console project to the O365Bot solution. I named it as O365Bot.AuthConsole.

2. Add Direct Line NuGet Package.

image

3. Add System.Configuration to reference

image

4. Replace the code of Program.cs

 using Microsoft.Bot.Connector.DirectLine;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace O365Bot.AuthConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            var userId = ConfigurationManager.AppSettings["UserId"];
            var directLineSecret = ConfigurationManager.AppSettings["DirectLineSecret"];
            DirectLineClient client = new DirectLineClient(directLineSecret);
            var conversation = client.Conversations.StartConversation();
            Activity activity = new Activity()
            {
                Type = ActivityTypes.Message,
                From = new ChannelAccount(userId, userId),
                Text = "Hi"
            };
            client.Conversations.PostActivity(conversation.ConversationId, activity);
            var reply = client.Conversations.GetActivities(conversation.ConversationId, null);
            var authReply = reply.Activities.Where(x => x.From.Id != userId).First();
            if (authReply.Attachments != null &&
                authReply.Attachments.First().ContentType != "application/vnd.microsoft.card.signin")
                return;

            Console.WriteLine((authReply.Attachments.First().Content as dynamic).buttons[0].value);
            Console.WriteLine("Copy the address, past to browser and authenticate, then past the displayed code back");
            var code = Console.ReadLine();

            activity = new Activity()
            {
                Type = ActivityTypes.Message,
                From = new ChannelAccount(userId, userId),
                Text = code
            };

            client.Conversations.PostActivity(conversation.ConversationId, activity);
            return;
        }
    }
}

5. Add appSettings in configuration node of App.config. Specify the same User Id with Function Test settings.

 <?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key="DirectLineSecret" value="__YourSecret__" />
    <add key="UserId" value="__UserId__" />
  </appSettings>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2" />
    </startup>
</configuration>

6. Run the console app, then you see the authentication URL. Copy and past the address to any browser to complete authentication.

image

7. Copy the result code and past it back.

image

8. Do the same for another environment.

Run the function test

All prerequisites are set! Let’s run function test now.

1. From Test menu in Visual Studio, select Test Settings | Select Test Settings File

image

2. Select Test.runsettings.

image

3. Set breakpoint at the beginning of Function_ShouldReturnEvents method.

4. Compile entire solution and, debug the Function_ShouldReturnEvents from Test Explorer.

image

5. Do the debug to see if it works. Obviously, the asset fails at the moment though. (Now you see I have a dental appointment :))

image

6. Stop the debug and replace the asset line as follows. I wonder if there is better way to asset this type of test..

 Assert.IsTrue(true);

Summery

As function test is all set, I will create Release Definition next.

GitHub: https://github.com/kenakamu/BotWithDevOps-Blog-sample/tree/master/article5

Ken