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)