diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/AdaptiveCardsTests.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/AdaptiveCardsTests.cs index dfce11f111..db7a093504 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/AdaptiveCardsTests.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI.Tests/Application/AdaptiveCardsTests.cs @@ -3,16 +3,16 @@ using Microsoft.TeamsAI.Application; using Newtonsoft.Json; using Microsoft.Bot.Builder; -using AdaptiveCards; using Newtonsoft.Json.Linq; using Microsoft.TeamsAI.Exceptions; +using Moq; namespace Microsoft.TeamsAI.Tests.Application { public class AdaptiveCardsTests { [Fact] - public async void Test_OnActionExecute_Verb_AdaptiveCard() + public async void Test_OnActionExecute_Verb() { // Arrange Activity[]? activitiesToSend = null; @@ -31,43 +31,23 @@ void CaptureSend(Activity[] arg) { type = "Action.Execute", verb = "test-verb", - data = new - { - testKey = "test-value" - } + data = new { testKey = "test-value" } } }) }); + var adaptiveCardInvokeResponseMock = new Mock(); var expectedInvokeResponse = new InvokeResponse() { Status = 200, - Body = new AdaptiveCardInvokeResponse() - { - StatusCode = 200, - Type = "application/vnd.microsoft.card.adaptive", - Value = new AdaptiveCard("1.4") - { - Body = new List - { - new AdaptiveTextBlock("test-value") - } - } - } + Body = adaptiveCardInvokeResponseMock.Object }; var app = new TeamsAI.Application.Application(new()); var adaptiveCards = new AdaptiveCards(app); - ActionExecuteAdaptiveCardHandler handler = (turnContext, turnState, data, cancellationToken) => + ActionExecuteHandler handler = (turnContext, turnState, data, cancellationToken) => { TestAdaptiveCardActionData actionData = Cast(data); Assert.Equal("test-value", actionData.TestKey); - var adaptiveCard = new AdaptiveCard("1.4") - { - Body = new List - { - new AdaptiveTextBlock(actionData.TestKey) - } - }; - return Task.FromResult(adaptiveCard); + return Task.FromResult(adaptiveCardInvokeResponseMock.Object); }; // Act @@ -82,7 +62,7 @@ void CaptureSend(Activity[] arg) } [Fact] - public async void Test_OnActionExecute_Verb_AdaptiveCard_NotHit() + public async void Test_OnActionExecute_Verb_NotHit() { // Arrange Activity[]? activitiesToSend = null; @@ -91,7 +71,7 @@ void CaptureSend(Activity[] arg) activitiesToSend = arg; } var adapter = new SimpleAdapter(CaptureSend); - var turnContext = new TurnContext(adapter, new Activity() + var turnContext1 = new TurnContext(adapter, new Activity() { Type = ActivityTypes.Invoke, Name = "adaptiveCard/action", @@ -100,141 +80,34 @@ void CaptureSend(Activity[] arg) action = new { type = "Action.Execute", - verb = "test-verb", - data = new - { - testKey = "test-value" - } + verb = "not-test-verb" } }) }); - var app = new TeamsAI.Application.Application(new()); - var adaptiveCards = new AdaptiveCards(app); - ActionExecuteAdaptiveCardHandler handler = (turnContext, turnState, data, cancellationToken) => - { - TestAdaptiveCardActionData actionData = Cast(data); - Assert.Equal("test-value", actionData.TestKey); - var adaptiveCard = new AdaptiveCard("1.4") - { - Body = new List - { - new AdaptiveTextBlock(actionData.TestKey) - } - }; - return Task.FromResult(adaptiveCard); - }; - - // Act - adaptiveCards.OnActionExecute("not-test-verb", handler); - await app.OnTurnAsync(turnContext); - - // Assert - Assert.Null(activitiesToSend); - } - - [Fact] - public async void Test_OnActionExecute_Verb_Text() - { - // Arrange - Activity[]? activitiesToSend = null; - void CaptureSend(Activity[] arg) - { - activitiesToSend = arg; - } - var adapter = new SimpleAdapter(CaptureSend); - var turnContext = new TurnContext(adapter, new Activity() + var turnContext2 = new TurnContext(adapter, new Activity() { Type = ActivityTypes.Invoke, - Name = "adaptiveCard/action", - Value = JObject.FromObject(new - { - action = new - { - type = "Action.Execute", - verb = "test-verb", - data = new - { - testKey = "test-value" - } - } - }) + Name = "application/search" }); - var expectedInvokeResponse = new InvokeResponse() - { - Status = 200, - Body = new AdaptiveCardInvokeResponse() - { - StatusCode = 200, - Type = "application/vnd.microsoft.activity.message", - Value = "test-value" - } - }; + var adaptiveCardInvokeResponseMock = new Mock(); var app = new TeamsAI.Application.Application(new()); var adaptiveCards = new AdaptiveCards(app); - ActionExecuteTextHandler handler = (turnContext, turnState, data, cancellationToken) => + ActionExecuteHandler handler = (turnContext, turnState, data, cancellationToken) => { - TestAdaptiveCardActionData actionData = Cast(data); - Assert.Equal("test-value", actionData.TestKey); - return Task.FromResult(actionData.TestKey!); + return Task.FromResult(adaptiveCardInvokeResponseMock.Object); }; // Act adaptiveCards.OnActionExecute("test-verb", handler); - await app.OnTurnAsync(turnContext); - - // Assert - Assert.NotNull(activitiesToSend); - Assert.Equal(1, activitiesToSend.Length); - Assert.Equal("invokeResponse", activitiesToSend[0].Type); - Assert.Equivalent(expectedInvokeResponse, activitiesToSend[0].Value); - } - - [Fact] - public async void Test_OnActionExecute_Verb_Text_NotHit() - { - // Arrange - Activity[]? activitiesToSend = null; - void CaptureSend(Activity[] arg) - { - activitiesToSend = arg; - } - var adapter = new SimpleAdapter(CaptureSend); - var turnContext = new TurnContext(adapter, new Activity() - { - Type = ActivityTypes.Invoke, - Name = "adaptiveCard/action", - Value = JObject.FromObject(new - { - action = new - { - type = "Action.Execute", - verb = "test-verb", - data = new - { - testKey = "test-value" - } - } - }) - }); - var app = new TeamsAI.Application.Application(new()); - var adaptiveCards = new AdaptiveCards(app); - ActionExecuteTextHandler handler = (turnContext, turnState, data, cancellationToken) => - { - TestAdaptiveCardActionData actionData = Cast(data); - Assert.Equal("test-value", actionData.TestKey); - return Task.FromResult(actionData.TestKey!); - }; - - // Act - adaptiveCards.OnActionExecute("not-test-verb", handler); - await app.OnTurnAsync(turnContext); + await app.OnTurnAsync(turnContext1); + await app.OnTurnAsync(turnContext2); // Assert Assert.Null(activitiesToSend); } [Fact] - public async void Test_OnActionExecute_RouteSelector_Text_ActivityNotMatched() + public async void Test_OnActionExecute_RouteSelector_ActivityNotMatched() { var adapter = new SimpleAdapter(); var turnContext = new TurnContext(adapter, new Activity() @@ -242,16 +115,16 @@ public async void Test_OnActionExecute_RouteSelector_Text_ActivityNotMatched() Type = ActivityTypes.Invoke, Name = "application/search" }); + var adaptiveCardInvokeResponseMock = new Mock(); var app = new TeamsAI.Application.Application(new()); var adaptiveCards = new AdaptiveCards(app); RouteSelector routeSelector = (turnContext, cancellationToken) => { return Task.FromResult(true); }; - ActionExecuteTextHandler handler = (turnContext, turnState, data, cancellationToken) => + ActionExecuteHandler handler = (turnContext, turnState, data, cancellationToken) => { - TestAdaptiveCardActionData actionData = Cast(data); - return Task.FromResult(actionData.TestKey!); + return Task.FromResult(adaptiveCardInvokeResponseMock.Object); }; // Act diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/AdaptiveCards.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/AdaptiveCards.cs index c9739b5fdc..aad41d0d70 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/AdaptiveCards.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/Application/AdaptiveCards.cs @@ -1,5 +1,4 @@ -using AdaptiveCards; -using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder; using Microsoft.Bot.Schema; using Microsoft.TeamsAI.Exceptions; using Microsoft.TeamsAI.State; @@ -78,19 +77,7 @@ public AdaptiveCardsSearchResult(string title, string value) /// A cancellation token that can be used by other objects /// or threads to receive notice of cancellation. /// A task that represents the work queued to execute. - public delegate Task ActionExecuteAdaptiveCardHandler(ITurnContext turnContext, TState turnState, object data, CancellationToken cancellationToken); - - /// - /// Function for handling Adaptive Card Action.Execute events. - /// - /// Type of the turn state. This allows for strongly typed access to the turn state. - /// A strongly-typed context object for this turn. - /// The turn state object that stores arbitrary data for this turn. - /// The data associated with the action. - /// A cancellation token that can be used by other objects - /// or threads to receive notice of cancellation. - /// A task that represents the work queued to execute. - public delegate Task ActionExecuteTextHandler(ITurnContext turnContext, TState turnState, object data, CancellationToken cancellationToken); + public delegate Task ActionExecuteHandler(ITurnContext turnContext, TState turnState, object data, CancellationToken cancellationToken); /// /// Function for handling Adaptive Card Action.Submit events. @@ -147,7 +134,7 @@ public AdaptiveCards(Application app) /// The named action to be handled. /// The code to execute when the action is triggered. /// The application for chaining purposes. - public Application OnActionExecute(string verb, ActionExecuteAdaptiveCardHandler handler) + public Application OnActionExecute(string verb, ActionExecuteHandler handler) { Verify.ParamNotNull(verb); Verify.ParamNotNull(handler); @@ -161,7 +148,7 @@ public Application OnActionExecute(string verb, Actio /// The named action to be handled. /// The code to execute when the action is triggered. /// The application for chaining purposes. - public Application OnActionExecute(Regex verbPattern, ActionExecuteAdaptiveCardHandler handler) + public Application OnActionExecute(Regex verbPattern, ActionExecuteHandler handler) { Verify.ParamNotNull(verbPattern); Verify.ParamNotNull(handler); @@ -175,7 +162,7 @@ public Application OnActionExecute(Regex verbPattern, /// The named action to be handled. /// The code to execute when the action is triggered. /// The application for chaining purposes. - public Application OnActionExecute(RouteSelector routeSelector, ActionExecuteAdaptiveCardHandler handler) + public Application OnActionExecute(RouteSelector routeSelector, ActionExecuteHandler handler) { Verify.ParamNotNull(routeSelector); Verify.ParamNotNull(handler); @@ -191,13 +178,7 @@ public Application OnActionExecute(RouteSelector rout throw new TeamsAIException($"Unexpected AdaptiveCards.OnActionExecute() triggered for activity type: {turnContext.Activity.Type}"); } - AdaptiveCard adaptiveCard = await handler(turnContext, turnState, invokeValue.Action.Data, cancellationToken); - AdaptiveCardInvokeResponse adaptiveCardInvokeResponse = new() - { - StatusCode = 200, - Type = "application/vnd.microsoft.card.adaptive", - Value = adaptiveCard - }; + AdaptiveCardInvokeResponse adaptiveCardInvokeResponse = await handler(turnContext, turnState, invokeValue.Action.Data, cancellationToken); InvokeResponse invokeResponse = CreateInvokeResponse(adaptiveCardInvokeResponse); Activity activity = new() { @@ -216,110 +197,7 @@ public Application OnActionExecute(RouteSelector rout /// The named actions to be handled. /// The code to execute when the action is triggered. /// The application for chaining purposes. - public Application OnActionExecute(MultipleRouteSelector routeSelectors, ActionExecuteAdaptiveCardHandler handler) - { - Verify.ParamNotNull(routeSelectors); - Verify.ParamNotNull(handler); - if (routeSelectors.Strings != null) - { - foreach (string verb in routeSelectors.Strings) - { - OnActionExecute(verb, handler); - } - } - if (routeSelectors.Regexes != null) - { - foreach (Regex verbPattern in routeSelectors.Regexes) - { - OnActionExecute(verbPattern, handler); - } - } - if (routeSelectors.RouteSelectors != null) - { - foreach (RouteSelector routeSelector in routeSelectors.RouteSelectors) - { - OnActionExecute(routeSelector, handler); - } - } - return _app; - } - - /// - /// Adds a route to the application for handling Adaptive Card Action.Execute events. - /// - /// The named action to be handled. - /// The code to execute when the action is triggered. - /// The application for chaining purposes. - public Application OnActionExecute(string verb, ActionExecuteTextHandler handler) - { - Verify.ParamNotNull(verb); - Verify.ParamNotNull(handler); - RouteSelector routeSelector = CreateActionExecuteSelector((string input) => string.Equals(verb, input)); - return OnActionExecute(routeSelector, handler); - } - - /// - /// Adds a route to the application for handling Adaptive Card Action.Execute events. - /// - /// The named action to be handled. - /// The code to execute when the action is triggered. - /// The application for chaining purposes. - public Application OnActionExecute(Regex verbPattern, ActionExecuteTextHandler handler) - { - Verify.ParamNotNull(verbPattern); - Verify.ParamNotNull(handler); - RouteSelector routeSelector = CreateActionExecuteSelector((string input) => verbPattern.IsMatch(input)); - return OnActionExecute(routeSelector, handler); - } - - /// - /// Adds a route to the application for handling Adaptive Card Action.Execute events. - /// - /// The named action to be handled. - /// The code to execute when the action is triggered. - /// The application for chaining purposes. - public Application OnActionExecute(RouteSelector routeSelector, ActionExecuteTextHandler handler) - { - Verify.ParamNotNull(routeSelector); - Verify.ParamNotNull(handler); - RouteHandler routeHandler = async (turnContext, turnState, cancellationToken) => - { - AdaptiveCardInvokeValue? invokeValue; - if (!string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) - || !string.Equals(turnContext.Activity.Name, ACTION_INVOKE_NAME) - || (invokeValue = GetInvokeValue(turnContext.Activity)) == null - || invokeValue.Action == null - || !string.Equals(invokeValue.Action.Type, ACTION_EXECUTE_TYPE)) - { - throw new TeamsAIException($"Unexpected AdaptiveCards.OnActionExecute() triggered for activity type: {turnContext.Activity.Type}"); - } - - string result = await handler(turnContext, turnState, invokeValue.Action.Data, cancellationToken); - AdaptiveCardInvokeResponse adaptiveCardInvokeResponse = new() - { - StatusCode = 200, - Type = "application/vnd.microsoft.activity.message", - Value = result - }; - InvokeResponse invokeResponse = CreateInvokeResponse(adaptiveCardInvokeResponse); - Activity activity = new() - { - Type = ActivityTypesEx.InvokeResponse, - Value = invokeResponse - }; - await turnContext.SendActivityAsync(activity, cancellationToken); - }; - _app.AddRoute(routeSelector, routeHandler, true); - return _app; - } - - /// - /// Adds a route to the application for handling Adaptive Card Action.Execute events. - /// - /// The named actions to be handled. - /// The code to execute when the action is triggered. - /// The application for chaining purposes. - public Application OnActionExecute(MultipleRouteSelector routeSelectors, ActionExecuteTextHandler handler) + public Application OnActionExecute(MultipleRouteSelector routeSelectors, ActionExecuteHandler handler) { Verify.ParamNotNull(routeSelectors); Verify.ParamNotNull(handler); @@ -551,14 +429,14 @@ private RouteSelector CreateActionExecuteSelector(Func isMatch) { RouteSelector routeSelector = (turnContext, cancellationToken) => { - bool isAction = string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) - && string.Equals(turnContext.Activity.Name, ACTION_INVOKE_NAME); - if (!isAction) - { - return Task.FromResult(false); - } - AdaptiveCardInvokeValue? invokeValue = GetInvokeValue(turnContext.Activity); - return Task.FromResult(invokeValue != null && isMatch(invokeValue.Action.Verb)); + AdaptiveCardInvokeValue? invokeValue; + return Task.FromResult( + string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) + && string.Equals(turnContext.Activity.Name, ACTION_INVOKE_NAME) + && (invokeValue = AdaptiveCards.GetInvokeValue(turnContext.Activity)) != null + && invokeValue.Action != null + && string.Equals(invokeValue.Action.Type, ACTION_EXECUTE_TYPE) + && isMatch(invokeValue.Action.Verb)); }; return routeSelector; } @@ -567,15 +445,15 @@ private RouteSelector CreateActionSubmitSelector(Func isMatch, str { RouteSelector routeSelector = (turnContext, cancellationToken) => { - bool isSubmit = string.Equals(turnContext.Activity.Type, ActivityTypes.Message, StringComparison.OrdinalIgnoreCase) + JObject? obj; + return Task.FromResult( + string.Equals(turnContext.Activity.Type, ActivityTypes.Message, StringComparison.OrdinalIgnoreCase) && string.IsNullOrEmpty(turnContext.Activity.Text) - && turnContext.Activity.Value != null; - if (!isSubmit) - { - return Task.FromResult(false); - } - JObject? data = turnContext.Activity.Value as JObject; - return Task.FromResult(data != null && data[filter] != null && data[filter]!.Type == JTokenType.String && isMatch(data[filter]!.Value()!)); + && turnContext.Activity.Value != null + && (obj = turnContext.Activity.Value as JObject) != null + && obj[filter] != null + && obj[filter]!.Type == JTokenType.String + && isMatch(obj[filter]!.Value()!)); }; return routeSelector; } @@ -584,14 +462,12 @@ private RouteSelector CreateSearchSelector(Func isMatch) { RouteSelector routeSelector = (turnContext, cancellationToken) => { - bool isSearch = string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) - && string.Equals(turnContext.Activity.Name, SEARCH_INVOKE_NAME); - if (!isSearch) - { - return Task.FromResult(false); - } - AdaptiveCardSearchInvokeValue? searchInvokeValue = GetInvokeValue(turnContext.Activity); - return Task.FromResult(searchInvokeValue != null && isMatch(searchInvokeValue.Dataset)); + AdaptiveCardSearchInvokeValue? searchInvokeValue; + return Task.FromResult( + string.Equals(turnContext.Activity.Type, ActivityTypes.Invoke, StringComparison.OrdinalIgnoreCase) + && string.Equals(turnContext.Activity.Name, SEARCH_INVOKE_NAME) + && (searchInvokeValue = GetInvokeValue(turnContext.Activity)) != null + && isMatch(searchInvokeValue.Dataset)); }; return routeSelector; }