From 72dd7c32d041a2b62a4c47919186e8b1fd3b1419 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 18:34:23 +0000 Subject: [PATCH 1/3] Initial plan From 5f92897fa30491505b183889e5b0f024c93205ca Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 18:45:20 +0000 Subject: [PATCH 2/3] Add unit tests for RequestExternalInputExecutor Co-authored-by: crickman <66376200+crickman@users.noreply.github.com> --- .../RequestExternalInputExecutorTest.cs | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/ObjectModel/RequestExternalInputExecutorTest.cs diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/ObjectModel/RequestExternalInputExecutorTest.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/ObjectModel/RequestExternalInputExecutorTest.cs new file mode 100644 index 0000000000..0d7c5ada8c --- /dev/null +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/ObjectModel/RequestExternalInputExecutorTest.cs @@ -0,0 +1,116 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Agents.AI.Workflows.Declarative.Events; +using Microsoft.Agents.AI.Workflows.Declarative.Interpreter; +using Microsoft.Agents.AI.Workflows.Declarative.ObjectModel; +using Microsoft.Agents.ObjectModel; +using Moq; +using Xunit.Abstractions; + +namespace Microsoft.Agents.AI.Workflows.Declarative.UnitTests.ObjectModel; + +/// +/// Tests for . +/// +public sealed class RequestExternalInputExecutorTest(ITestOutputHelper output) : WorkflowActionExecutorTest(output) +{ + [Fact] + public void RequestExternalInputNamingConvention() + { + // Arrange + string testId = this.CreateActionId().Value; + + // Act + string inputStep = RequestExternalInputExecutor.Steps.Input(testId); + string captureStep = RequestExternalInputExecutor.Steps.Capture(testId); + + // Assert + Assert.Equal($"{testId}_{nameof(RequestExternalInputExecutor.Steps.Input)}", inputStep); + Assert.Equal($"{testId}_{nameof(RequestExternalInputExecutor.Steps.Capture)}", captureStep); + } + + [Fact] + public async Task RequestExternalInputWithoutVariableAsync() + { + // Arrange & Act & Assert + await this.ExecuteTestAsync( + displayName: nameof(RequestExternalInputWithoutVariableAsync), + variablePath: null); + } + + [Fact] + public async Task RequestExternalInputWithVariableAsync() + { + // Arrange & Act & Assert + await this.ExecuteTestAsync( + displayName: nameof(RequestExternalInputWithVariableAsync), + variablePath: "InputVariable"); + } + + [Fact] + public async Task ExecuteIsNotDiscreteActionAsync() + { + // Arrange + RequestExternalInput model = this.CreateModel( + nameof(ExecuteIsNotDiscreteActionAsync), + null); + Mock mockProvider = new(MockBehavior.Strict); + RequestExternalInputExecutor action = new(model, mockProvider.Object, this.State); + + // Act + WorkflowEvent[] events = await this.ExecuteAsync(action, isDiscrete: false); + + // Assert + VerifyModel(model, action); + VerifyInvocationEvent(events); + + // Verify IsDiscreteAction is false + Assert.Equal( + false, + action.GetType().BaseType? + .GetProperty("IsDiscreteAction", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)? + .GetValue(action)); + + // Verify EmitResultEvent is false + Assert.Equal( + false, + action.GetType().BaseType? + .GetProperty("EmitResultEvent", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)? + .GetValue(action)); + } + + private async Task ExecuteTestAsync( + string displayName, + string? variablePath) + { + // Arrange + RequestExternalInput model = this.CreateModel(displayName, variablePath); + Mock mockProvider = new(MockBehavior.Strict); + RequestExternalInputExecutor action = new(model, mockProvider.Object, this.State); + + // Act + WorkflowEvent[] events = await this.ExecuteAsync(action, isDiscrete: false); + + // Assert + VerifyModel(model, action); + VerifyInvocationEvent(events); + } + + private RequestExternalInput CreateModel(string displayName, string? variablePath) + { + RequestExternalInput.Builder actionBuilder = new() + { + Id = this.CreateActionId(), + DisplayName = this.FormatDisplayName(displayName), + }; + + if (variablePath != null) + { + actionBuilder.Variable = PropertyPath.Create(FormatVariablePath(variablePath)); + } + + return AssignParent(actionBuilder); + } +} From 11168a80efbd29b15a541a0e3d190e7d419d76f5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 01:05:03 +0000 Subject: [PATCH 3/3] Add CaptureResponseAsync tests - achieve 100% coverage Co-authored-by: crickman <66376200+crickman@users.noreply.github.com> --- .../RequestExternalInputExecutorTest.cs | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/ObjectModel/RequestExternalInputExecutorTest.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/ObjectModel/RequestExternalInputExecutorTest.cs index 0d7c5ada8c..dbd10b93fb 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/ObjectModel/RequestExternalInputExecutorTest.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/ObjectModel/RequestExternalInputExecutorTest.cs @@ -1,11 +1,14 @@ // Copyright (c) Microsoft. All rights reserved. using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.AI.Workflows.Declarative.Events; using Microsoft.Agents.AI.Workflows.Declarative.Interpreter; using Microsoft.Agents.AI.Workflows.Declarative.ObjectModel; +using Microsoft.Agents.AI.Workflows.Declarative.PowerFx; using Microsoft.Agents.ObjectModel; +using Microsoft.Extensions.AI; using Moq; using Xunit.Abstractions; @@ -81,6 +84,110 @@ public async Task ExecuteIsNotDiscreteActionAsync() .GetValue(action)); } + [Fact] + public async Task CaptureResponseWithoutConversationAsync() + { + // Arrange + RequestExternalInput model = this.CreateModel( + nameof(CaptureResponseWithoutConversationAsync), + "TestVariable"); + Mock mockProvider = new(MockBehavior.Strict); + RequestExternalInputExecutor action = new(model, mockProvider.Object, this.State); + + ChatMessage testMessage = new(ChatRole.User, "Test input"); + ExternalInputResponse response = new(testMessage); + + // Create DeclarativeWorkflowContext with mock base context + Mock mockBaseContext = new(MockBehavior.Loose); + DeclarativeWorkflowContext context = new(mockBaseContext.Object, this.State); + + // Act + await action.CaptureResponseAsync(context, response, CancellationToken.None); + + // Assert + // Verify variable was set (should not be blank) + Assert.IsNotType(this.State.Get("TestVariable")); + } + + [Fact] + public async Task CaptureResponseWithConversationAsync() + { + // Arrange + RequestExternalInput model = this.CreateModel( + nameof(CaptureResponseWithConversationAsync), + "TestVariable"); + const string conversationId = "test-conversation-123"; + + ChatMessage testMessage = new(ChatRole.User, "Test input"); + ExternalInputResponse response = new(testMessage); + + Mock mockProvider = new(MockBehavior.Strict); + mockProvider + .Setup(p => p.CreateMessageAsync(conversationId, testMessage, It.IsAny())) + .ReturnsAsync(testMessage); + + RequestExternalInputExecutor action = new(model, mockProvider.Object, this.State); + + // Set up conversation ID in state so GetWorkflowConversation returns it + this.State.Set(SystemScope.Names.ConversationId, Microsoft.PowerFx.Types.FormulaValue.New(conversationId), VariableScopeNames.System); + + // Create DeclarativeWorkflowContext with mock base context + Mock mockBaseContext = new(MockBehavior.Loose); + DeclarativeWorkflowContext context = new(mockBaseContext.Object, this.State); + + // Act + await action.CaptureResponseAsync(context, response, CancellationToken.None); + + // Assert + mockProvider.Verify( + p => p.CreateMessageAsync(conversationId, testMessage, It.IsAny()), + Times.Once); + Assert.IsNotType(this.State.Get("TestVariable")); + } + + [Fact] + public async Task CaptureResponseWithMultipleMessagesAsync() + { + // Arrange + RequestExternalInput model = this.CreateModel( + nameof(CaptureResponseWithMultipleMessagesAsync), + null); + const string conversationId = "test-conversation-456"; + + ChatMessage[] messages = + [ + new ChatMessage(ChatRole.User, "First message"), + new ChatMessage(ChatRole.User, "Second message"), + new ChatMessage(ChatRole.User, "Third message") + ]; + ExternalInputResponse response = new(messages); + + Mock mockProvider = new(MockBehavior.Strict); + foreach (ChatMessage message in messages) + { + mockProvider + .Setup(p => p.CreateMessageAsync(conversationId, message, It.IsAny())) + .ReturnsAsync(message); + } + + RequestExternalInputExecutor action = new(model, mockProvider.Object, this.State); + + // Set up conversation ID in state + this.State.Set(SystemScope.Names.ConversationId, Microsoft.PowerFx.Types.FormulaValue.New(conversationId), VariableScopeNames.System); + + // Create DeclarativeWorkflowContext with mock base context + Mock mockBaseContext = new(MockBehavior.Loose); + DeclarativeWorkflowContext context = new(mockBaseContext.Object, this.State); + + // Act + await action.CaptureResponseAsync(context, response, CancellationToken.None); + + // Assert + mockProvider.Verify( + p => p.CreateMessageAsync(conversationId, It.IsAny(), It.IsAny()), + Times.Exactly(3)); + } + private async Task ExecuteTestAsync( string displayName, string? variablePath)