.NET Workflow - Integrated updated CPS Object Model (#681)

* Checkpoint

* Update workflows/DeepResearch.yaml

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Comment

* Fix comment

* Update package version

* Fix nuget haxx

* Checkpoint

* Code complete

* Testing

* Message content workaround

* Add sequential flow

* Checkpoint

* Integration test project

* Checkpoint

* Checkpoint cleanup

* Complete

* Update package

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Chris
2025-09-15 20:41:07 +00:00
committed by GitHub
co-authored by Copilot
parent db58a10a37
commit 74879489a4
79 changed files with 2829 additions and 684 deletions
@@ -0,0 +1,5 @@
type: foundry_agent
name: BasicAgent
description: Basic agent for integration tests
model:
id: ${AzureAI:ModelDeployment}
@@ -0,0 +1,114 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Azure.Identity;
using Microsoft.Agents.Workflows.Declarative.IntegrationTests.Framework;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Configuration;
using Shared.IntegrationTests;
using Xunit.Abstractions;
namespace Microsoft.Agents.Workflows.Declarative.IntegrationTests;
/// <summary>
/// Tests execution of workflow created by <see cref="DeclarativeWorkflowBuilder"/>.
/// </summary>
[Collection("Global")]
public sealed class DeclarativeWorkflowTest(ITestOutputHelper output, AgentFixture agentFixture) : WorkflowTest(output), IClassFixture<AgentFixture>
{
[Theory]
[InlineData("SendActivity.yaml", "SendActivity.json")]
[InlineData("InvokeAgent.yaml", "InvokeAgent.json")]
public Task Validate(string workflowFileName, string testcaseFileName) =>
this.RunWorkflow(workflowFileName, testcaseFileName);
private Task RunWorkflow(string workflowFileName, string testcaseFileName)
{
this.Output.WriteLine($"WORKFLOW: {workflowFileName}");
this.Output.WriteLine($"TESTCASE: {testcaseFileName}");
Testcase testcase = ReadTestcase(testcaseFileName);
IConfiguration configuration = InitializeConfig();
string workflowPath = Path.Combine("Workflows", workflowFileName);
this.Output.WriteLine($" {testcase.Description}");
return
testcase.Setup.Input.Type switch
{
nameof(ChatMessage) => this.RunWorkflow<ChatMessage>(testcase, workflowPath, configuration),
nameof(String) => this.RunWorkflow<string>(testcase, workflowPath, configuration),
_ => throw new NotSupportedException($"Input type '{testcase.Setup.Input.Type}' is not supported."),
};
}
private async Task RunWorkflow<TInput>(
Testcase testcase,
string workflowPath,
IConfiguration configuration) where TInput : notnull
{
this.Output.WriteLine($"INPUT: {testcase.Setup.Input.Value}");
AzureAIConfiguration? foundryConfig = configuration.GetSection("AzureAI").Get<AzureAIConfiguration>();
Assert.NotNull(foundryConfig);
IDictionary<string, string?> agentMap = await agentFixture.GetAgentsAsync(foundryConfig);
IConfiguration workflowConfig =
new ConfigurationBuilder()
.AddInMemoryCollection(agentMap)
.Build();
DeclarativeWorkflowOptions workflowOptions =
new(new AzureAgentProvider(foundryConfig.Endpoint, new AzureCliCredential()))
{
Configuration = workflowConfig,
LoggerFactory = this.Output
};
Workflow<TInput> workflow = DeclarativeWorkflowBuilder.Build<TInput>(workflowPath, workflowOptions);
WorkflowEvents workflowEvents = await WorkflowHarness.RunAsync(workflow, (TInput)GetInput<TInput>(testcase));
foreach (DeclarativeActionInvokeEvent actionInvokeEvent in workflowEvents.ActionInvokeEvents)
{
this.Output.WriteLine($"ACTION: {actionInvokeEvent.ActionId} [{actionInvokeEvent.ActionType}]");
}
Assert.Equal(testcase.Validation.ActionCount, workflowEvents.ActionInvokeEvents.Count);
Assert.Equal(testcase.Validation.ActionCount, workflowEvents.ActionCompleteEvents.Count);
}
private static object GetInput<TInput>(Testcase testcase) where TInput : notnull =>
testcase.Setup.Input.Type switch
{
nameof(ChatMessage) => new ChatMessage(ChatRole.User, testcase.Setup.Input.Value),
nameof(String) => testcase.Setup.Input.Value,
_ => throw new NotSupportedException($"Input type '{testcase.Setup.Input.Type}' is not supported."),
};
private static Testcase ReadTestcase(string testcaseFileName)
{
using Stream testcaseStream = File.Open(Path.Combine("Testcases", testcaseFileName), FileMode.Open);
Testcase? testcase = JsonSerializer.Deserialize<Testcase>(testcaseStream, s_jsonSerializerOptions);
Assert.NotNull(testcase);
return testcase;
}
private static IConfigurationRoot InitializeConfig() =>
new ConfigurationBuilder()
.AddUserSecrets(Assembly.GetExecutingAssembly())
.AddEnvironmentVariables()
.Build();
private static readonly JsonSerializerOptions s_jsonSerializerOptions = new()
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
WriteIndented = true,
};
}
@@ -0,0 +1,50 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Azure.AI.Agents.Persistent;
using Azure.Identity;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.AzureAI;
using Shared.IntegrationTests;
namespace Microsoft.Agents.Workflows.Declarative.IntegrationTests.Framework;
#pragma warning disable SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
internal static class AgentFactory
{
public static async Task<ImmutableDictionary<string, string?>> CreateAsync(string agentsDirectory, AzureAIConfiguration config, CancellationToken cancellationToken)
{
PersistentAgentsClient clientAgents = new(config.Endpoint, new AzureCliCredential());
IKernelBuilder kernelBuilder = Kernel.CreateBuilder();
kernelBuilder.Services.AddSingleton(clientAgents);
Kernel kernel = kernelBuilder.Build();
AzureAIAgentFactory factory = new();
Dictionary<string, string?> agentMap = [];
foreach (string file in Directory.GetFiles(agentsDirectory, "*.yaml"))
{
Debug.WriteLine($"TEST AGENT: Creating - {file}");
string agentText = File.ReadAllText(file);
Agent? agent = await factory.CreateAgentFromYamlAsync(agentText, new AgentCreationOptions() { Kernel = kernel }, configuration: null, cancellationToken);
Assert.NotNull(agent?.Name);
Debug.WriteLine($"TEST AGENT: {agent.Name} => {agent.Id}");
agentMap[agent.Name] = agent.Id;
}
return agentMap.ToImmutableDictionary();
}
}
@@ -0,0 +1,26 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Shared.IntegrationTests;
namespace Microsoft.Agents.Workflows.Declarative.IntegrationTests.Framework;
public sealed class AgentFixture : IDisposable
{
private static ImmutableDictionary<string, string?>? s_agentMap;
internal async Task<ImmutableDictionary<string, string?>> GetAgentsAsync(AzureAIConfiguration config, CancellationToken cancellationToken = default)
{
s_agentMap ??= await AgentFactory.CreateAsync("Agents", config, cancellationToken);
return s_agentMap;
}
public void Dispose()
{
GC.SuppressFinalize(this);
}
}
@@ -0,0 +1,73 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Microsoft.Extensions.Logging;
using Xunit.Abstractions;
namespace Microsoft.Agents.Workflows.Declarative.IntegrationTests.Framework;
public sealed class TestOutputAdapter(ITestOutputHelper output) : TextWriter, ILogger, ILoggerFactory
{
private readonly Stack<string> _scopes = [];
public override Encoding Encoding { get; } = Encoding.UTF8;
public void AddProvider(ILoggerProvider provider) => throw new NotSupportedException();
public ILogger CreateLogger(string categoryName) => this;
public bool IsEnabled(LogLevel logLevel) => true;
public override void WriteLine(object? value = null) => this.SafeWrite($"{value}");
public override void WriteLine(string? format, params object?[] arg) => this.SafeWrite(string.Format(format ?? string.Empty, arg));
public override void WriteLine(string? value) => this.SafeWrite(value ?? string.Empty);
public override void Write(object? value = null) => this.SafeWrite($"{value}");
public override void Write(char[]? buffer) => this.SafeWrite(new string(buffer));
public IDisposable BeginScope<TState>(TState state) where TState : notnull
{
this._scopes.Push($"{state}");
return new LoggerScope(() => this._scopes.Pop());
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
{
string message = formatter(state, exception);
string scope = this._scopes.Count > 0 ? $"[{this._scopes.Peek()}] " : string.Empty;
output.WriteLine($"{scope}{message}");
}
private void SafeWrite(string value)
{
try
{
output.WriteLine(value ?? string.Empty);
}
catch (InvalidOperationException exception) when (exception.Message == "There is no currently active test.")
{
// This exception is thrown when the test output is accessed outside of a test context.
// We can ignore it since we are not in a test context.
}
}
private sealed class LoggerScope(Action action) : IDisposable
{
private bool _disposed;
public void Dispose()
{
if (!this._disposed)
{
action.Invoke();
this._disposed = true;
}
}
}
}
@@ -0,0 +1,59 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Text.Json.Serialization;
namespace Microsoft.Agents.Workflows.Declarative.IntegrationTests.Framework;
public sealed class Testcase
{
[JsonConstructor]
public Testcase(
string description,
TestcaseSetup setup,
TestcaseValidation validation)
{
this.Description = description;
this.Setup = setup;
this.Validation = validation;
}
public string Description { get; }
public TestcaseSetup Setup { get; }
public TestcaseValidation Validation { get; }
}
public sealed class TestcaseSetup
{
[JsonConstructor]
public TestcaseSetup(TestcaseInput input)
{
this.Input = input;
}
public TestcaseInput Input { get; }
}
public sealed class TestcaseInput
{
[JsonConstructor]
public TestcaseInput(string type, string value)
{
this.Type = type;
this.Value = value;
}
public string Type { get; }
public string Value { get; }
}
public sealed class TestcaseValidation
{
[JsonConstructor]
public TestcaseValidation(int actionCount)
{
this.ActionCount = actionCount;
}
public int ActionCount { get; }
}
@@ -0,0 +1,23 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Immutable;
using System.Linq;
namespace Microsoft.Agents.Workflows.Declarative.IntegrationTests.Framework;
internal sealed class WorkflowEvents
{
public WorkflowEvents(ImmutableList<WorkflowEvent> workflowEvents)
{
this.Events = workflowEvents;
this.EventCounts = workflowEvents.GroupBy(e => e.GetType()).ToImmutableDictionary(e => e.Key, e => e.Count());
this.ActionInvokeEvents = workflowEvents.OfType<DeclarativeActionInvokeEvent>().ToImmutableList();
this.ActionCompleteEvents = workflowEvents.OfType<DeclarativeActionCompleteEvent>().ToImmutableList();
}
public ImmutableList<WorkflowEvent> Events { get; }
public IImmutableDictionary<Type, int> EventCounts { get; }
public ImmutableList<DeclarativeActionInvokeEvent> ActionInvokeEvents { get; }
public ImmutableList<DeclarativeActionCompleteEvent> ActionCompleteEvents { get; private set; }
}
@@ -0,0 +1,17 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.Agents.Workflows.Declarative.IntegrationTests.Framework;
internal static class WorkflowHarness
{
public static async Task<WorkflowEvents> RunAsync<TInput>(Workflow<TInput> workflow, TInput input) where TInput : notnull
{
StreamingRun run = await InProcessExecution.StreamAsync(workflow, input);
ImmutableList<WorkflowEvent> workflowEvents = run.WatchStreamAsync().ToEnumerable().ToImmutableList();
return new WorkflowEvents(workflowEvents);
}
}
@@ -0,0 +1,37 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using Microsoft.Bot.ObjectModel;
using Xunit.Abstractions;
namespace Microsoft.Agents.Workflows.Declarative.IntegrationTests.Framework;
/// <summary>
/// Base class for workflow tests.
/// </summary>
public abstract class WorkflowTest : IDisposable
{
public TestOutputAdapter Output { get; }
protected WorkflowTest(ITestOutputHelper output)
{
this.Output = new TestOutputAdapter(output);
Console.SetOut(this.Output);
}
public void Dispose()
{
this.Dispose(isDisposing: true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool isDisposing)
{
if (isDisposing)
{
this.Output.Dispose();
}
}
internal static string FormatVariablePath(string variableName, string? scope = null) => $"{scope ?? VariableScopeNames.Topic}.{variableName}";
}
@@ -0,0 +1,37 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(ProjectsTargetFrameworks)</TargetFrameworks>
<NoWarn>$(NoWarn);IDE1006</NoWarn>
</PropertyGroup>
<PropertyGroup>
<InjectSharedIntegrationTestCode>true</InjectSharedIntegrationTestCode>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.Agents.Workflows.Declarative\Microsoft.Agents.Workflows.Declarative.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Azure.Identity" />
<PackageReference Include="FluentAssertions" />
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
<PackageReference Include="Microsoft.SemanticKernel.Agents.Abstractions" />
<PackageReference Include="Microsoft.SemanticKernel.Agents.AzureAI" />
<PackageReference Include="Microsoft.SemanticKernel.Agents.Yaml" />
</ItemGroup>
<ItemGroup>
<None Update="Agents\*.yaml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Testcases\*.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Workflows\*.yaml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
@@ -0,0 +1,12 @@
{
"description": "Produce a single response from an agent.",
"setup": {
"input": {
"type": "String",
"value": "Why is the sky blue?"
}
},
"validation": {
"action_count": 1
}
}
@@ -0,0 +1,12 @@
{
"description": "Send an activity message .",
"setup": {
"input": {
"type": "String",
"value": "Why is the sky blue?"
}
},
"validation": {
"action_count": 3
}
}
@@ -0,0 +1,49 @@
#
# This workflow demonstrates a conversation and message manipulation.
#
# Any Foundry Agent may be used to provide the response.
# See: ./setup/QuestionAgent.yaml
#
kind: AdaptiveDialog
beginDialog:
kind: OnActivity
id: workflow_test
actions:
- kind: CreateConversation
id: conversation_create1
conversationId: Topic.FirstConversationId
- kind: CreateConversation
id: conversation_create2
conversationId: Topic.SecondConversationId
- kind: SendActivity
id: sendActivity_conversation
activity: |-
Conversation 1: {Topic.FirstConversationId}
Conversation 2: {Topic.SecondConversationId}
- kind: AddConversationMessage
id: add_message
message: Topic.MyMessage1
role: User
conversationId: =Topic.FirstConversationId
content:
- type: Text
value: {System.LastMessage.Text}
- kind: SendActivity
id: sendActivity_message
activity: |-
Messsage 1: {Topic.MyMessage1}
- kind: CopyConversationMessages
id: copy_messages
conversationId: =Topic.SecondConversationId
messages: =[Topic.MyMessage1]
- kind: SendActivity
id: sendActivity_copy
activity: Done!
@@ -0,0 +1,23 @@
#
# This workflow demonstrates a conversation and message manipulation.
#
# Any Foundry Agent may be used to provide the response.
# See: ./setup/QuestionAgent.yaml
#
kind: AdaptiveDialog
beginDialog:
kind: OnActivity
id: workflow_test
actions:
- kind: RetrieveConversationMessage
id: get_message
message: Topic.MyMessage
conversationId: thread_T8xIzNrNcPkUkoCEGzxg80Vt
messageId: msg_J4x6YZTDUUWNs60FOUAucldy
- kind: SendActivity
id: sendActivity_message
activity: |-
{Topic.MyMessage}
@@ -0,0 +1,22 @@
#
# This workflow demonstrates a conversation and message manipulation.
#
# Any Foundry Agent may be used to provide the response.
# See: ./setup/QuestionAgent.yaml
#
kind: AdaptiveDialog
beginDialog:
kind: OnActivity
id: workflow_test
actions:
- kind: RetrieveConversationMessages
id: get_message
messages: Topic.MyMessages
conversationId: thread_T8xIzNrNcPkUkoCEGzxg80Vt
- kind: SendActivity
id: sendActivity_message
activity: |-
{Topic.MyMessages}
@@ -0,0 +1,15 @@
kind: AdaptiveDialog
beginDialog:
kind: OnActivity
id: workflow_test
actions:
- kind: InvokeAzureAgent
id: invoke_agent
agent:
name: =Env.BasicAgent
input:
messages: =[UserMessage(System.LastMessageText)]
output:
messages: Topic.Answer
@@ -0,0 +1,30 @@
#
# This workflow provides the most basic example of providing a response that includes the user and environment input.
#
# No agent setup is required to run this workflow.
#
kind: AdaptiveDialog
beginDialog:
kind: OnActivity
id: workflow_test
actions:
# Capture input
- kind: SetVariable
id: setvar_userinput
variable: Topic.UserInput
value: =System.LastMessage.Text
# Capture environment variable
- kind: SetVariable
id: setvar_username
variable: Global.UserName
value: =Env.USERNAME
# Respond with input
- kind: SendActivity
id: sendActivity_demo
activity: |-
Hello {Global.UserName},
You said, "{Topic.UserInput}"
@@ -168,7 +168,6 @@ public sealed class DeclarativeWorkflowTest(ITestOutputHelper output) : Workflow
[InlineData(typeof(InvokeSkillAction.Builder))]
[InlineData(typeof(LogCustomTelemetryEvent.Builder))]
[InlineData(typeof(OAuthInput.Builder))]
[InlineData(typeof(Question.Builder))]
[InlineData(typeof(RecognizeIntent.Builder))]
[InlineData(typeof(RepeatDialog.Builder))]
[InlineData(typeof(ReplaceDialog.Builder))]
@@ -192,7 +191,7 @@ public sealed class DeclarativeWorkflowTest(ITestOutputHelper output) : Workflow
BeginDialog =
new OnActivity.Builder()
{
Id = "workflow",
Id = "anything",
Actions = [unsupportedAction]
}
};
@@ -226,7 +225,7 @@ public sealed class DeclarativeWorkflowTest(ITestOutputHelper output) : Workflow
private void AssertMessage(string message)
{
Assert.Contains(this.WorkflowEvents.OfType<AgentRunResponseEvent>(), e => string.Equals(e.Response.Messages[0].Text.Trim(), message, StringComparison.Ordinal));
Assert.Contains(this.WorkflowEvents.OfType<MessageActivityEvent>(), e => string.Equals(e.Message.Trim(), message, StringComparison.Ordinal));
}
private Task RunWorkflow(string workflowPath) => this.RunWorkflow<string>(workflowPath, string.Empty);
@@ -246,7 +245,7 @@ public sealed class DeclarativeWorkflowTest(ITestOutputHelper output) : Workflow
{
if (workflowEvent is ExecutorInvokedEvent invokeEvent)
{
DeclarativeExecutorResult? message = invokeEvent.Data as DeclarativeExecutorResult;
ExecutorResultMessage? message = invokeEvent.Data as ExecutorResultMessage;
this.Output.WriteLine($"EXEC: {invokeEvent.ExecutorId} << {message?.ExecutorId ?? "?"} [{message?.Result ?? "-"}]");
}
else if (workflowEvent is AgentRunResponseEvent messageEvent)
@@ -258,7 +257,7 @@ public sealed class DeclarativeWorkflowTest(ITestOutputHelper output) : Workflow
}
private sealed class RootExecutor() :
ReflectingExecutor<RootExecutor>(WorkflowActionVisitor.RootId("workflow")),
ReflectingExecutor<RootExecutor>(WorkflowActionVisitor.Steps.Root("anything")),
IMessageHandler<string>
{
public async ValueTask HandleAsync(string message, IWorkflowContext context)
@@ -19,7 +19,7 @@ public class FormulaValueExtensionsTests
BooleanDataValue typedValue = Assert.IsType<BooleanDataValue>(dataValue);
Assert.Equal(formulaValue.Value, typedValue.Value);
BooleanValue formulaCopy = Assert.IsType<BooleanValue>(dataValue.ToFormulaValue());
BooleanValue formulaCopy = Assert.IsType<BooleanValue>(dataValue.ToFormula());
Assert.Equal(typedValue.Value, formulaCopy.Value);
Assert.Equal(bool.TrueString, formulaValue.Format());
@@ -35,7 +35,7 @@ public class FormulaValueExtensionsTests
StringDataValue typedValue = Assert.IsType<StringDataValue>(dataValue);
Assert.Equal(formulaValue.Value, typedValue.Value);
StringValue formulaCopy = Assert.IsType<StringValue>(typedValue.ToFormulaValue());
StringValue formulaCopy = Assert.IsType<StringValue>(typedValue.ToFormula());
Assert.Equal(typedValue.Value, formulaCopy.Value);
Assert.Equal(formulaValue.Value, formulaValue.Format());
@@ -51,7 +51,7 @@ public class FormulaValueExtensionsTests
NumberDataValue typedValue = Assert.IsType<NumberDataValue>(dataValue);
Assert.Equal(formulaValue.Value, typedValue.Value);
DecimalValue formulaCopy = Assert.IsType<DecimalValue>(typedValue.ToFormulaValue());
DecimalValue formulaCopy = Assert.IsType<DecimalValue>(typedValue.ToFormula());
Assert.Equal(typedValue.Value, formulaCopy.Value);
Assert.Equal("45.3", formulaValue.Format());
@@ -67,7 +67,7 @@ public class FormulaValueExtensionsTests
FloatDataValue typedValue = Assert.IsType<FloatDataValue>(dataValue);
Assert.Equal(formulaValue.Value, typedValue.Value);
NumberValue formulaCopy = Assert.IsType<NumberValue>(typedValue.ToFormulaValue());
NumberValue formulaCopy = Assert.IsType<NumberValue>(typedValue.ToFormula());
Assert.Equal(typedValue.Value, formulaCopy.Value);
Assert.Equal("3.1415926535897", formulaValue.Format());
@@ -103,7 +103,7 @@ public class FormulaValueExtensionsTests
DateDataValue typedValue = Assert.IsType<DateDataValue>(dataValue);
Assert.Equal(formulaValue.GetConvertedValue(TimeZoneInfo.Utc), typedValue.Value);
DateValue formulaCopy = Assert.IsType<DateValue>(dataValue.ToFormulaValue());
DateValue formulaCopy = Assert.IsType<DateValue>(dataValue.ToFormula());
Assert.Equal(typedValue.Value, formulaCopy.GetConvertedValue(TimeZoneInfo.Utc));
Assert.Equal($"{timestamp}", formulaValue.Format());
@@ -120,7 +120,7 @@ public class FormulaValueExtensionsTests
DateTimeDataValue typedValue = Assert.IsType<DateTimeDataValue>(dataValue);
Assert.Equal(formulaValue.GetConvertedValue(TimeZoneInfo.Utc), typedValue.Value);
DateTimeValue formulaCopy = Assert.IsType<DateTimeValue>(typedValue.ToFormulaValue());
DateTimeValue formulaCopy = Assert.IsType<DateTimeValue>(typedValue.ToFormula());
Assert.Equal(typedValue.Value, formulaCopy.GetConvertedValue(TimeZoneInfo.Utc));
Assert.Equal($"{timestamp}", formulaValue.Format());
@@ -136,7 +136,7 @@ public class FormulaValueExtensionsTests
TimeDataValue typedValue = Assert.IsType<TimeDataValue>(dataValue);
Assert.Equal(formulaValue.Value, typedValue.Value);
TimeValue formulaCopy = Assert.IsType<TimeValue>(typedValue.ToFormulaValue());
TimeValue formulaCopy = Assert.IsType<TimeValue>(typedValue.ToFormula());
Assert.Equal(typedValue.Value, formulaCopy.Value);
Assert.Equal("10:35:00", formulaValue.Format());
@@ -158,7 +158,7 @@ public class FormulaValueExtensionsTests
Assert.Contains(property.Key, formulaValue.Fields.Select(field => field.Name));
}
RecordValue formulaCopy = Assert.IsType<RecordValue>(dataValue.ToFormulaValue(), exactMatch: false);
RecordValue formulaCopy = Assert.IsType<RecordValue>(dataValue.ToFormula(), exactMatch: false);
Assert.Equal(formulaCopy.Fields.Count(), dataValue.Properties.Count);
foreach (NamedValue field in formulaCopy.Fields)
{
@@ -174,6 +174,16 @@ public class FormulaValueExtensionsTests
}
""",
formulaValue.Format().Replace(Environment.NewLine, "\n"));
Dictionary<string, int> source =
new()
{
["FieldA"] = 1,
["FieldB"] = 2,
["FieldC"] = 3
};
FormulaValue formula = source.ToFormula();
Assert.IsType<RecordValue>(formula, exactMatch: false);
}
[Fact]
@@ -188,7 +198,7 @@ public class FormulaValueExtensionsTests
TableDataValue dataValue = formulaValue.ToTable();
Assert.Equal(formulaValue.Rows.Count(), dataValue.Values.Length);
TableValue formulaCopy = Assert.IsType<TableValue>(dataValue.ToFormulaValue(), exactMatch: false);
TableValue formulaCopy = Assert.IsType<TableValue>(dataValue.ToFormula(), exactMatch: false);
Assert.Equal(formulaCopy.Rows.Count(), dataValue.Values.Length);
Assert.Equal(
@@ -27,7 +27,7 @@ public sealed class SendActivityExecutorTest(ITestOutputHelper output) : Workflo
// Assert
this.VerifyModel(model, action);
Assert.Contains(events, e => e is AgentRunResponseEvent);
Assert.Contains(events, e => e is MessageActivityEvent);
}
private SendActivity CreateModel(string displayName, string activityMessage, string? summary = null)
@@ -14,7 +14,7 @@ using Xunit.Abstractions;
namespace Microsoft.Agents.Workflows.Declarative.UnitTests.ObjectModel;
/// <summary>
/// Base test class for <see cref="WorkflowActionExecutor"/> implementations.
/// Base test class for <see cref="DeclarativeActionExecutor"/> implementations.
/// </summary>
public abstract class WorkflowActionExecutorTest(ITestOutputHelper output) : WorkflowTest(output)
{
@@ -26,7 +26,7 @@ public abstract class WorkflowActionExecutorTest(ITestOutputHelper output) : Wor
protected string FormatDisplayName(string name) => $"{this.GetType().Name}_{name}";
internal async Task<WorkflowEvent[]> Execute(WorkflowActionExecutor executor)
internal async Task<WorkflowEvent[]> Execute(DeclarativeActionExecutor executor)
{
TestWorkflowExecutor workflowExecutor = new();
WorkflowBuilder workflowBuilder = new(workflowExecutor);
@@ -38,7 +38,7 @@ public abstract class WorkflowActionExecutorTest(ITestOutputHelper output) : Wor
return events;
}
internal void VerifyModel(DialogAction model, WorkflowActionExecutor action)
internal void VerifyModel(DialogAction model, DeclarativeActionExecutor action)
{
Assert.Equal(model.Id, action.Id);
Assert.Equal(model, action.Model);
@@ -80,7 +80,7 @@ public abstract class WorkflowActionExecutorTest(ITestOutputHelper output) : Wor
{
public async ValueTask HandleAsync(WorkflowScopes message, IWorkflowContext context)
{
await context.SendMessageAsync(new DeclarativeExecutorResult(this.Id)).ConfigureAwait(false);
await context.SendMessageAsync(new ExecutorResultMessage(this.Id)).ConfigureAwait(false);
}
}
}