From 88433586fa38cd0e1dd24d177539411a22337674 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 23:58:11 +0000 Subject: [PATCH 1/4] Initial plan From 7a2f9d343bd099dee5bac7b3d9139c0bdb161aa6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Feb 2026 00:06:31 +0000 Subject: [PATCH 2/4] Add CopyConversationMessagesExecutorTest with 7 comprehensive test cases Co-authored-by: crickman <66376200+crickman@users.noreply.github.com> --- .../CopyConversationMessagesExecutorTest.cs | 203 ++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/ObjectModel/CopyConversationMessagesExecutorTest.cs diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/ObjectModel/CopyConversationMessagesExecutorTest.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/ObjectModel/CopyConversationMessagesExecutorTest.cs new file mode 100644 index 0000000000..8bb005b1d0 --- /dev/null +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/ObjectModel/CopyConversationMessagesExecutorTest.cs @@ -0,0 +1,203 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Agents.AI.Workflows.Declarative.Extensions; +using Microsoft.Agents.AI.Workflows.Declarative.ObjectModel; +using Microsoft.Agents.AI.Workflows.Declarative.PowerFx; +using Microsoft.Agents.ObjectModel; +using Microsoft.Extensions.AI; +using Microsoft.PowerFx.Types; +using Xunit.Abstractions; + +namespace Microsoft.Agents.AI.Workflows.Declarative.UnitTests.ObjectModel; + +/// +/// Tests for . +/// +public sealed class CopyConversationMessagesExecutorTest(ITestOutputHelper output) : WorkflowActionExecutorTest(output) +{ + [Fact] + public async Task CopyMessagesWithSingleStringMessageAsync() + { + // Arrange, Act, Assert + await this.ExecuteTestAsync( + displayName: nameof(CopyMessagesWithSingleStringMessageAsync), + conversationId: "TestConversationId", + messages: ValueExpression.Literal(StringDataValue.Create("Hello, how can I help you?")), + expectedMessageCount: 1); + } + + [Fact] + public async Task CopyMessagesWithSingleRecordMessageAsync() + { + // Arrange + ChatMessage testMessage = new ChatMessage(ChatRole.User, "Test message content"); + DataValue messageDataValue = testMessage.ToRecord().ToDataValue(); + Assert.IsType(messageDataValue); + RecordDataValue messageRecord = (RecordDataValue)messageDataValue; + + // Act, Assert + await this.ExecuteTestAsync( + displayName: nameof(CopyMessagesWithSingleRecordMessageAsync), + conversationId: "TestConversationId", + messages: ValueExpression.Literal(messageRecord), + expectedMessageCount: 1); + } + + [Fact] + public async Task CopyMessagesWithMultipleMessagesAsync() + { + // Arrange + List testMessages = + [ + new ChatMessage(ChatRole.User, "First message"), + new ChatMessage(ChatRole.Assistant, "Second message"), + new ChatMessage(ChatRole.User, "Third message") + ]; + DataValue messagesDataValue = testMessages.ToTable().ToDataValue(); + Assert.IsType(messagesDataValue); + TableDataValue messagesTable = (TableDataValue)messagesDataValue; + + // Act, Assert + await this.ExecuteTestAsync( + displayName: nameof(CopyMessagesWithMultipleMessagesAsync), + conversationId: "TestConversationId", + messages: ValueExpression.Literal(messagesTable), + expectedMessageCount: 3); + } + + [Fact] + public async Task CopyMessagesWithVariableExpressionAsync() + { + // Arrange + List testMessages = + [ + new ChatMessage(ChatRole.User, "Message from variable") + ]; + TableValue messagesTable = testMessages.ToTable(); + this.State.Set("SourceMessages", messagesTable); + + // Act, Assert + await this.ExecuteTestAsync( + displayName: nameof(CopyMessagesWithVariableExpressionAsync), + conversationId: "TestConversationId", + messages: ValueExpression.Variable(PropertyPath.TopicVariable("SourceMessages")), + expectedMessageCount: 1); + } + + [Fact] + public async Task CopyMessagesToWorkflowConversationAsync() + { + // Arrange + this.State.Set(SystemScope.Names.ConversationId, FormulaValue.New("WorkflowConversationId"), VariableScopeNames.System); + + List testMessages = + [ + new ChatMessage(ChatRole.User, "Message to workflow conversation") + ]; + DataValue messagesDataValue = testMessages.ToTable().ToDataValue(); + Assert.IsType(messagesDataValue); + TableDataValue messagesTable = (TableDataValue)messagesDataValue; + + // Act, Assert + await this.ExecuteTestAsync( + displayName: nameof(CopyMessagesToWorkflowConversationAsync), + conversationId: "WorkflowConversationId", + messages: ValueExpression.Literal(messagesTable), + expectedMessageCount: 1, + expectWorkflowEvent: true); + } + + [Fact] + public async Task CopyMessagesToNonWorkflowConversationAsync() + { + // Arrange + this.State.Set(SystemScope.Names.ConversationId, FormulaValue.New("WorkflowConversationId"), VariableScopeNames.System); + + List testMessages = + [ + new ChatMessage(ChatRole.User, "Message to non-workflow conversation") + ]; + DataValue messagesDataValue = testMessages.ToTable().ToDataValue(); + Assert.IsType(messagesDataValue); + TableDataValue messagesTable = (TableDataValue)messagesDataValue; + + // Act, Assert + await this.ExecuteTestAsync( + displayName: nameof(CopyMessagesToNonWorkflowConversationAsync), + conversationId: "DifferentConversationId", + messages: ValueExpression.Literal(messagesTable), + expectedMessageCount: 1, + expectWorkflowEvent: false); + } + + [Fact] + public async Task CopyMessagesWithBlankDataValueAsync() + { + // Arrange, Act, Assert + await this.ExecuteTestAsync( + displayName: nameof(CopyMessagesWithBlankDataValueAsync), + conversationId: "TestConversationId", + messages: ValueExpression.Literal(DataValue.Blank()), + expectedMessageCount: 0); + } + + private async Task ExecuteTestAsync( + string displayName, + string conversationId, + ValueExpression messages, + int expectedMessageCount, + bool expectWorkflowEvent = false) + { + // Arrange + MockAgentProvider mockAgentProvider = new(); + mockAgentProvider.TestMessages.Clear(); + + CopyConversationMessages model = this.CreateModel( + this.FormatDisplayName(displayName), + conversationId, + messages); + + CopyConversationMessagesExecutor action = new(model, mockAgentProvider.Object, this.State); + + // Act + WorkflowEvent[] events = await this.ExecuteAsync(action); + + // Assert + Assert.Equal(expectedMessageCount, mockAgentProvider.TestMessages.Count); + VerifyModel(model, action); + + if (expectWorkflowEvent && expectedMessageCount > 0) + { + AgentResponseEvent[] responseEvents = events.OfType().ToArray(); + Assert.NotEmpty(responseEvents); + AgentResponseEvent responseEvent = responseEvents.First(); + Assert.Equal(action.Id, responseEvent.ExecutorId); + Assert.NotNull(responseEvent.Response); + Assert.Equal(expectedMessageCount, responseEvent.Response.Messages.Count); + } + else if (!expectWorkflowEvent) + { + AgentResponseEvent[] responseEvents = events.OfType().ToArray(); + Assert.Empty(responseEvents); + } + } + + private CopyConversationMessages CreateModel( + string displayName, + string conversationId, + ValueExpression messages) + { + CopyConversationMessages.Builder actionBuilder = new() + { + Id = this.CreateActionId(), + DisplayName = this.FormatDisplayName(displayName), + ConversationId = StringExpression.Literal(conversationId), + Messages = messages + }; + + return AssignParent(actionBuilder); + } +} From 214041da70fa1006db9e43e5ca2cb43de40bdf24 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Feb 2026 00:09:15 +0000 Subject: [PATCH 3/4] Simplify ExecuteTestAsync to avoid duplicate event filtering logic Co-authored-by: crickman <66376200+crickman@users.noreply.github.com> --- .../ObjectModel/CopyConversationMessagesExecutorTest.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/ObjectModel/CopyConversationMessagesExecutorTest.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/ObjectModel/CopyConversationMessagesExecutorTest.cs index 8bb005b1d0..8602cca51f 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/ObjectModel/CopyConversationMessagesExecutorTest.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/ObjectModel/CopyConversationMessagesExecutorTest.cs @@ -169,18 +169,17 @@ private async Task ExecuteTestAsync( Assert.Equal(expectedMessageCount, mockAgentProvider.TestMessages.Count); VerifyModel(model, action); + AgentResponseEvent[] responseEvents = events.OfType().ToArray(); if (expectWorkflowEvent && expectedMessageCount > 0) { - AgentResponseEvent[] responseEvents = events.OfType().ToArray(); Assert.NotEmpty(responseEvents); AgentResponseEvent responseEvent = responseEvents.First(); Assert.Equal(action.Id, responseEvent.ExecutorId); Assert.NotNull(responseEvent.Response); Assert.Equal(expectedMessageCount, responseEvent.Response.Messages.Count); } - else if (!expectWorkflowEvent) + else { - AgentResponseEvent[] responseEvents = events.OfType().ToArray(); Assert.Empty(responseEvents); } } From dc79f28f153efe5cdcdc9c59e1fd37a2c19cf1d8 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Wed, 11 Feb 2026 08:31:03 -0800 Subject: [PATCH 4/4] Refine --- .../ObjectModel/CopyConversationMessagesExecutor.cs | 11 ++++------- .../CopyConversationMessagesExecutorTest.cs | 4 ++-- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/ObjectModel/CopyConversationMessagesExecutor.cs b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/ObjectModel/CopyConversationMessagesExecutor.cs index 6a51ce5805..bf5be5310f 100644 --- a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/ObjectModel/CopyConversationMessagesExecutor.cs +++ b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/ObjectModel/CopyConversationMessagesExecutor.cs @@ -42,14 +42,11 @@ internal sealed class CopyConversationMessagesExecutor(CopyConversationMessages private IEnumerable? GetInputMessages() { - DataValue? messages = null; + Throw.IfNull(this.Model.Messages, $"{nameof(this.Model)}.{nameof(this.Model.Messages)}"); - if (this.Model.Messages is not null) - { - EvaluationResult expressionResult = this.Evaluator.GetValue(this.Model.Messages); - messages = expressionResult.Value; - } + EvaluationResult expressionResult = this.Evaluator.GetValue(this.Model.Messages); + DataValue messages = expressionResult.Value; - return messages?.ToChatMessages(); + return messages.ToChatMessages(); } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/ObjectModel/CopyConversationMessagesExecutorTest.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/ObjectModel/CopyConversationMessagesExecutorTest.cs index 8602cca51f..cb818fec15 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/ObjectModel/CopyConversationMessagesExecutorTest.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/ObjectModel/CopyConversationMessagesExecutorTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft. All rights reserved. +// Copyright (c) Microsoft. All rights reserved. using System.Collections.Generic; using System.Linq; @@ -33,7 +33,7 @@ await this.ExecuteTestAsync( public async Task CopyMessagesWithSingleRecordMessageAsync() { // Arrange - ChatMessage testMessage = new ChatMessage(ChatRole.User, "Test message content"); + ChatMessage testMessage = new(ChatRole.User, "Test message content"); DataValue messageDataValue = testMessage.ToRecord().ToDataValue(); Assert.IsType(messageDataValue); RecordDataValue messageRecord = (RecordDataValue)messageDataValue;