.NET Workflows - Add unit tests for ForeachExecutor (Declarative Workflows) (#3835)

* Initial plan

* Add comprehensive unit tests for ForeachExecutor

Co-authored-by: crickman <66376200+crickman@users.noreply.github.com>

* Formatting

* Checkpoint

* Checkpoint

* Updated test capabilities for non-discrete

* Update dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/ObjectModel/ForeachExecutorTest.cs

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

* Update dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/ObjectModel/ForeachExecutorTest.cs

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

* Consistency

* Cleanup test

* Fixed

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: crickman <66376200+crickman@users.noreply.github.com>
Co-authored-by: Chris Rickman <crickman@microsoft.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Copilot
2026-02-12 16:29:54 +00:00
committed by GitHub
Unverified
parent be7b55f99b
commit 69dcfe31ee
12 changed files with 440 additions and 60 deletions
@@ -65,7 +65,7 @@ namespace Microsoft.Agents.AI.Workflows.Declarative.CodeGen
this._values = [evaluatedValue];
}
await this.ResetAsync(context, null, cancellationToken).ConfigureAwait(false);
await this.ResetAsync(context, cancellationToken).ConfigureAwait(false);
return default;
}
@@ -84,9 +84,19 @@ namespace Microsoft.Agents.AI.Workflows.Declarative.CodeGen
AssignVariable(this.Index, "this._index", tightFormat: true);
}
this.Write("\n\n this._index++;\n }\n }\n\n public async ValueTask ResetAsy" +
"nc(IWorkflowContext context, object? _, CancellationToken cancellationToken)\n " +
" {");
this.Write(@"
this._index++;
}
}
public async ValueTask CompleteAsync(IWorkflowContext context, object? _, CancellationToken cancellationToken)
{
await this.ResetAsync(context, cancellationToken).ConfigureAwait(false);
}
private async ValueTask ResetAsync(IWorkflowContext context, CancellationToken cancellationToken)
{");
AssignVariable(this.Value, "UnassignedValue.Instance", tightFormat: true);
@@ -36,7 +36,7 @@ internal sealed class <#= this.Name #>Executor(FormulaSession session) : ActionE
this._values = [evaluatedValue];
}
await this.ResetAsync(context, null, cancellationToken).ConfigureAwait(false);
await this.ResetAsync(context, cancellationToken).ConfigureAwait(false);
return default;
}
@@ -59,7 +59,12 @@ internal sealed class <#= this.Name #>Executor(FormulaSession session) : ActionE
}
}
public async ValueTask ResetAsync(IWorkflowContext context, object? _, CancellationToken cancellationToken)
public async ValueTask CompleteAsync(IWorkflowContext context, object? _, CancellationToken cancellationToken)
{
await this.ResetAsync(context, cancellationToken).ConfigureAwait(false);
}
private async ValueTask ResetAsync(IWorkflowContext context, CancellationToken cancellationToken)
{<#
AssignVariable(this.Value, "UnassignedValue.Instance", tightFormat: true);
@@ -201,7 +201,7 @@ internal sealed class WorkflowActionVisitor : DialogActionVisitor
{
// Transition to end of inner actions
string endActionsId = ForeachExecutor.Steps.End(action.Id);
this.ContinueWith(new DelegateActionExecutor(endActionsId, this._workflowState, action.ResetAsync), action.Id);
this.ContinueWith(new DelegateActionExecutor(endActionsId, this._workflowState, action.CompleteAsync), action.Id);
// Transition to select the next item
this._workflowModel.AddLink(endActionsId, loopId);
}
@@ -163,7 +163,7 @@ internal sealed class WorkflowTemplateVisitor : DialogActionVisitor
{
// Transition to end of inner actions
string endActionsId = ForeachExecutor.Steps.End(action.Id); // Loop continuation
this.ContinueWith(new EmptyTemplate(endActionsId, this._rootId, $"{action.Id.FormatName()}.{nameof(ForeachExecutor.ResetAsync)}"), action.Id);
this.ContinueWith(new EmptyTemplate(endActionsId, this._rootId, $"{action.Id.FormatName()}.{nameof(ForeachExecutor.CompleteAsync)}"), action.Id);
// Transition to select the next item
this._workflowModel.AddLink(endActionsId, loopId);
}
@@ -37,27 +37,21 @@ internal sealed class ForeachExecutor : DeclarativeActionExecutor<Foreach>
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken = default)
{
Throw.IfNull(this.Model.Items, $"{nameof(this.Model)}.{nameof(this.Model.Items)}");
this._index = 0;
if (this.Model.Items is null)
EvaluationResult<DataValue> expressionResult = this.Evaluator.GetValue(this.Model.Items);
if (expressionResult.Value is TableDataValue tableValue)
{
this._values = [];
this.HasValue = false;
this._values = [.. tableValue.Values.Select(value => value.Properties.Values.First().ToFormula())];
}
else
{
EvaluationResult<DataValue> expressionResult = this.Evaluator.GetValue(this.Model.Items);
if (expressionResult.Value is TableDataValue tableValue)
{
this._values = [.. tableValue.Values.Select(value => value.Properties.Values.First().ToFormula())];
}
else
{
this._values = [expressionResult.Value.ToFormula()];
}
this._values = [expressionResult.Value.ToFormula()];
}
await this.ResetAsync(context, null, cancellationToken).ConfigureAwait(false);
await this.ResetStateAsync(context, cancellationToken).ConfigureAwait(false);
return default;
}
@@ -79,19 +73,24 @@ internal sealed class ForeachExecutor : DeclarativeActionExecutor<Foreach>
}
}
public async ValueTask ResetAsync(IWorkflowContext context, object? _, CancellationToken cancellationToken)
public async ValueTask CompleteAsync(IWorkflowContext context, object? _, CancellationToken cancellationToken)
{
try
{
await context.QueueStateResetAsync(Throw.IfNull(this.Model.Value), cancellationToken).ConfigureAwait(false);
if (this.Model.Index is not null)
{
await context.QueueStateResetAsync(this.Model.Index, cancellationToken).ConfigureAwait(false);
}
await this.ResetStateAsync(context, cancellationToken).ConfigureAwait(false);
}
finally
{
await context.RaiseCompletionEventAsync(this.Model, cancellationToken).ConfigureAwait(false);
}
}
private async Task ResetStateAsync(IWorkflowContext context, CancellationToken cancellationToken)
{
await context.QueueStateResetAsync(Throw.IfNull(this.Model.Value), cancellationToken).ConfigureAwait(false);
if (this.Model.Index is not null)
{
await context.QueueStateResetAsync(this.Model.Index, cancellationToken).ConfigureAwait(false);
}
}
}
@@ -54,7 +54,7 @@ public class ForeachTemplateTest(ITestOutputHelper output) : WorkflowActionTempl
AssertGeneratedCode<ActionExecutor>(template.Id, workflowCode);
AssertAgentProvider(template.UseAgentProvider, workflowCode);
AssertGeneratedMethod(nameof(ForeachExecutor.TakeNextAsync), workflowCode);
AssertGeneratedMethod(nameof(ForeachExecutor.ResetAsync), workflowCode);
AssertGeneratedMethod(nameof(ForeachExecutor.CompleteAsync), workflowCode);
}
private Foreach CreateModel(
@@ -52,7 +52,7 @@ public sealed class DeclarativeWorkflowTest(ITestOutputHelper output) : Workflow
{
await this.RunWorkflowAsync("LoopBreak.yaml");
this.AssertExecutionCount(expectedCount: 6);
this.AssertExecuted("foreach_loop");
this.AssertExecuted("foreach_loop", isDiscrete: false);
this.AssertExecuted("break_loop_now");
this.AssertExecuted("end_all");
this.AssertNotExecuted("set_variable_inner");
@@ -64,7 +64,7 @@ public sealed class DeclarativeWorkflowTest(ITestOutputHelper output) : Workflow
{
await this.RunWorkflowAsync("LoopContinue.yaml");
this.AssertExecutionCount(expectedCount: 22);
this.AssertExecuted("foreach_loop");
this.AssertExecuted("foreach_loop", isDiscrete: false);
this.AssertExecuted("continue_loop_now");
this.AssertExecuted("end_all");
this.AssertNotExecuted("set_variable_inner");
@@ -103,7 +103,7 @@ public sealed class DeclarativeWorkflowTest(ITestOutputHelper output) : Workflow
this.AssertExecuted("conditionGroup_test");
if (input % 2 == 0)
{
this.AssertExecuted("conditionItem_even", isScope: true);
this.AssertExecuted("conditionItem_even", isAction: false);
this.AssertExecuted("sendActivity_even");
this.AssertNotExecuted("conditionItem_odd");
this.AssertNotExecuted("sendActivity_odd");
@@ -111,7 +111,7 @@ public sealed class DeclarativeWorkflowTest(ITestOutputHelper output) : Workflow
}
else
{
this.AssertExecuted("conditionItem_odd", isScope: true);
this.AssertExecuted("conditionItem_odd", isAction: false);
this.AssertExecuted("sendActivity_odd");
this.AssertNotExecuted("conditionItem_even");
this.AssertNotExecuted("sendActivity_even");
@@ -131,13 +131,13 @@ public sealed class DeclarativeWorkflowTest(ITestOutputHelper output) : Workflow
this.AssertExecuted("conditionGroup_test");
if (input % 2 == 0)
{
this.AssertExecuted("sendActivity_else", isScope: true);
this.AssertExecuted("sendActivity_else", isAction: false);
this.AssertNotExecuted("conditionItem_odd");
this.AssertNotExecuted("sendActivity_odd");
}
else
{
this.AssertExecuted("conditionItem_odd", isScope: true);
this.AssertExecuted("conditionItem_odd", isAction: false);
this.AssertExecuted("sendActivity_odd");
this.AssertNotExecuted("sendActivity_else");
}
@@ -152,7 +152,7 @@ public sealed class DeclarativeWorkflowTest(ITestOutputHelper output) : Workflow
await this.RunWorkflowAsync("ConditionFallThrough.yaml", input);
this.AssertExecutionCount(expectedActions);
this.AssertExecuted("setVariable_test");
this.AssertExecuted("conditionGroup_test", isScope: true);
this.AssertExecuted("conditionGroup_test", isAction: false);
if (input % 2 == 0)
{
this.AssertNotExecuted("conditionItem_odd");
@@ -160,7 +160,7 @@ public sealed class DeclarativeWorkflowTest(ITestOutputHelper output) : Workflow
}
else
{
this.AssertExecuted("conditionItem_odd", isScope: true);
this.AssertExecuted("conditionItem_odd", isAction: false);
this.AssertExecuted("sendActivity_odd");
this.AssertMessage("ODD");
}
@@ -307,14 +307,17 @@ public sealed class DeclarativeWorkflowTest(ITestOutputHelper output) : Workflow
Assert.DoesNotContain(this.WorkflowEvents.OfType<ExecutorCompletedEvent>(), e => e.ExecutorId == executorId);
}
private void AssertExecuted(string executorId, bool isScope = false)
private void AssertExecuted(string executorId, bool isAction = true, bool isDiscrete = true)
{
Assert.Contains(this.WorkflowEvents.OfType<ExecutorInvokedEvent>(), e => e.ExecutorId == executorId);
Assert.Contains(this.WorkflowEvents.OfType<ExecutorCompletedEvent>(), e => e.ExecutorId == executorId);
if (!isScope)
if (isAction)
{
Assert.Contains(this.WorkflowEvents.OfType<DeclarativeActionInvokedEvent>(), e => e.ActionId == executorId);
Assert.Contains(this.WorkflowEvents.OfType<DeclarativeActionCompletedEvent>(), e => e.ActionId == executorId);
if (isDiscrete)
{
Assert.Contains(this.WorkflowEvents.OfType<DeclarativeActionCompletedEvent>(), e => e.ActionId == executorId);
}
}
}
@@ -0,0 +1,320 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Agents.AI.Workflows.Declarative.ObjectModel;
using Microsoft.Agents.ObjectModel;
using Microsoft.PowerFx.Types;
using Xunit.Abstractions;
namespace Microsoft.Agents.AI.Workflows.Declarative.UnitTests.ObjectModel;
/// <summary>
/// Tests for <see cref="ForeachExecutor"/>.
/// </summary>
public sealed class ForeachExecutorTest(ITestOutputHelper output) : WorkflowActionExecutorTest(output)
{
[Fact]
public void ForeachThrowsWhenModelInvalid() =>
// Arrange, Act & Assert
Assert.Throws<DeclarativeModelException>(() => new ForeachExecutor(new Foreach(), this.State));
[Fact]
public void ForeachNamingConvention()
{
// Arrange
string testId = this.CreateActionId().Value;
// Act
string startStep = ForeachExecutor.Steps.Start(testId);
string nextStep = ForeachExecutor.Steps.Next(testId);
string endStep = ForeachExecutor.Steps.End(testId);
// Assert
Assert.Equal($"{testId}_{nameof(ForeachExecutor.Steps.Start)}", startStep);
Assert.Equal($"{testId}_{nameof(ForeachExecutor.Steps.Next)}", nextStep);
Assert.Equal($"{testId}_{nameof(ForeachExecutor.Steps.End)}", endStep);
}
[Fact]
public async Task ForeachInvokedWithSingleValueAsync()
{
// Arrange
this.SetVariableState("CurrentValue");
// Act & Assert
await this.ExecuteTestAsync(
displayName: nameof(ForeachInvokedWithSingleValueAsync),
items: ValueExpression.Literal(new NumberDataValue(42)),
valueName: "CurrentValue",
indexName: null);
}
[Fact]
public async Task ForeachInvokedWithTableValueAsync()
{
// Arrange
this.SetVariableState("CurrentValue");
// Act & Assert
await this.ExecuteTestAsync(
displayName: nameof(ForeachInvokedWithTableValueAsync),
items: ValueExpression.Literal(DataValue.EmptyTable),
valueName: "CurrentValue",
indexName: null);
}
[Fact]
public async Task ForeachInvokedWithIndexAsync()
{
// Arrange
this.SetVariableState("CurrentValue", "CurrentIndex");
TableDataValue tableValue = DataValue.TableFromRecords(
DataValue.RecordFromFields(new KeyValuePair<string, DataValue>("item", new NumberDataValue(1))),
DataValue.RecordFromFields(new KeyValuePair<string, DataValue>("item", new NumberDataValue(2))),
DataValue.RecordFromFields(new KeyValuePair<string, DataValue>("item", new NumberDataValue(3))));
// Act & Assert
await this.ExecuteTestAsync(
displayName: nameof(ForeachInvokedWithIndexAsync),
items: ValueExpression.Literal(tableValue),
valueName: "CurrentValue",
indexName: "CurrentIndex");
}
[Fact]
public async Task ForeachInvokedWithExpressionAsync()
{
// Arrange
this.SetVariableState("CurrentValue");
this.State.Set("SourceArray", FormulaValue.NewTable(RecordType.Empty()));
// Act & Assert
await this.ExecuteTestAsync(
displayName: nameof(ForeachInvokedWithExpressionAsync),
items: ValueExpression.Variable(PropertyPath.TopicVariable("SourceArray")),
valueName: "CurrentValue",
indexName: null);
}
[Fact]
public async Task ForeachTakeNextAsync()
{
// Arrange
this.SetVariableState("CurrentValue");
this.State.Set(
"SourceArray",
FormulaValue.NewTable(
RecordType.Empty(),
FormulaValue.NewRecordFromFields(new NamedValue("value", FormulaValue.New(10))),
FormulaValue.NewRecordFromFields(new NamedValue("value", FormulaValue.New(20))),
FormulaValue.NewRecordFromFields(new NamedValue("value", FormulaValue.New(30)))));
// Act & Assert
await this.TakeNextTestAsync(
displayName: nameof(ForeachTakeNextAsync),
items: ValueExpression.Variable(PropertyPath.TopicVariable("SourceArray")),
valueName: "CurrentValue",
indexName: null);
}
[Fact]
public async Task ForeachTakeNextWithIndexAsync()
{
// Arrange
this.SetVariableState("CurrentValue", "CurrentIndex");
this.State.Set(
"SourceArray",
FormulaValue.NewTable(
RecordType.Empty(),
FormulaValue.NewRecordFromFields(new NamedValue("value", FormulaValue.New(10))),
FormulaValue.NewRecordFromFields(new NamedValue("value", FormulaValue.New(20))),
FormulaValue.NewRecordFromFields(new NamedValue("value", FormulaValue.New(30)))));
// Act & Assert
await this.TakeNextTestAsync(
displayName: nameof(ForeachTakeNextWithIndexAsync),
items: ValueExpression.Variable(PropertyPath.TopicVariable("SourceArray")),
valueName: "CurrentValue",
indexName: "CurrentIndex");
}
[Fact]
public async Task ForeachTakeLastAsync()
{
// Arrange
this.SetVariableState("CurrentValue");
this.State.Set(
"SourceArray",
FormulaValue.NewTable(
RecordType.Empty(),
FormulaValue.NewRecordFromFields(new NamedValue("value", FormulaValue.New(10)))));
// Act & Assert
await this.TakeNextTestAsync(
displayName: nameof(ForeachTakeLastAsync),
items: ValueExpression.Variable(PropertyPath.TopicVariable("SourceArray")),
valueName: "CurrentValue",
indexName: null);
}
[Fact]
public async Task ForeachTakeNextWhenDoneAsync()
{
// Arrange
this.SetVariableState("CurrentValue");
// Act & Assert
await this.TakeNextTestAsync(
displayName: nameof(ForeachTakeNextWhenDoneAsync),
items: ValueExpression.Literal(DataValue.EmptyTable),
valueName: "CurrentValue",
indexName: null,
expectValue: false);
}
[Fact]
public async Task ForeachCompletedWithoutIndexAsync()
{
// Arrange
this.SetVariableState("CurrentValue");
// Act & Assert
await this.CompletedTestAsync(
displayName: nameof(ForeachCompletedWithoutIndexAsync),
valueName: "CurrentValue",
indexName: null);
}
[Fact]
public async Task ForeachCompletedWithIndexAsync()
{
// Arrange
this.SetVariableState("CurrentValue", "CurrentIndex");
// Act & Assert
await this.CompletedTestAsync(
displayName: nameof(ForeachCompletedWithIndexAsync),
valueName: "CurrentValue",
indexName: "CurrentIndex");
}
private void SetVariableState(string valueName, string? indexName = null, FormulaValue? valueState = null)
{
this.State.Set(valueName, valueState ?? FormulaValue.New("something"));
if (indexName is not null)
{
this.State.Set(indexName, FormulaValue.New(33));
}
}
private async Task ExecuteTestAsync(
string displayName,
ValueExpression items,
string valueName,
string? indexName,
bool expectValue = false)
{
// Arrange
Foreach model = this.CreateModel(displayName, items, valueName, indexName);
ForeachExecutor action = new(model, this.State);
// Act
WorkflowEvent[] events = await this.ExecuteAsync(action, isDiscrete: false);
// Assert
VerifyModel(model, action);
VerifyInvocationEvent(events);
// IsDiscreteAction should be false for Foreach
Assert.Equal(
false,
action.GetType().BaseType?
.GetProperty("IsDiscreteAction", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)?
.GetValue(action));
// Verify HasValue state after execution
Assert.Equal(expectValue, action.HasValue);
// Verify value was reset at the end
this.VerifyUndefined(valueName);
// Verify index was reset at the end if it was used
if (indexName is not null)
{
this.VerifyUndefined(indexName);
}
}
private async Task TakeNextTestAsync(
string displayName,
ValueExpression items,
string valueName,
string? indexName,
bool expectValue = true)
{
// Arrange
Foreach model = this.CreateModel(displayName, items, valueName, indexName);
ForeachExecutor action = new(model, this.State);
// Act
await this.ExecuteAsync(action, ForeachExecutor.Steps.Next(action.Id), action.TakeNextAsync);
// Assert
VerifyModel(model, action);
// Verify HasValue state after execution
Assert.Equal(expectValue, action.HasValue);
}
private async Task CompletedTestAsync(
string displayName,
string valueName,
string? indexName)
{
// Arrange
Foreach model = this.CreateModel(displayName, ValueExpression.Literal(DataValue.EmptyTable), valueName, indexName);
ForeachExecutor action = new(model, this.State);
// Act
WorkflowEvent[] events = await this.ExecuteAsync(ForeachExecutor.Steps.End(action.Id), action.CompleteAsync);
// Assert
VerifyModel(model, action);
VerifyCompletionEvent(events);
// Verify HasValue state after completion
Assert.False(action.HasValue);
// Verify value was reset at the end
this.VerifyUndefined(valueName);
// Verify index was reset at the end if it was used
if (indexName is not null)
{
this.VerifyUndefined(indexName);
}
}
private Foreach CreateModel(
string displayName,
ValueExpression items,
string valueName,
string? indexName)
{
Foreach.Builder actionBuilder = new()
{
Id = this.CreateActionId(),
DisplayName = this.FormatDisplayName(displayName),
Items = items,
Value = PropertyPath.Create(FormatVariablePath(valueName)),
};
if (indexName is not null)
{
actionBuilder.Index = PropertyPath.Create(FormatVariablePath(indexName));
}
return AssignParent<Foreach>(actionBuilder);
}
}
@@ -6,6 +6,7 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.Agents.AI.Workflows.Declarative.Extensions;
using Microsoft.Agents.AI.Workflows.Declarative.Interpreter;
using Microsoft.Agents.AI.Workflows.Declarative.Kit;
using Microsoft.Agents.AI.Workflows.Declarative.PowerFx;
using Microsoft.Agents.ObjectModel;
using Microsoft.PowerFx.Types;
@@ -25,17 +26,37 @@ public abstract class WorkflowActionExecutorTest(ITestOutputHelper output) : Wor
protected string FormatDisplayName(string name) => $"{this.GetType().Name}_{name}";
internal async Task<WorkflowEvent[]> ExecuteAsync(DeclarativeActionExecutor executor)
internal Task<WorkflowEvent[]> ExecuteAsync(string actionId, DelegateAction<ActionExecutorResult> executorAction) =>
this.ExecuteAsync(new DelegateActionExecutor(actionId, this.State, executorAction), isDiscrete: false);
internal Task<WorkflowEvent[]> ExecuteAsync(Executor executor, string actionId, DelegateAction<ActionExecutorResult> executorAction) =>
this.ExecuteAsync([executor, new DelegateActionExecutor(actionId, this.State, executorAction)], isDiscrete: false);
internal Task<WorkflowEvent[]> ExecuteAsync(Executor executor, bool isDiscrete = true) =>
this.ExecuteAsync([executor], isDiscrete);
internal async Task<WorkflowEvent[]> ExecuteAsync(Executor[] executors, bool isDiscrete)
{
this.State.Bind();
TestWorkflowExecutor workflowExecutor = new();
WorkflowBuilder workflowBuilder = new(workflowExecutor);
workflowBuilder.AddEdge(workflowExecutor, executor);
Executor prevExecutor = workflowExecutor;
foreach (Executor executor in executors)
{
workflowBuilder.AddEdge(prevExecutor, executor);
prevExecutor = executor;
}
await using StreamingRun run = await InProcessExecution.StreamAsync(workflowBuilder.Build(), this.State);
WorkflowEvent[] events = await run.WatchStreamAsync().ToArrayAsync();
Assert.Contains(events, e => e is DeclarativeActionInvokedEvent);
Assert.Contains(events, e => e is DeclarativeActionCompletedEvent);
if (isDiscrete)
{
VerifyInvocationEvent(events);
VerifyCompletionEvent(events);
}
ExecutorFailedEvent[] failureEvents = events.OfType<ExecutorFailedEvent>().ToArray();
switch (failureEvents.Length)
{
@@ -48,6 +69,7 @@ public abstract class WorkflowActionExecutorTest(ITestOutputHelper output) : Wor
throw aggregateException;
}
return events;
}
@@ -57,15 +79,21 @@ public abstract class WorkflowActionExecutorTest(ITestOutputHelper output) : Wor
Assert.Equal(model, action.Model);
}
protected static void VerifyInvocationEvent(WorkflowEvent[] events) =>
Assert.Contains(events, e => e is DeclarativeActionInvokedEvent);
protected static void VerifyCompletionEvent(WorkflowEvent[] events) =>
Assert.Contains(events, e => e is DeclarativeActionCompletedEvent);
protected void VerifyState(string variableName, FormulaValue expectedValue) => this.VerifyState(variableName, WorkflowFormulaState.DefaultScopeName, expectedValue);
internal void VerifyState(string variableName, string scopeName, FormulaValue expectedValue)
protected void VerifyState(string variableName, string scopeName, FormulaValue expectedValue)
{
FormulaValue actualValue = this.State.Get(variableName, scopeName);
Assert.Equal(expectedValue.Format(), actualValue.Format());
}
internal void VerifyUndefined(string variableName, string? scopeName = null) =>
protected void VerifyUndefined(string variableName, string? scopeName = null) =>
Assert.IsType<BlankValue>(this.State.Get(variableName, scopeName));
protected static TAction AssignParent<TAction>(DialogAction.Builder actionBuilder) where TAction : DialogAction
@@ -1,4 +1,4 @@
// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// </auto-generated>
@@ -81,7 +81,7 @@ public static class WorkflowProvider
this._values = [evaluatedValue];
}
await this.ResetAsync(context, null, cancellationToken).ConfigureAwait(false);
await this.ResetAsync(context, cancellationToken).ConfigureAwait(false);
return default;
}
@@ -99,7 +99,12 @@ public static class WorkflowProvider
}
}
public async ValueTask ResetAsync(IWorkflowContext context, object? _, CancellationToken cancellationToken)
public async ValueTask CompleteAsync(IWorkflowContext context, object? _, CancellationToken cancellationToken)
{
await this.ResetAsync(context, cancellationToken).ConfigureAwait(false);
}
private async ValueTask ResetAsync(IWorkflowContext context, CancellationToken cancellationToken)
{
await context.QueueStateUpdateAsync(key: "LoopValue", value: UnassignedValue.Instance, scopeName: "Local").ConfigureAwait(false);
await context.QueueStateUpdateAsync(key: "LoopIndex", value: UnassignedValue.Instance, scopeName: "Local").ConfigureAwait(false);
@@ -160,7 +165,7 @@ public static class WorkflowProvider
SetVariableInnerExecutor setVariableInner = new(myWorkflowRoot.Session);
SendActivityInnerExecutor sendActivityInner = new(myWorkflowRoot.Session);
DelegateExecutor endAll = new(id: "end_all", myWorkflowRoot.Session);
DelegateExecutor foreachLoopEnd = new(id: "foreach_loop_End", myWorkflowRoot.Session, foreachLoop.ResetAsync);
DelegateExecutor foreachLoopEnd = new(id: "foreach_loop_End", myWorkflowRoot.Session, foreachLoop.CompleteAsync);
// Define the workflow builder
WorkflowBuilder builder = new(myWorkflowRoot);
@@ -182,4 +187,4 @@ public static class WorkflowProvider
// Build the workflow
return builder.Build(validateOrphans: false);
}
}
}
@@ -1,4 +1,4 @@
// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// </auto-generated>
@@ -81,7 +81,7 @@ public static class WorkflowProvider
this._values = [evaluatedValue];
}
await this.ResetAsync(context, null, cancellationToken).ConfigureAwait(false);
await this.ResetAsync(context, cancellationToken).ConfigureAwait(false);
return default;
}
@@ -99,7 +99,12 @@ public static class WorkflowProvider
}
}
public async ValueTask ResetAsync(IWorkflowContext context, object? _, CancellationToken cancellationToken)
public async ValueTask CompleteAsync(IWorkflowContext context, object? _, CancellationToken cancellationToken)
{
await this.ResetAsync(context, cancellationToken).ConfigureAwait(false);
}
private async ValueTask ResetAsync(IWorkflowContext context, CancellationToken cancellationToken)
{
await context.QueueStateUpdateAsync(key: "LoopValue", value: UnassignedValue.Instance, scopeName: "Local").ConfigureAwait(false);
await context.QueueStateUpdateAsync(key: "LoopIndex", value: UnassignedValue.Instance, scopeName: "Local").ConfigureAwait(false);
@@ -160,7 +165,7 @@ public static class WorkflowProvider
SetVariableInnerExecutor setVariableInner = new(myWorkflowRoot.Session);
SendActivityInnerExecutor sendActivityInner = new(myWorkflowRoot.Session);
DelegateExecutor endAll = new(id: "end_all", myWorkflowRoot.Session);
DelegateExecutor foreachLoopEnd = new(id: "foreach_loop_End", myWorkflowRoot.Session, foreachLoop.ResetAsync);
DelegateExecutor foreachLoopEnd = new(id: "foreach_loop_End", myWorkflowRoot.Session, foreachLoop.CompleteAsync);
// Define the workflow builder
WorkflowBuilder builder = new(myWorkflowRoot);
@@ -182,4 +187,4 @@ public static class WorkflowProvider
// Build the workflow
return builder.Build(validateOrphans: false);
}
}
}
@@ -1,4 +1,4 @@
// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// </auto-generated>
@@ -81,7 +81,7 @@ public static class WorkflowProvider
this._values = [evaluatedValue];
}
await this.ResetAsync(context, null, cancellationToken).ConfigureAwait(false);
await this.ResetAsync(context, cancellationToken).ConfigureAwait(false);
return default;
}
@@ -99,7 +99,12 @@ public static class WorkflowProvider
}
}
public async ValueTask ResetAsync(IWorkflowContext context, object? _, CancellationToken cancellationToken)
public async ValueTask CompleteAsync(IWorkflowContext context, object? _, CancellationToken cancellationToken)
{
await this.ResetAsync(context, cancellationToken).ConfigureAwait(false);
}
private async ValueTask ResetAsync(IWorkflowContext context, CancellationToken cancellationToken)
{
await context.QueueStateUpdateAsync(key: "LoopValue", value: UnassignedValue.Instance, scopeName: "Local").ConfigureAwait(false);
await context.QueueStateUpdateAsync(key: "LoopIndex", value: UnassignedValue.Instance, scopeName: "Local").ConfigureAwait(false);
@@ -158,7 +163,7 @@ public static class WorkflowProvider
SetVariableInnerExecutor setVariableInner = new(myWorkflowRoot.Session);
SendActivityInnerExecutor sendActivityInner = new(myWorkflowRoot.Session);
DelegateExecutor endAll = new(id: "end_all", myWorkflowRoot.Session);
DelegateExecutor foreachLoopEnd = new(id: "foreach_loop_End", myWorkflowRoot.Session, foreachLoop.ResetAsync);
DelegateExecutor foreachLoopEnd = new(id: "foreach_loop_End", myWorkflowRoot.Session, foreachLoop.CompleteAsync);
// Define the workflow builder
WorkflowBuilder builder = new(myWorkflowRoot);
@@ -178,4 +183,4 @@ public static class WorkflowProvider
// Build the workflow
return builder.Build(validateOrphans: false);
}
}
}