Bot Framework と Microsoft Graph で DevOps その 15 : プロアクティブ通知 テスト編

※2017/06/11 これまでの記事でコードを一部直しました。動きがおかしい場合、過去記事を念のため再度確認してください。変更した記事はその5その7その9その12その13その14 です。ファンクションテスト動いていませんでした。。

前回はプロアクティブ通知の実装をしましたが、今回はエミュレーターでの検証とテストを。

エミュレーターでのテスト

これまでずっとローカルで試験を行っていましたが、Microsoft Graph から通知を行う関係で、サーバーは Azure に立てている状態で検証が必要となります。

エミュレータからの接続

1. エミュレーターより App Settings を開きます。

image

2. 画面の手順に従い、ngrok をインストール。パスを指定します。ngrok はローカルのアプリを ngrok サーバーをプロキシとして外部公開する仕組みです。

image

3. Url、App ID、App Password を入力して接続します。Locale は任意のものを使えます。

image

4. add appointment とメッセージを送ってみます。

5. 認証を求められたら接続はできています。

テスト手順

1. まず認証を済ませます。

2. イベントの作成を始めます。

image

3. https://portal.office.com より予定表に接続します。

image

4. 任意の予定を変更します。

image

5. 変更された旨通知がきます。詳細取得を選択します。

image

6. 詳細が表示され、元のダイアログに戻ることを確認します。

image

リモートデバッグ

今回のようにリモートに配置しないとテストできない場合、いくつかテスト方法はあります。

Visual Studio から Azure App Services でライブデバッグする

これはコードの変更がいらないので楽ですが、パフォーマンスに難があります。

1. デバッグビルドを発行する。既定はリリースビルドになっているため、Settings より設定を変えてください。

image

2. サーバーエクスプローラーから該当のサービスを右クリックしてアタッチする。

image

ngrok を使う

ngrok を使えば、ローカル実行しているサービスに外部からアクセスできます。しかし今回はコードを変更する必要があるためやりませんでした。変更箇所は Microsoft Graph で通知を受け取る際の、NotificationUrl を ngrok で公開しているアドレスにします。またテストが終わればゴミが残らないよう都度サブスクリプションを消します。

ユニットテストの更新

ユニットテストの変更点は 2 つ。

- INotificationService の解決

- 通知シナリオテストの追加

しかし現時点で通知シナリオテストについてよい方法が見つかっていませんので、一旦他のテストが問題なく実行できるよう、INotificationService の解決を行います。

INotificationService のモック

以下のコードでサービスのモックが行えます。

 var subscriptionId = Guid.NewGuid().ToString();
var mockNotificationService = new Mock<INotificationService>();
mockNotificationService.Setup(x => x.SubscribeEventChange()).ReturnsAsync(subscriptionId);
mockNotificationService.Setup(x => x.RenewSubscribeEventChange(It.IsAny<string>())).Returns(Task.FromResult(true));

builder.RegisterInstance(mockNotificationService.Object).As<INotificationService>();

通知受け取りをどうするか

どこをテストするかによりますが、Resume メソッド移行をテストするとプロアクティブ通知のロジックが確認できるので、今回はそうしました。Microsoft Graph からの受取部分をテストしたい場合は別途そこをユニットテスト化してください。Resume については、UnitTest1.cs にてメソッドを追加すれば対応できます。

 /// <summary>
/// プロアクティブ通知でダイアログ差し込みを行い、結果を受信
/// </summary>
public async Task<List<IMessageActivity>> Resume(IContainer container, IDialog<object> dialog, IMessageActivity toBot)
{
    using (var scope = DialogModule.BeginLifetimeScope(container, toBot))
    {
        var results = new List<IMessageActivity>();

        var botData = scope.Resolve<IBotData>();
        await botData.LoadAsync(CancellationToken.None);
        var task = scope.Resolve<IDialogTask>();

        //現在の会話にダイアログを差し込み
        task.Call(dialog.Void<object, IMessageActivity>(), null);
        await task.PollAsync(CancellationToken.None);
        await botData.FlushAsync(CancellationToken.None);
                
        // 結果の取得
        var queue = scope.Resolve<Queue<IMessageActivity>>();
        while (queue.Count != 0)
        {
            results.Add(queue.Dequeue());
        }

        return results;
    }
}

コードの変更

UnitTest1.cs の中身を以下のコードと差し替えます。テスト全体でいい加減長い。モックしないといけない範囲だけ気を付けてください。

 using Autofac;
using Microsoft.Bot.Builder.Base;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.Dialogs.Internals;
using Microsoft.Bot.Builder.Internals.Fibers;
using Microsoft.Bot.Builder.Tests;
using Microsoft.Bot.Connector;
using Microsoft.Graph;
using Microsoft.QualityTools.Testing.Fakes;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using O365Bot.Dialogs;
using O365Bot.Handlers;
using O365Bot.Resources;
using O365Bot.Services;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;

namespace O365Bot.UnitTests
{
    [TestClass]
    public class SampleDialogTest : DialogTestBase
    {
        private string locale = "ja-JP";

        public SampleDialogTest()
        {            
            Thread.CurrentThread.CurrentCulture = new CultureInfo(locale);
            Thread.CurrentThread.CurrentUICulture = new CultureInfo(locale);
        }

        [TestMethod]
        public async Task ShouldReturnEvents()
        {
            // Fakes を使うためにコンテキストを作成
            using (ShimsContext.Create())
            {
                // AuthBot の GetAccessToken メソッドを実行した際、dummyToken というトークンが返るよう設定
                AuthBot.Fakes.ShimContextExtensions.GetAccessTokenIBotContextString = 
                    async (a, e) => { return "dummyToken"; };

                // サービスのモック
                var mockEventService = new Mock<IEventService>();
                mockEventService.Setup(x => x.GetEvents()).ReturnsAsync(new List<Event>()
                {
                    new Event
                    {
                        Subject = "dummy event",
                        Start = new DateTimeTimeZone()
                        {
                            DateTime = "2017-05-31 12:00",
                            TimeZone = "Standard Tokyo Time"
                        },
                        End = new DateTimeTimeZone()
                        {
                            DateTime = "2017-05-31 13:00",
                            TimeZone = "Standard Tokyo Time"
                        }
                    }
                });
                var subscriptionId = Guid.NewGuid().ToString();
                var mockNotificationService = new Mock<INotificationService>();
                mockNotificationService.Setup(x => x.SubscribeEventChange()).ReturnsAsync(subscriptionId);
                mockNotificationService.Setup(x => x.RenewSubscribeEventChange(It.IsAny<string>())).Returns(Task.FromResult(true));

                var builder = new ContainerBuilder();
                builder.RegisterInstance(mockEventService.Object).As<IEventService>();
                builder.RegisterInstance(mockNotificationService.Object).As<INotificationService>();
                WebApiApplication.Container = builder.Build();
                
                // テストしたいダイアログのインスタンス作成
                IDialog<object> rootDialog = new RootDialog();

                // Bot に送るメッセージを作成
                var toBot = DialogTestBase.MakeTestMessage();
                toBot.From.Id = Guid.NewGuid().ToString();
                // Locale 設定
                toBot.Locale = "ja-JP";
                toBot.Text = "get appointments";

                // メモリ内で実行できる環境を作成
                Func<IDialog<object>> MakeRoot = () => rootDialog;
                using (new FiberTestBase.ResolveMoqAssembly(rootDialog))
                using (var container = Build(Options.MockConnectorFactory | Options.ScopedQueue, rootDialog))
                {
                    // グローバルメッセージ登録
                    RegisterBotModules(container);

                    // メッセージを送信して、結果を受信
                    IMessageActivity toUser = await GetResponse(container, MakeRoot, toBot);

                    // 結果の検証
                    Assert.IsTrue(toUser.Text.Equals("2017-05-31 12:00-2017-05-31 13:00: dummy event"));
                }
            }
        }

        [TestMethod]
        public async Task ShouldCreateAllDayEvent()
        {               
            // Fakes を使うためにコンテキストを作成
            using (ShimsContext.Create())
            {
                // AuthBot の GetAccessToken メソッドを実行した際、dummyToken というトークンが返るよう設定
                AuthBot.Fakes.ShimContextExtensions.GetAccessTokenIBotContextString =
                    async (a, e) => { return "dummyToken"; };

                // サービスのモック
                var mockEventService = new Mock<IEventService>();
                mockEventService.Setup(x => x.CreateEvent(It.IsAny<Event>())).Returns(Task.FromResult(true));
                var subscriptionId = Guid.NewGuid().ToString();
                var mockNotificationService = new Mock<INotificationService>();
                mockNotificationService.Setup(x => x.SubscribeEventChange()).ReturnsAsync(subscriptionId);
                mockNotificationService.Setup(x => x.RenewSubscribeEventChange(It.IsAny<string>())).Returns(Task.FromResult(true));

                var builder = new ContainerBuilder();
                builder.RegisterInstance(mockEventService.Object).As<IEventService>();
                builder.RegisterInstance(mockNotificationService.Object).As<INotificationService>();
                WebApiApplication.Container = builder.Build();
                
                // テストしたいダイアログのインスタンス作成
                IDialog<object> rootDialog = new RootDialog();
                              
                // メモリ内で実行できる環境を作成
                Func<IDialog<object>> MakeRoot = () => rootDialog;
                using (new FiberTestBase.ResolveMoqAssembly(rootDialog))
                using (var container = Build(Options.MockConnectorFactory | Options.ScopedQueue, rootDialog))
                {
                    // グローバルメッセージ登録
                    RegisterBotModules(container);

                    // Bot に送るメッセージを作成
                    var toBot = DialogTestBase.MakeTestMessage();
                    // ロケールで日本語を指定
                    toBot.Locale = locale;
                    toBot.From.Id = Guid.NewGuid().ToString();
                    toBot.Text = "add appointment";

                    // メッセージを送信して、結果を受信
                    var toUser = await GetResponses(container, MakeRoot, toBot);

                    // 結果の検証
                    Assert.IsTrue(toUser[0].Text.Equals(O365Bot_Models_OutlookEvent.message0_LIST));
                    Assert.IsTrue(toUser[1].Text.Equals(O365Bot_Models_OutlookEvent.Subject_promptDefinition_LIST));

                    toBot.Text = "件名";
                    toUser = await GetResponses(container, MakeRoot, toBot);
                    Assert.IsTrue(toUser[0].Text.Equals(O365Bot_Models_OutlookEvent.Description_promptDefinition_LIST));

                    toBot.Text = "詳細";
                    toUser = await GetResponses(container, MakeRoot, toBot);
                    Assert.IsTrue(toUser[0].Text.Equals(O365Bot_Models_OutlookEvent.Start_promptDefinition_LIST));

                    toBot.Text = "2017/06/06 13:00";
                    toUser = await GetResponses(container, MakeRoot, toBot);
                    Assert.IsTrue((toUser[0].Attachments[0].Content as HeroCard).Text.Equals(O365Bot_Models_OutlookEvent.IsAllDay_promptDefinition_LIST.Replace("{||}","")));

                    toBot.Text = "はい";
                    toUser = await GetResponses(container, MakeRoot, toBot);
                    Assert.IsTrue(toUser[0].Text.Equals(O365BotLabel.Event_Created));
                }
            }
        }

        [TestMethod]
        public async Task ShouldCreateEvent()
        {
            // Fakes を使うためにコンテキストを作成
            using (ShimsContext.Create())
            {
                // AuthBot の GetAccessToken メソッドを実行した際、dummyToken というトークンが返るよう設定
                AuthBot.Fakes.ShimContextExtensions.GetAccessTokenIBotContextString =
                    async (a, e) => { return "dummyToken"; };

                // サービスのモック
                var mockEventService = new Mock<IEventService>();
                mockEventService.Setup(x => x.CreateEvent(It.IsAny<Event>())).Returns(Task.FromResult(true));
                var subscriptionId = Guid.NewGuid().ToString();
                var mockNotificationService = new Mock<INotificationService>();
                mockNotificationService.Setup(x => x.SubscribeEventChange()).ReturnsAsync(subscriptionId);
                mockNotificationService.Setup(x => x.RenewSubscribeEventChange(It.IsAny<string>())).Returns(Task.FromResult(true));

                var builder = new ContainerBuilder();
                builder.RegisterInstance(mockEventService.Object).As<IEventService>();
                builder.RegisterInstance(mockNotificationService.Object).As<INotificationService>();
                WebApiApplication.Container = builder.Build();
                              
                // テストしたいダイアログのインスタンス作成
                IDialog<object> rootDialog = new RootDialog();

                // メモリ内で実行できる環境を作成
                Func<IDialog<object>> MakeRoot = () => rootDialog;
                using (new FiberTestBase.ResolveMoqAssembly(rootDialog))
                using (var container = Build(Options.MockConnectorFactory | Options.ScopedQueue, rootDialog))
                {
                    // グローバルメッセージ登録
                    RegisterBotModules(container);

                    // Bot に送るメッセージを作成
                    var toBot = DialogTestBase.MakeTestMessage();
                    // ロケールで日本語を指定
                    toBot.Locale = locale;
                    toBot.From.Id = Guid.NewGuid().ToString();
                    toBot.Text = "add appointment";

                    // メッセージを送信して、結果を受信
                    var toUser = await GetResponses(container, MakeRoot, toBot);

                    // 結果の検証
                    Assert.IsTrue(toUser[0].Text.Equals(O365Bot_Models_OutlookEvent.message0_LIST));
                    Assert.IsTrue(toUser[1].Text.Equals(O365Bot_Models_OutlookEvent.Subject_promptDefinition_LIST));

                    toBot.Text = "件名";
                    toUser = await GetResponses(container, MakeRoot, toBot);
                    Assert.IsTrue(toUser[0].Text.Equals(O365Bot_Models_OutlookEvent.Description_promptDefinition_LIST));

                    toBot.Text = "詳細";
                    toUser = await GetResponses(container, MakeRoot, toBot);
                    Assert.IsTrue(toUser[0].Text.Equals(O365Bot_Models_OutlookEvent.Start_promptDefinition_LIST));

                    toBot.Text = "2017/06/06 13:00";
                    toUser = await GetResponses(container, MakeRoot, toBot);
                    Assert.IsTrue((toUser[0].Attachments[0].Content as HeroCard).Text.Equals(O365Bot_Models_OutlookEvent.IsAllDay_promptDefinition_LIST.Replace("{||}","")));
                    
                    toBot.Text = "いいえ";
                    toUser = await GetResponses(container, MakeRoot, toBot);
                    Assert.IsTrue(toUser[0].Text.Equals(O365Bot_Models_OutlookEvent.Hours_promptDefinition_LIST));


                    toBot.Text = "4";
                    toUser = await GetResponses(container, MakeRoot, toBot);
                    Assert.IsTrue(toUser[0].Text.Equals(O365BotLabel.Event_Created));
                }
            }
        }

        [TestMethod]
        public async Task ShouldCancelCurrrentDialog()
        {
            // Fakes を使うためにコンテキストを作成
            using (ShimsContext.Create())
            {
                // AuthBot の GetAccessToken メソッドを実行した際、dummyToken というトークンが返るよう設定
                AuthBot.Fakes.ShimContextExtensions.GetAccessTokenIBotContextString =
                    async (a, e) => { return "dummyToken"; };

                // サービスのモック
                var mockEventService = new Mock<IEventService>();
                mockEventService.Setup(x => x.CreateEvent(It.IsAny<Event>())).Returns(Task.FromResult(true));
                var subscriptionId = Guid.NewGuid().ToString();
                var mockNotificationService = new Mock<INotificationService>();
                mockNotificationService.Setup(x => x.SubscribeEventChange()).ReturnsAsync(subscriptionId);
                mockNotificationService.Setup(x => x.RenewSubscribeEventChange(It.IsAny<string>())).Returns(Task.FromResult(true));

                var builder = new ContainerBuilder();
                builder.RegisterInstance(mockEventService.Object).As<IEventService>();
                builder.RegisterInstance(mockNotificationService.Object).As<INotificationService>();
                WebApiApplication.Container = builder.Build();
               
                // テストしたいダイアログのインスタンス作成
                IDialog<object> rootDialog = new RootDialog();

                // メモリ内で実行できる環境を作成
                Func<IDialog<object>> MakeRoot = () => rootDialog;
                using (new FiberTestBase.ResolveMoqAssembly(rootDialog))
                using (var container = Build(Options.MockConnectorFactory | Options.ScopedQueue, rootDialog))
                {
                    // グローバルメッセージ登録
                    RegisterBotModules(container);

                    // Bot に送るメッセージを作成
                    var toBot = DialogTestBase.MakeTestMessage();
                    // ロケールで日本語を指定
                    toBot.Locale = locale;
                    toBot.From.Id = Guid.NewGuid().ToString();
                    toBot.Text = "add appointment";
                    
                    // メッセージを送信して、結果を受信
                    var toUser = await GetResponses(container, MakeRoot, toBot);

                    // 結果の検証
                    Assert.IsTrue(toUser[0].Text.Equals(O365Bot_Models_OutlookEvent.message0_LIST));
                    Assert.IsTrue(toUser[1].Text.Equals(O365Bot_Models_OutlookEvent.Subject_promptDefinition_LIST));

                    toBot.Text = "件名";
                    toUser = await GetResponses(container, MakeRoot, toBot);
                    Assert.IsTrue(toUser[0].Text.Equals(O365Bot_Models_OutlookEvent.Description_promptDefinition_LIST));

                    toBot.Text = O365BotLabel.Cancel;
                    toUser = await GetResponses(container, MakeRoot, toBot);
                    Assert.IsTrue(toUser.Count.Equals(0));

                    toBot.Text = "add appointment";
                    toUser = await GetResponses(container, MakeRoot, toBot);
                    // 結果の検証
                    Assert.IsTrue(toUser[0].Text.Equals(O365Bot_Models_OutlookEvent.message0_LIST));
                    Assert.IsTrue(toUser[1].Text.Equals(O365Bot_Models_OutlookEvent.Subject_promptDefinition_LIST));
                }
            }
        }

        [TestMethod]
        public async Task ShouldInterruptCurrentDialog()
        {            
            // Fakes を使うためにコンテキストを作成
            using (ShimsContext.Create())
            {
                // AuthBot の GetAccessToken メソッドを実行した際、dummyToken というトークンが返るよう設定
                AuthBot.Fakes.ShimContextExtensions.GetAccessTokenIBotContextString =
                    async (a, e) => { return "dummyToken"; };

                // サービスのモック
                var mockEventService = new Mock<IEventService>();
                mockEventService.Setup(x => x.CreateEvent(It.IsAny<Event>())).Returns(Task.FromResult(true));
                mockEventService.Setup(x => x.GetEvents()).ReturnsAsync(new List<Event>()
                {
                    new Event
                    {
                        Subject = "dummy event",
                        Start = new DateTimeTimeZone()
                        {
                            DateTime = "2017-05-31 12:00",
                            TimeZone = "Standard Tokyo Time"
                        },
                        End = new DateTimeTimeZone()
                        {
                            DateTime = "2017-05-31 13:00",
                            TimeZone = "Standard Tokyo Time"
                        }
                    }
                });
                var subscriptionId = Guid.NewGuid().ToString();
                var mockNotificationService = new Mock<INotificationService>();
                mockNotificationService.Setup(x => x.SubscribeEventChange()).ReturnsAsync(subscriptionId);
                mockNotificationService.Setup(x => x.RenewSubscribeEventChange(It.IsAny<string>())).Returns(Task.FromResult(true));

                var builder = new ContainerBuilder();
                builder.RegisterInstance(mockEventService.Object).As<IEventService>();
                builder.RegisterInstance(mockNotificationService.Object).As<INotificationService>();
                WebApiApplication.Container = builder.Build();

                // テストしたいダイアログのインスタンス作成
                IDialog<object> rootDialog = new RootDialog();

                // メモリ内で実行できる環境を作成
                Func<IDialog<object>> MakeRoot = () => rootDialog;
                using (new FiberTestBase.ResolveMoqAssembly(rootDialog))
                using (var container = Build(Options.MockConnectorFactory | Options.ScopedQueue, rootDialog))
                {
                    // グローバルメッセージ登録
                    RegisterBotModules(container);

                    // Bot に送るメッセージを作成
                    var toBot = DialogTestBase.MakeTestMessage();
                    // ロケールで日本語を指定
                    toBot.Locale = locale;
                    toBot.From.Id = Guid.NewGuid().ToString();
                    toBot.Text = "add appointment";

                    // メッセージを送信して、結果を受信
                    var toUser = await GetResponses(container, MakeRoot, toBot);

                    // 結果の検証
                    Assert.IsTrue(toUser[0].Text.Equals(O365Bot_Models_OutlookEvent.message0_LIST));
                    Assert.IsTrue(toUser[1].Text.Equals(O365Bot_Models_OutlookEvent.Subject_promptDefinition_LIST));

                    toBot.Text = "件名";
                    toUser = await GetResponses(container, MakeRoot, toBot);
                    Assert.IsTrue(toUser[0].Text.Equals(O365Bot_Models_OutlookEvent.Description_promptDefinition_LIST));

                    toBot.Text = O365BotLabel.Get_Events;
                    toUser = await GetResponses(container, MakeRoot, toBot);
                    Assert.IsTrue(toUser[0].Text.Equals("2017-05-31 12:00-2017-05-31 13:00: dummy event"));

                    toBot.Text = "詳細";
                    toUser = await GetResponses(container, MakeRoot, toBot);
                    Assert.IsTrue(toUser[0].Text.Equals(O365Bot_Models_OutlookEvent.Start_promptDefinition_LIST));
                }
            }
        }

        [TestMethod]
        public async Task ShouldReceiveNotifyEventChageDialog()
        {
            // Fakes を使うためにコンテキストを作成
            using (ShimsContext.Create())
            {
                // AuthBot の GetAccessToken メソッドを実行した際、dummyToken というトークンが返るよう設定
                AuthBot.Fakes.ShimContextExtensions.GetAccessTokenIBotContextString =
                    async (a, e) => { return "dummyToken"; };

                // サービスのモック
                var mockEventService = new Mock<IEventService>();
                mockEventService.Setup(x => x.CreateEvent(It.IsAny<Event>())).Returns(Task.FromResult(true));
                mockEventService.Setup(x => x.GetEvent(It.IsAny<string>())).ReturnsAsync(new Event()
                {
                    Subject = "dummy event",
                    Start = new DateTimeTimeZone()
                    {
                        DateTime = "2017-05-31 12:00",
                        TimeZone = "Standard Tokyo Time"
                    },
                    End = new DateTimeTimeZone()
                    {
                        DateTime = "2017-05-31 13:00",
                        TimeZone = "Standard Tokyo Time"
                    },
                    Body = new ItemBody()
                    {
                        Content = "Dummy Body"
                    },
                    Location = new Location()
                    {
                        DisplayName = "Dummy Location"
                    }
                });
                
                var subscriptionId = Guid.NewGuid().ToString();
                var mockNotificationService = new Mock<INotificationService>();
                mockNotificationService.Setup(x => x.SubscribeEventChange()).ReturnsAsync(subscriptionId);
                mockNotificationService.Setup(x => x.RenewSubscribeEventChange(It.IsAny<string>())).Returns(Task.FromResult(true));

                var builder = new ContainerBuilder();
                builder.RegisterInstance(mockEventService.Object).As<IEventService>();
                builder.RegisterInstance(mockNotificationService.Object).As<INotificationService>();
                WebApiApplication.Container = builder.Build();

                // テストしたいダイアログのインスタンス作成
                IDialog<object> rootDialog = new RootDialog();

                // メモリ内で実行できる環境を作成
                Func<IDialog<object>> MakeRoot = () => rootDialog;
                using (new FiberTestBase.ResolveMoqAssembly(rootDialog))
                using (var container = Build(Options.MockConnectorFactory | Options.ScopedQueue, rootDialog))
                {
                    // グローバルメッセージ登録
                    RegisterBotModules(container);

                    // Bot に送るメッセージを作成
                    var toBot = DialogTestBase.MakeTestMessage();
                    // ロケールで日本語を指定
                    toBot.Locale = locale;
                    toBot.From.Id = Guid.NewGuid().ToString();
                    toBot.Text = "add appointment";
                    
                    // メッセージを送信して、結果を受信
                    var toUser = await GetResponses(container, MakeRoot, toBot);

                    // 結果の検証
                    Assert.IsTrue(toUser[0].Text.Equals(O365Bot_Models_OutlookEvent.message0_LIST));
                    Assert.IsTrue(toUser[1].Text.Equals(O365Bot_Models_OutlookEvent.Subject_promptDefinition_LIST));

                    toBot.Text = "件名";
                    toUser = await GetResponses(container, MakeRoot, toBot);
                    Assert.IsTrue(toUser[0].Text.Equals(O365Bot_Models_OutlookEvent.Description_promptDefinition_LIST));
                    
                    // プロアクティブ通知
                    // 現在の会話情報を、購読 ID を元に取りだす。
                    var conversationReference = CacheService.caches[subscriptionId] as ConversationReference;
                    // イベント ID を取り出す。
                    var id = Guid.NewGuid().ToString();

                    // ロケールを取り出して設定
                    var activity = conversationReference.GetPostToBotMessage();
                    locale = CacheService.caches[activity.From.Id].ToString();
                    Thread.CurrentThread.CurrentCulture = new CultureInfo(locale);
                    Thread.CurrentThread.CurrentUICulture = new CultureInfo(locale);
                    toUser = await Resume(container, new NotifyEventChageDialog(id), activity);

                    Assert.IsTrue((toUser[0].Attachments[0].Content as HeroCard).Text.Equals(O365BotLabel.Notification_Event_Changed));

                    toBot.Text = O365BotLabel.Ask_To_See_Detail;
                    toUser = await GetResponses(container, MakeRoot, toBot);
                    Assert.IsTrue(toUser[0].Text.Equals(O365BotLabel.Get_Event_Detail));
                    Assert.IsTrue(toUser[1].Text.Equals("2017-05-31 12:00-2017-05-31 13:00: dummy event@Dummy Location-Dummy Body"));

                    toBot.Text = "詳細";
                    toUser = await GetResponses(container, MakeRoot, toBot);
                    Assert.IsTrue(toUser[0].Text.Equals(O365Bot_Models_OutlookEvent.Start_promptDefinition_LIST));
                }
            }
        }

        /// <summary>
        /// グローバルメッセージ登録
        /// </summary>
        private void RegisterBotModules(IContainer container)
        {
            var builder = new ContainerBuilder();
            builder.RegisterModule(new ReflectionSurrogateModule());
            builder.RegisterModule<GlobalMessageHandlers>();
            builder.Update(container);
        }

        /// <summary>
        /// Bot にメッセージを送って、結果を受信
        /// </summary>
        public async Task<IMessageActivity> GetResponse(IContainer container, Func<IDialog<object>> makeRoot, IMessageActivity toBot)
        {
            using (var scope = DialogModule.BeginLifetimeScope(container, toBot))
            {
                DialogModule_MakeRoot.Register(scope, makeRoot);

                // act: sending the message
                using (new LocalizedScope(toBot.Locale))
                {
                    var task = scope.Resolve<IPostToBot>();
                    await task.PostAsync(toBot, CancellationToken.None);
                }
                //await Conversation.SendAsync(toBot, makeRoot, CancellationToken.None);
                return scope.Resolve<Queue<IMessageActivity>>().Dequeue();
            }
        }

        /// <summary>
        /// Bot にメッセージを送って、結果を受信
        /// </summary>
        public async Task<List<IMessageActivity>> GetResponses(IContainer container, Func<IDialog<object>> makeRoot, IMessageActivity toBot)
        {
            using (var scope = DialogModule.BeginLifetimeScope(container, toBot))
            {
                var results = new List<IMessageActivity>();
                DialogModule_MakeRoot.Register(scope, makeRoot);

                // act: sending the message
                using (new LocalizedScope(toBot.Locale))
                {
                    var task = scope.Resolve<IPostToBot>();
                    await task.PostAsync(toBot, CancellationToken.None);
                }
                //await Conversation.SendAsync(toBot, makeRoot, CancellationToken.None);
                var queue= scope.Resolve<Queue<IMessageActivity>>();
                while(queue.Count != 0)
                {
                    results.Add(queue.Dequeue());
                }

                return results;
            }
        }

        /// <summary>
        /// プロアクティブ通知でダイアログ差し込みを行い、結果を受信
        /// </summary>
        public async Task<List<IMessageActivity>> Resume(IContainer container, IDialog<object> dialog, IMessageActivity toBot)
        {
            using (var scope = DialogModule.BeginLifetimeScope(container, toBot))
            {
                var results = new List<IMessageActivity>();

                var botData = scope.Resolve<IBotData>();
                await botData.LoadAsync(CancellationToken.None);
                var task = scope.Resolve<IDialogTask>();

                //現在の会話にダイアログを差し込み
                task.Call(dialog.Void<object, IMessageActivity>(), null);
                await task.PollAsync(CancellationToken.None);
                await botData.FlushAsync(CancellationToken.None);
                
                // 結果の取得
                var queue = scope.Resolve<Queue<IMessageActivity>>();
                while (queue.Count != 0)
                {
                    results.Add(queue.Dequeue());
                }

                return results;
            }
        }        
    }
}

ファンクションテストの更新

ファンクションテストでは、いくつかの課題があり、今回は対応を断念。

- 今回採用している Direct Line での接続方法では、Bot Connector 側からのプロアクティブ通知を受信できない。
- Microsoft Graph 側の通知をどうするか。

まとめ

今回 Microsoft Graph の通知や前回記事のコードが動かない箇所があり(修正済)時間がかかりました。通知については今後よりリッチになっていくと思いますが、現時点では変更されたという事位しかわからないようで、差分だけ取ることが出来ませんでした。いいやり方あればコメントでお願いします。

またプロアクティブ通知の部分でテストコードと実際のコードで 2 重にコード書いているところが残念ですが、一旦これで。

次回はメッセージのインターセプトを紹介します。ミドルウェア的に処理を入れ込む感じで、できることが広がりますよ!