// Copyright (c) Microsoft. All rights reserved. using System.Threading.Tasks; using Microsoft.Agents.AI.Workflows.Declarative.Kit; using Microsoft.Agents.AI.Workflows.Declarative.ObjectModel; using Microsoft.Agents.ObjectModel; namespace Microsoft.Agents.AI.Workflows.Declarative.UnitTests.ObjectModel; /// /// Tests for . /// public sealed class ConditionGroupExecutorTest(ITestOutputHelper output) : WorkflowActionExecutorTest(output) { [Fact] public void ConditionGroupThrowsWhenModelInvalid() => // Arrange, Act & Assert Assert.Throws(() => new ConditionGroupExecutor(new ConditionGroup(), this.State)); [Fact] public void ConditionGroupDefaultNaming() { // Arrange ConditionGroup model = this.CreateModel(nameof(ConditionGroupDefaultNaming), [false], includeElse: true, defineActionIds: false); ConditionItem condition = model.Conditions[0]; // Act string conditionStepId = ConditionGroupExecutor.Steps.Item(model, condition); string elseStepId = ConditionGroupExecutor.Steps.Else(model); // Assert Assert.Equal($"{model.Id}_Items0", conditionStepId); Assert.Equal(model.ElseActions.Id.Value, elseStepId); } [Fact] public void ConditionGroupExplicitNaming() { // Arrange ConditionGroup model = this.CreateModel(nameof(ConditionGroupExplicitNaming), [false], includeElse: true); ConditionItem condition = model.Conditions[0]; // Act string conditionStepId = ConditionGroupExecutor.Steps.Item(model, condition); string elseStepId = ConditionGroupExecutor.Steps.Else(model); // Assert Assert.Equal(condition.Id, conditionStepId); Assert.Equal(model.ElseActions.Id.Value, elseStepId); } [Fact] public async Task ConditionGroupFirstConditionTrueAsync() { // Arrange, Act & Assert await this.ExecuteTestAsync( displayName: nameof(ConditionGroupFirstConditionTrueAsync), conditions: [true, false]); } [Fact] public async Task ConditionGroupSecondConditionTrueAsync() { // Arrange, Act & Assert await this.ExecuteTestAsync( displayName: nameof(ConditionGroupSecondConditionTrueAsync), conditions: [false, true]); } [Fact] public async Task ConditionGroupFirstConditionNullAsync() { // Arrange, Act & Assert await this.ExecuteTestAsync( displayName: nameof(ConditionGroupFirstConditionNullAsync), conditions: [null, true]); } [Fact] public async Task ConditionGroupElseBranchAsync() { // Arrange, Act & Assert await this.ExecuteTestAsync( displayName: nameof(ConditionGroupElseBranchAsync), conditions: [false, false], includeElse: true); } [Fact] public async Task ConditionGroupDoneAsync() { ConditionGroup model = this.CreateModel(nameof(ConditionGroupDoneAsync), [true]); ConditionGroupExecutor action = new(model, this.State); // Act WorkflowEvent[] events = await this.ExecuteAsync("condition_done_id", action.DoneAsync); // Assert VerifyModel(model, action); Assert.NotEmpty(events); VerifyCompletionEvent(events); } [Fact] public void ConditionGroupIsMatchTrue() { // Arrange ConditionGroup model = this.CreateModel(nameof(ConditionGroupIsMatchTrue), [true]); ConditionItem firstCondition = model.Conditions[0]; ConditionGroupExecutor executor = new(model, this.State); ActionExecutorResult result = new(executor.Id, ConditionGroupExecutor.Steps.Item(model, firstCondition)); // Act bool isMatch = executor.IsMatch(firstCondition, result); // Assert Assert.True(isMatch); } [Fact] public void ConditionGroupIsMatchFalse() { // Arrange ConditionGroup model = this.CreateModel(nameof(ConditionGroupIsMatchFalse), [true, false]); ConditionItem firstCondition = model.Conditions[0]; ConditionItem secondCondition = model.Conditions[1]; ConditionGroupExecutor executor = new(model, this.State); ActionExecutorResult result = new(executor.Id, ConditionGroupExecutor.Steps.Item(model, secondCondition)); // Act bool isMatch = executor.IsMatch(firstCondition, result); // Assert Assert.False(isMatch); } [Fact] public void ConditionGroupIsElseTrue() { // Arrange ConditionGroup model = this.CreateModel(nameof(ConditionGroupIsElseTrue), [false]); ConditionGroupExecutor executor = new(model, this.State); ActionExecutorResult result = new(executor.Id, ConditionGroupExecutor.Steps.Else(model)); // Act bool isElse = executor.IsElse(result); // Assert Assert.True(isElse); } [Fact] public void ConditionGroupIsElseFalse() { // Arrange ConditionGroup model = this.CreateModel(nameof(ConditionGroupIsElseFalse), [false]); ConditionGroupExecutor executor = new(model, this.State); ActionExecutorResult result = new(executor.Id, "different_step"); // Act bool isElse = executor.IsElse(result); // Assert Assert.False(isElse); } private async Task ExecuteTestAsync( string displayName, bool?[] conditions, bool includeElse = false) { // Arrange ConditionGroup model = this.CreateModel(displayName, conditions, includeElse); ConditionGroupExecutor action = new(model, this.State); // Act WorkflowEvent[] events = await this.ExecuteAsync(action, isDiscrete: false); // Assert VerifyModel(model, action); Assert.NotEmpty(events); VerifyInvocationEvent(events); VerifyIsDiscrete(action, isDiscrete: false); } private ConditionGroup CreateModel( string displayName, bool?[] conditions, bool includeElse = false, bool defineActionIds = true) { ConditionGroup.Builder actionBuilder = new() { Id = this.CreateActionId(), DisplayName = this.FormatDisplayName(displayName), }; for (int index = 0; index < conditions.Length; ++index) { bool? condition = conditions[index]; ConditionItem.Builder conditionBuilder = new() { Id = defineActionIds ? $"condition_{index}" : null, Actions = this.CreateActions(defineActionIds ? $"condition_actions_{index}" : null), Condition = condition is null ? null : BoolExpression.Literal(condition.Value).ToBuilder(), }; actionBuilder.Conditions.Add(conditionBuilder); } if (includeElse) { actionBuilder.ElseActions = this.CreateActions(defineActionIds ? "else_actions" : null); } return AssignParent(actionBuilder); } private ActionScope.Builder CreateActions(string? actionScopeId) { ActionScope.Builder actions = []; if (actionScopeId is not null) { actions.Id = new ActionId(actionScopeId); } actions.Actions.Add( new SendActivity.Builder { Id = $"{actionScopeId ?? "action"}_send_activity", Activity = new MessageActivityTemplate(), }); return actions; } }