public async Task ChannelsWithMessageUpdates_CanBeChanged()
        {
            var shortCircuited = false;
            var deleted        = false;

            await RunTest();

            Assert.IsTrue(shortCircuited, "Non-updating channel didn't short-circuit");
            Assert.IsFalse(deleted, "Non-updating channel deleted activity");

            await RunTest(middleware => middleware.ChannelsWithMessageUpdates.Add(Channels.Test));

            Assert.IsFalse(shortCircuited, "Updating channel short-circuited");
            Assert.IsTrue(deleted, "Updating channel didn't delete activity");

            async Task RunTest(Action <CardManagerMiddleware> action = null)
            {
                const string ActionId = "action ID";

                var botState   = CreateUserState();
                var middleware = new CardManagerMiddleware(new CardManager(botState));
                var adapter    = new TestAdapter().Use(middleware);

                var cardActivity = MessageFactory.Attachment(new HeroCard(buttons: new List <CardAction>
                {
                    new CardAction(ActionTypes.PostBack, value: new object()),
                }).ToAttachment());

                var actionActivity = new Activity(value: new JObject {
                    { DataIdScopes.Action, ActionId }
                }.WrapLibraryData());

                action?.Invoke(middleware);

                middleware.UpdatingOptions.IdOptions.Set(DataIdScopes.Action, ActionId);
                middleware.NonUpdatingOptions.IdOptions.Set(DataIdScopes.Action, ActionId);

                await new TestFlow(adapter, async(turnContext, cancellationToken) =>
                {
                    if (turnContext.Activity.Value == null)
                    {
                        await turnContext.SendActivityAsync(cardActivity);
                    }

                    shortCircuited = false;

                    await botState.SaveChangesAsync(turnContext);
                })
                .Send("hi")
                .Do(() => Assert.IsTrue(adapter.ActiveQueue.Contains(cardActivity)))
                .Send(actionActivity)
                .Do(() => deleted        = !adapter.ActiveQueue.Contains(cardActivity))
                .Do(() => shortCircuited = true)
                .Send(actionActivity)
                .StartTestAsync();
            }
        }
        public async Task OnDeleteActivity_Ignore()
        {
            const string UserSays_PleaseDelete = "please delete";

            await RunTest(true, 1);
            await RunTest(false, 0);

            async Task RunTest(bool ignore, int activityCount)
            {
                var botState   = CreateUserState();
                var accessor   = botState.CreateProperty <CardManagerState>(nameof(CardManagerState));
                var middleware = new CardManagerMiddleware(new CardManager(botState));
                var adapter    = new TestAdapter().Use(middleware);

                var cardActivity = MessageFactory.Attachment(new HeroCard(buttons: new List <CardAction>
                {
                    new CardAction(ActionTypes.PostBack, value: new JObject()),
                }).ToAttachment());

                ResourceResponse response = null;

                middleware.ChannelsWithMessageUpdates.Add(Channels.Test);

                await new TestFlow(adapter, async(turnContext, cancellationToken) =>
                {
                    if (turnContext.Activity.Text == UserSays_PleaseDelete)
                    {
                        if (ignore)
                        {
                            var ignoreDelete = turnContext.TurnState.Get <CardManagerTurnState>()?.MiddlewareIgnoreDelete;

                            ignoreDelete?.Add(response.Id);
                        }

                        await turnContext.DeleteActivityAsync(response.Id);
                    }
                    else
                    {
                        response = await turnContext.SendActivityAsync(cardActivity);
                    }

                    var state = await accessor.GetNotNullAsync(turnContext, () => new CardManagerState());

                    await turnContext.SendActivityAsync($"Saved: {state.SavedActivities.Count}");

                    await botState.SaveChangesAsync(turnContext);
                })
                .Send("hi")
                .AssertReply(cardActivity, EqualityComparer <IActivity> .Default)
                .AssertReply($"Saved: {1}")
                .Send(UserSays_PleaseDelete)
                .AssertReply($"Saved: {activityCount}")
                .StartTestAsync();
            }
        }
        public AdapterWithMiddleware(
            IConfiguration configuration,
            InspectionState inspectionState,
            ConversationState conversationState,
            CardManager cardManager,
            ILogger <BotFrameworkHttpAdapter> logger)
            : base(configuration, logger)
        {
            // Inspection needs credentials because it will be sending the Activities and User and Conversation State to the emulator
            var credentials = new MicrosoftAppCredentials(configuration["MicrosoftAppId"], configuration["MicrosoftAppPassword"]);

            Use(new InspectionMiddleware(inspectionState, null, conversationState, credentials));

            var cardManagerMiddleware = new CardManagerMiddleware(cardManager);

            cardManagerMiddleware.NonUpdatingOptions.AutoClearEnabledOnSend = false;

            Use(cardManagerMiddleware
                .SetAutoApplyIds(false)
                .SetIdOptions(new DataIdOptions(new[]
            {
                DataIdScopes.Action,
                DataIdScopes.Card,
                DataIdScopes.Carousel,
                DataIdScopes.Batch,
            })));

            OnTurnError = async(turnContext, exception) =>
            {
                // Log any leaked exception from the application.
                logger.LogError(exception, $"[OnTurnError] unhandled error : {exception.Message}");

                // Send a message to the user
                await turnContext.SendActivityAsync("The bot encountered an error or bug.");

                await turnContext.SendActivityAsync("To continue to run this bot, please fix the bot source code.");

                // Send a trace activity, which will be displayed in the Bot Framework Emulator
                await turnContext.TraceActivityAsync("OnTurnError Trace", exception.Message, "https://www.botframework.com/schemas/error", "TurnError");
            };
        }
        public async Task IdTrackingStyle_Disabled()
        {
            const string ActionId = "action ID";

            var botState   = CreateUserState();
            var accessor   = botState.CreateProperty <CardManagerState>(nameof(CardManagerState));
            var middleware = new CardManagerMiddleware(new CardManager(botState));
            var adapter    = new TestAdapter().Use(middleware);

            var cardActivity = MessageFactory.Attachment(new HeroCard(buttons: new List <CardAction>
            {
                new CardAction(ActionTypes.PostBack, value: new object()),
            }).ToAttachment());

            var actionActivity = new Activity(value: new JObject {
                { DataIdScopes.Action, ActionId }
            }.WrapLibraryData());

            middleware.NonUpdatingOptions.IdTrackingStyle = TrackingStyle.TrackDisabled;
            middleware.NonUpdatingOptions.IdOptions.Set(DataIdScopes.Action, ActionId);

            await new TestFlow(adapter, async(turnContext, cancellationToken) =>
            {
                if (turnContext.Activity.Value == null)
                {
                    await turnContext.SendActivityAsync(cardActivity);
                }

                var state = await accessor.GetNotNullAsync(turnContext, () => new CardManagerState());

                await turnContext.SendActivityAsync(
                    $"Tracked: {state.DataIdsByScope.TryGetValue(DataIdScopes.Action, out var set) && set.Contains(ActionId)}");
            })
            .Send("hi")
            .AssertReply(cardActivity, EqualityComparer <IActivity> .Default)
            .AssertReply($"Tracked: {false}")
            .Send(actionActivity)
            .AssertReply($"Tracked: {true}")
            .StartTestAsync();
        }
        private static async Task TestAutoDeactivate(
            bool useChannelWithMessageUpdates,
            string autoDeactivateInAction,
            bool autoDeactivateInOptions)
        {
            const string ActionId = "action ID";

            var botState             = CreateUserState();
            var accessor             = botState.CreateProperty <CardManagerState>(nameof(CardManagerState));
            var middleware           = new CardManagerMiddleware(new CardManager(botState));
            var adapter              = new TestAdapter().Use(middleware);
            var expectedToDeactivate = autoDeactivateInAction == BehaviorSwitch.On ||
                                       (autoDeactivateInAction != BehaviorSwitch.Off && autoDeactivateInOptions);
            var expectedInStateBefore = !useChannelWithMessageUpdates;
            var expectedInQueueBefore = true;
            var expectedInStateAfter  = !(useChannelWithMessageUpdates || expectedToDeactivate);
            var expectedInQueueAfter  = !(useChannelWithMessageUpdates && expectedToDeactivate);
            var isInState             = false;
            var isInQueue             = false;
            var activitiesProcessed   = 0;

            var cardActivity = MessageFactory.Attachment(new HeroCard(buttons: new List <CardAction>
            {
                new CardAction(ActionTypes.PostBack, value: new Dictionary <string, object>
                {
                    { Behaviors.AutoDeactivate, autoDeactivateInAction },
                }.WrapLibraryData()),
            }).ToAttachment());

            var actionActivity = new Activity(value: new JObject
            {
                { Behaviors.AutoDeactivate, autoDeactivateInAction },
                { DataIdScopes.Action, ActionId },
            }.WrapLibraryData());

            if (useChannelWithMessageUpdates)
            {
                middleware.ChannelsWithMessageUpdates.Add(Channels.Test);
                middleware.UpdatingOptions.IdOptions.Set(DataIdScopes.Action, ActionId);
                middleware.UpdatingOptions.AutoDeleteOnAction = autoDeactivateInOptions;
            }
            else
            {
                middleware.NonUpdatingOptions.IdOptions.Set(DataIdScopes.Action, ActionId);
                middleware.NonUpdatingOptions.AutoDisableOnAction    = autoDeactivateInOptions;
                middleware.NonUpdatingOptions.AutoClearEnabledOnSend = false;
            }

            void AssertCard(bool expected, bool useState) => Assert.AreEqual(
                expected,
                useState ? isInState : isInQueue,
                $"Card activity {(expected ? "wasn't" : "was")} found in {(useState ? "state" : "queue")}");

            await new TestFlow(adapter, async(turnContext, cancellationToken) =>
            {
                if (turnContext.Activity.Value == null)
                {
                    await turnContext.SendActivityAsync(cardActivity);
                }

                var state = await accessor.GetNotNullAsync(turnContext, () => new CardManagerState());

                isInState = state.DataIdsByScope.TryGetValue(DataIdScopes.Action, out var set) && set.Contains(ActionId);
                isInQueue = adapter.ActiveQueue.Contains(cardActivity);

                activitiesProcessed++;

                await botState.SaveChangesAsync(turnContext);
            })
            .Send("hi")
            .Do(() => AssertCard(expectedInStateBefore, true))
            .Do(() => AssertCard(expectedInQueueBefore, false))
            .Send(actionActivity)
            .Do(() => AssertCard(expectedInStateAfter, true))
            .Do(() => AssertCard(expectedInQueueAfter, false))
            .StartTestAsync();

            Assert.AreEqual(2, activitiesProcessed);
        }