.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
co-authored by crickman Copilot copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Chris Rickman
parent be7b55f99b
commit 69dcfe31ee
12 changed files with 440 additions and 60 deletions
@@ -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);
}
}
}