diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Extensions/ChatMessageExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Extensions/ChatMessageExtensions.cs index e841bd4bbb..279fec3e6d 100644 --- a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Extensions/ChatMessageExtensions.cs +++ b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Extensions/ChatMessageExtensions.cs @@ -45,23 +45,17 @@ internal static class ChatMessageExtensions public static IEnumerable ToChatMessages(this TableDataValue messages) { - foreach (DataValue message in messages.Values) + foreach (RecordDataValue record in messages.Values) { - if (message is RecordDataValue record) + DataValue sourceRecord = record; + if (record.Properties.Count == 1 && record.Properties.TryGetValue("Value", out DataValue? singleColumn)) { - if (record.Properties.Count == 1 && record.Properties.TryGetValue("Value", out DataValue? singleColumn)) - { - record = singleColumn as RecordDataValue ?? record; - } - ChatMessage? convertedMessage = record.ToChatMessage(); - if (convertedMessage is not null) - { - yield return convertedMessage; - } + sourceRecord = singleColumn; } - else if (message is StringDataValue text) + ChatMessage? convertedMessage = sourceRecord.ToChatMessage(); + if (convertedMessage is not null) { - yield return ToChatMessage(text); + yield return convertedMessage; } } } @@ -143,7 +137,7 @@ internal static class ChatMessageExtensions private static ChatRole GetRole(this RecordDataValue message) { StringDataValue? roleValue = message.GetProperty(TypeSchema.Message.Fields.Role); - if (roleValue is null || string.IsNullOrWhiteSpace(roleValue.Value)) + if (string.IsNullOrWhiteSpace(roleValue?.Value)) { return ChatRole.User; } @@ -164,13 +158,13 @@ internal static class ChatMessageExtensions { foreach (RecordDataValue contentItem in content.Values) { - StringDataValue? contentValue = contentItem?.GetProperty(TypeSchema.Message.Fields.ContentValue); + StringDataValue? contentValue = contentItem.GetProperty(TypeSchema.Message.Fields.ContentValue); if (contentValue is null || string.IsNullOrWhiteSpace(contentValue.Value)) { continue; } yield return - contentItem?.GetProperty(TypeSchema.Message.Fields.ContentType)?.Value switch + contentItem.GetProperty(TypeSchema.Message.Fields.ContentType)?.Value switch { TypeSchema.Message.ContentTypes.ImageUrl => GetImageContent(contentValue.Value), TypeSchema.Message.ContentTypes.ImageFile => new HostedFileContent(contentValue.Value), @@ -218,6 +212,7 @@ internal static class ChatMessageExtensions UriContent uriContent => CreateContentRecord(TypeSchema.Message.ContentTypes.ImageUrl, uriContent.Uri.ToString()), HostedFileContent fileContent => CreateContentRecord(TypeSchema.Message.ContentTypes.ImageFile, fileContent.FileId), TextContent textContent => CreateContentRecord(TypeSchema.Message.ContentTypes.Text, textContent.Text), + DataContent dataContent => CreateContentRecord(TypeSchema.Message.ContentTypes.ImageUrl, dataContent.Uri), _ => [] }; diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Extensions/ExpandoObjectExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Extensions/ExpandoObjectExtensions.cs index b279272c7b..7168b62fb9 100644 --- a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Extensions/ExpandoObjectExtensions.cs +++ b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Extensions/ExpandoObjectExtensions.cs @@ -15,7 +15,7 @@ internal static class ExpandoObjectExtensions foreach (KeyValuePair property in value) { - recordType.Add(property.Key, property.Value.GetFormulaType()); + recordType = recordType.Add(property.Key, property.Value.GetFormulaType()); } return recordType; diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/Extensions/ChatMessageExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/Extensions/ChatMessageExtensionsTests.cs new file mode 100644 index 0000000000..8fdc76af95 --- /dev/null +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/Extensions/ChatMessageExtensionsTests.cs @@ -0,0 +1,669 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Collections.Generic; +using System.Linq; +using Microsoft.Agents.AI.Workflows.Declarative.Extensions; +using Microsoft.Agents.AI.Workflows.Declarative.PowerFx; +using Microsoft.Bot.ObjectModel; +using Microsoft.Extensions.AI; +using Microsoft.PowerFx.Types; + +namespace Microsoft.Agents.AI.Workflows.Declarative.UnitTests.Extensions; + +public sealed class ChatMessageExtensionsTests +{ + [Fact] + public void ToRecordWithSimpleTextMessage() + { + // Arrange + ChatMessage message = new(ChatRole.User, "Hello World"); + + // Act + RecordValue result = message.ToRecord(); + + // Assert + Assert.NotNull(result); + Assert.Contains(result.Fields, f => f.Name == TypeSchema.Message.Fields.Role); + Assert.Contains(result.Fields, f => f.Name == TypeSchema.Message.Fields.Text); + + FormulaValue roleField = result.GetField(TypeSchema.Message.Fields.Role); + StringValue roleValue = Assert.IsType(roleField); + Assert.Equal(ChatRole.User.Value, roleValue.Value); + } + + [Fact] + public void ToRecordWithAssistantMessage() + { + // Arrange + ChatMessage message = new(ChatRole.Assistant, "I can help you"); + + // Act + RecordValue result = message.ToRecord(); + + // Assert + Assert.NotNull(result); + Assert.Contains(result.Fields, f => f.Name == TypeSchema.Message.Fields.Role); + + FormulaValue roleField = result.GetField(TypeSchema.Message.Fields.Role); + StringValue roleValue = Assert.IsType(roleField); + Assert.Equal(ChatRole.Assistant.Value, roleValue.Value); + } + + [Fact] + public void ToRecordIncludesAllStandardFields() + { + // Arrange + ChatMessage message = new(ChatRole.User, "Test") + { + MessageId = "msg-123" + }; + + // Act + RecordValue result = message.ToRecord(); + + // Assert + Assert.NotNull(result.GetField(TypeSchema.Discriminator)); + Assert.NotNull(result.GetField(TypeSchema.Message.Fields.Id)); + Assert.NotNull(result.GetField(TypeSchema.Message.Fields.Role)); + Assert.NotNull(result.GetField(TypeSchema.Message.Fields.Content)); + Assert.NotNull(result.GetField(TypeSchema.Message.Fields.Text)); + Assert.NotNull(result.GetField(TypeSchema.Message.Fields.Metadata)); + } + + [Fact] + public void ToTableWithMultipleMessages() + { + // Arrange + IEnumerable messages = + [ + new(ChatRole.User, "First message"), + new(ChatRole.Assistant, "Second message"), + new(ChatRole.User, "Third message") + ]; + + // Act + TableValue result = messages.ToTable(); + + // Assert + Assert.NotNull(result); + Assert.Equal(3, result.Rows.Count()); + } + + [Fact] + public void ToTableWithEmptyMessages() + { + // Arrange + IEnumerable messages = []; + + // Act + TableValue result = messages.ToTable(); + + // Assert + Assert.NotNull(result); + Assert.Empty(result.Rows); + } + + [Fact] + public void ToChatMessagesWithNull() + { + // Arrange + DataValue? value = null; + + // Act + IEnumerable? result = value.ToChatMessages(); + + // Assert + Assert.Null(result); + } + + [Fact] + public void ToChatMessagesWithBlankDataValue() + { + // Arrange + DataValue value = DataValue.Blank(); + + // Act + IEnumerable? result = value.ToChatMessages(); + + // Assert + Assert.Null(result); + } + + [Fact] + public void ToChatMessagesWithStringDataValue() + { + // Arrange + DataValue value = StringDataValue.Create("Hello"); + + // Act + IEnumerable? result = value.ToChatMessages(); + + // Assert + Assert.NotNull(result); + ChatMessage message = Assert.Single(result); + Assert.Equal(ChatRole.User, message.Role); + Assert.Equal("Hello", message.Text); + } + + [Fact] + public void ToChatMessagesWithRecordDataValue() + { + // Arrange + ChatMessage source = new(ChatRole.User, "Test"); + DataValue record = source.ToRecord().ToDataValue(); + + // Act + IEnumerable? result = record.ToChatMessages(); + + // Assert + Assert.NotNull(result); + ChatMessage message = Assert.Single(result); + Assert.Equal(source.Role, message.Role); + Assert.Equal(source.Text, message.Text); + } + + [Fact] + public void ToChatMessagesWithTableDataValue() + { + // Arrange + ChatMessage[] source = [new(ChatRole.User, "Test")]; + DataValue table = source.ToTable().ToDataValue(); + + // Act + IEnumerable? result = table.ToChatMessages(); + + // Assert + Assert.NotNull(result); + ChatMessage message = Assert.Single(result); + Assert.Equal(source[0].Role, message.Role); + Assert.Equal(source[0].Text, message.Text); + } + + [Fact] + public void ToChatMessagesWithTableOfDataValue() + { + // Arrange + TableDataValue table = DataValue.TableFromValues([new StringDataValue("test")]); + + // Act + IEnumerable? result = table.ToChatMessages(); + + // Assert + Assert.NotNull(result); + ChatMessage message = Assert.Single(result); + Assert.Equal(ChatRole.User, message.Role); + Assert.Equal("test", message.Text); + } + + [Fact] + public void ToChatMessagesWithUnsupportedValue() + { + // Arrange + BooleanDataValue booleanValue = new(true); + + // Act + IEnumerable? messages = booleanValue.ToChatMessages(); + + // Assert + Assert.Null(messages); + } + + [Fact] + public void ToChatMessageFromStringDataValue() + { + // Arrange + StringDataValue value = StringDataValue.Create("Test message"); + + // Act + ChatMessage result = value.ToChatMessage(); + + // Assert + Assert.NotNull(result); + Assert.Equal(ChatRole.User, result.Role); + Assert.Equal("Test message", result.Text); + } + + [Fact] + public void ToChatMessageFromDataValueRecord() + { + // Arrange + ChatMessage source = new(ChatRole.User, "Test"); + DataValue record = source.ToRecord().ToDataValue(); + + // Act + ChatMessage? result = record.ToChatMessage(); + + // Assert + Assert.NotNull(result); + Assert.Equal(ChatRole.User, result.Role); + Assert.Equal("Test", result.Text); + } + [Fact] + public void ToChatMessageFromDataValueString() + { + // Arrange + DataValue value = StringDataValue.Create("Test message"); + + // Act + ChatMessage? result = value.ToChatMessage(); + + // Assert + Assert.NotNull(result); + Assert.Equal(ChatRole.User, result.Role); + Assert.Equal("Test message", result.Text); + } + + [Fact] + public void ToChatMessageFromBlankDataValue() + { + // Arrange + DataValue value = DataValue.Blank(); + + // Act + ChatMessage? result = value.ToChatMessage(); + + // Assert + Assert.Null(result); + } + + [Fact] + public void ToChatMessageFromUnsupportedValue() + { + // Arrange + DataValue value = BooleanDataValue.Create(true); + + // Act & Assert + Assert.Throws(() => value.ToChatMessage()); + } + + [Fact] + public void ToChatMessageFromRecordDataValue() + { + // Arrange + // Note: Use "Agent" not "Assistant" - AgentMessageRole.Agent maps to ChatRole.Assistant + RecordDataValue record = DataValue.RecordFromFields( + new KeyValuePair(TypeSchema.Message.Fields.Role, StringDataValue.Create("Agent")), + new KeyValuePair(TypeSchema.Message.Fields.Content, DataValue.EmptyTable)); + + // Act + ChatMessage result = record.ToChatMessage(); + + // Assert + Assert.NotNull(result); + Assert.Equal(ChatRole.Assistant, result.Role); + } + + [Fact] + public void ToChatMessageWithImpliedRole() + { + // Arrange + RecordValue source = + FormulaValue.NewRecordFromFields( + new NamedValue(TypeSchema.Message.Fields.Role, FormulaValue.New(string.Empty)), + new NamedValue( + TypeSchema.Message.Fields.Content, + FormulaValue.NewTable( + TypeSchema.Message.ContentRecordType, + FormulaValue.NewRecordFromFields( + new NamedValue(TypeSchema.Message.Fields.ContentType, TypeSchema.Message.ContentTypes.Text.ToFormula()), + new NamedValue(TypeSchema.Message.Fields.ContentValue, FormulaValue.New("Test")))))); + RecordDataValue record = source.ToRecord(); + + // Act + ChatMessage? result = record.ToChatMessage(); + + // Assert + Assert.NotNull(result); + Assert.Equal(ChatRole.User, result.Role); + Assert.Equal("Test", result.Text); + } + + [Fact] + public void ToChatMessageWithImageUrlContentType() + { + // Arrange + ChatMessage source = new(ChatRole.User, [AgentMessageContentType.ImageUrl.ToContent("https://example.com/image.jpg")!]); + DataValue record = source.ToRecord().ToDataValue(); + + // Act + ChatMessage? result = record.ToChatMessage(); + + // Assert + Assert.NotNull(result); + AIContent content = Assert.Single(result.Contents); + Assert.IsType(content); + } + + [Fact] + public void ToChatMessageWithWithImageDataContentType() + { + // Arrange + ChatMessage source = new(ChatRole.User, [AgentMessageContentType.ImageUrl.ToContent("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA")!]); + DataValue record = source.ToRecord().ToDataValue(); + + // Act + ChatMessage? result = record.ToChatMessage(); + + // Assert + Assert.NotNull(result); + AIContent content = Assert.Single(result.Contents); + Assert.IsType(content); + } + + [Fact] + public void ToChatMessageWithWithImageFileContentType() + { + // Arrange + ChatMessage source = new(ChatRole.User, [AgentMessageContentType.ImageFile.ToContent("file-id-123")!]); + DataValue record = source.ToRecord().ToDataValue(); + + // Act + ChatMessage? result = record.ToChatMessage(); + + // Assert + Assert.NotNull(result); + AIContent content = Assert.Single(result.Contents); + Assert.IsType(content); + } + + [Fact] + public void ToChatMessageWithUnsupportedContent() + { + // Arrange + ChatMessage source = new(ChatRole.User, "Test"); + RecordDataValue record = source.ToRecord().ToRecord(); + DataValue contentValue = record.Properties[TypeSchema.Message.Fields.Content]; + TableDataValue contentValues = Assert.IsType(contentValue, exactMatch: false); + RecordDataValue badContent = DataValue.RecordFromFields( + new KeyValuePair(TypeSchema.Message.Fields.ContentType, StringDataValue.Create(TypeSchema.Message.ContentTypes.Text)), + new KeyValuePair(TypeSchema.Message.Fields.ContentValue, BooleanDataValue.Create(true))); + contentValues.Values.Add(badContent); + + // Act + ChatMessage message = record.ToChatMessage(); + + // Assert + Assert.Single(message.Contents); + Assert.Equal("Test", message.Text); + } + + [Fact] + public void ToChatMessageWithEmptyContent() + { + // Arrange + ChatMessage source = new(ChatRole.User, "Test"); + source.Contents.Add(new TextContent(string.Empty)); + RecordDataValue record = source.ToRecord().ToRecord(); + + // Act + ChatMessage message = record.ToChatMessage(); + + // Assert + Assert.Single(message.Contents); + Assert.Equal("Test", message.Text); + } + + [Fact] + public void ToMetadataWithNull() + { + // Arrange + RecordDataValue? metadata = null; + + // Act + AdditionalPropertiesDictionary? result = metadata.ToMetadata(); + + // Assert + Assert.Null(result); + } + + [Fact] + public void ToMetadataWithProperties() + { + // Arrange + RecordDataValue metadata = DataValue.RecordFromFields( + new KeyValuePair("key1", StringDataValue.Create("value1")), + new KeyValuePair("key2", NumberDataValue.Create(42))); + + // Act + AdditionalPropertiesDictionary? result = metadata.ToMetadata(); + + // Assert + Assert.NotNull(result); + Assert.Equal(2, result.Count); + Assert.Equal("value1", result["key1"]); + Assert.Equal(42m, result["key2"]); + } + + [Fact] + public void ToChatRoleFromAgentMessageRole() + { + // Act & Assert + Assert.Equal(ChatRole.Assistant, AgentMessageRole.Agent.ToChatRole()); + Assert.Equal(ChatRole.User, AgentMessageRole.User.ToChatRole()); + Assert.Equal(ChatRole.User, ((AgentMessageRole)99).ToChatRole()); + Assert.Equal(ChatRole.User, ((AgentMessageRole?)null).ToChatRole()); + } + + [Fact] + public void AgentMessageContentTypeToContentMissing() + { + // Act & Assert + Assert.Null(AgentMessageContentType.Text.ToContent(string.Empty)); + Assert.Null(AgentMessageContentType.Text.ToContent(null)); + } + + [Fact] + public void AgentMessageContentTypeToContentText() + { + // Arrange & Act + AIContent? result = AgentMessageContentType.Text.ToContent("Sample text"); + + // Assert + Assert.NotNull(result); + TextContent textContent = Assert.IsType(result); + Assert.Equal("Sample text", textContent.Text); + } + + [Fact] + public void ToContentWithImageUrlContentType() + { + // Arrange & Act + AIContent? result = AgentMessageContentType.ImageUrl.ToContent("https://example.com/image.jpg"); + + // Assert + Assert.NotNull(result); + UriContent uriContent = Assert.IsType(result); + Assert.Equal("https://example.com/image.jpg", uriContent.Uri.ToString()); + } + + [Fact] + public void ToContentWithImageUrlContentTypeDataUri() + { + // Arrange & Act + AIContent? result = AgentMessageContentType.ImageUrl.ToContent("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA"); + + // Assert + Assert.NotNull(result); + DataContent dataContent = Assert.IsType(result); + Assert.False(dataContent.Data.IsEmpty); + } + + [Fact] + public void ToContentWithImageFileContentType() + { + // Arrange & Act + AIContent? result = AgentMessageContentType.ImageFile.ToContent("file-id-123"); + + // Assert + Assert.NotNull(result); + HostedFileContent fileContent = Assert.IsType(result); + Assert.Equal("file-id-123", fileContent.FileId); + } + + [Fact] + public void ToChatMessageFromFunctionResultContents() + { + // Arrange + IEnumerable functionResults = + [ + new(callId: "call1", result: "Result 1"), + new(callId: "call2", result: "Result 2") + ]; + + // Act + ChatMessage result = functionResults.ToChatMessage(); + + // Assert + Assert.NotNull(result); + Assert.Equal(ChatRole.Tool, result.Role); + Assert.Equal(2, result.Contents.Count); + } + + [Fact] + public void ToChatMessagesFromTableDataValueWithStrings() + { + // Arrange + TableDataValue table = + DataValue.TableFromValues( + [ + StringDataValue.Create("Message 1"), + StringDataValue.Create("Message 2") + ]); + + // Act + IEnumerable result = table.ToChatMessages(); + + // Assert + Assert.NotNull(result); + Assert.Equal(2, result.Count()); + Assert.All(result, msg => Assert.Equal(ChatRole.User, msg.Role)); + } + + [Fact] + public void ToChatMessagesFromTableDataValueWithRecords() + { + // Arrange + RecordDataValue record1 = DataValue.RecordFromFields( + new KeyValuePair(TypeSchema.Message.Fields.Role, StringDataValue.Create("User")), + new KeyValuePair(TypeSchema.Message.Fields.Content, DataValue.EmptyTable)); + + RecordDataValue record2 = DataValue.RecordFromFields( + new KeyValuePair(TypeSchema.Message.Fields.Role, StringDataValue.Create("Assistant")), + new KeyValuePair(TypeSchema.Message.Fields.Content, DataValue.EmptyTable)); + + TableDataValue table = DataValue.TableFromRecords(record1, record2); + + // Act + IEnumerable result = table.ToChatMessages(); + + // Assert + Assert.NotNull(result); + Assert.Equal(2, result.Count()); + } + + [Fact] + public void ToChatMessagesFromTableDataValueWithSingleColumnRecords() + { + // Arrange + RecordDataValue innerRecord = DataValue.RecordFromFields( + new KeyValuePair(TypeSchema.Message.Fields.Role, StringDataValue.Create("User")), + new KeyValuePair(TypeSchema.Message.Fields.Content, DataValue.EmptyTable)); + + RecordDataValue wrappedRecord = DataValue.RecordFromFields( + new KeyValuePair("Value", innerRecord)); + + TableDataValue table = DataValue.TableFromRecords(wrappedRecord); + + // Act + IEnumerable result = table.ToChatMessages(); + + // Assert + Assert.NotNull(result); + ChatMessage message = Assert.Single(result); + Assert.Equal(ChatRole.User, message.Role); + } + + [Fact] + public void ToRecordWithMessageContainingMultipleContentItems() + { + // Arrange + ChatMessage message = + new(ChatRole.User, + [ + new TextContent("First part"), + new TextContent("Second part") + ]); + + // Act + RecordValue result = message.ToRecord(); + + // Assert + Assert.NotNull(result); + FormulaValue contentField = result.GetField(TypeSchema.Message.Fields.Content); + TableValue contentTable = Assert.IsType(contentField, exactMatch: false); + Assert.Equal(2, contentTable.Rows.Count()); + } + + [Fact] + public void ToRecordWithMessageContainingUriContent() + { + // Arrange + ChatMessage message = + new(ChatRole.User, + [ + new UriContent("https://example.com/image.jpg", "image/*") + ]); + + // Act + RecordValue result = message.ToRecord(); + + // Assert + Assert.NotNull(result); + FormulaValue contentField = result.GetField(TypeSchema.Message.Fields.Content); + TableValue contentTable = Assert.IsType(contentField, exactMatch: false); + Assert.Single(contentTable.Rows); + } + + [Fact] + public void ToRecordWithMessageContainingHostedFileContent() + { + // Arrange + ChatMessage message = + new(ChatRole.User, + [ + new HostedFileContent("file-123") + ]); + + // Act + RecordValue result = message.ToRecord(); + + // Assert + Assert.NotNull(result); + FormulaValue contentField = result.GetField(TypeSchema.Message.Fields.Content); + TableValue contentTable = Assert.IsType(contentField, exactMatch: false); + Assert.Single(contentTable.Rows); + } + + [Fact] + public void ToRecordWithMessageContainingMetadata() + { + // Arrange + ChatMessage message = new(ChatRole.User, "Test message") + { + AdditionalProperties = new AdditionalPropertiesDictionary + { + ["custom_key"] = "custom_value", + ["count"] = 5 + } + }; + + // Act + RecordValue result = message.ToRecord(); + + // Assert + Assert.NotNull(result); + FormulaValue metadataField = result.GetField(TypeSchema.Message.Fields.Metadata); + RecordValue metadataRecord = Assert.IsType(metadataField, exactMatch: false); + Assert.Equal(2, metadataRecord.Fields.Count()); + } +} diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/Extensions/DataValueExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/Extensions/DataValueExtensionsTests.cs new file mode 100644 index 0000000000..c527b72abc --- /dev/null +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/Extensions/DataValueExtensionsTests.cs @@ -0,0 +1,845 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Microsoft.Agents.AI.Workflows.Declarative.Extensions; +using Microsoft.Agents.AI.Workflows.Declarative.Kit; +using Microsoft.Bot.ObjectModel; +using Microsoft.PowerFx.Types; + +namespace Microsoft.Agents.AI.Workflows.Declarative.UnitTests.Extensions; + +public sealed class DataValueExtensionsTests +{ + [Fact] + public void ToDataValueWithNull() + { + // Arrange + object? value = null; + + // Act + DataValue result = value.ToDataValue(); + + // Assert + Assert.IsType(result); + } + + [Fact] + public void ToDataValueWithUnassignedValue() + { + // Arrange + object value = UnassignedValue.Instance; + + // Act + DataValue result = value.ToDataValue(); + + // Assert + Assert.IsType(result); + } + + [Fact] + public void ToDataValueWithBooleanTrue() + { + // Arrange + const bool Value = true; + + // Act + DataValue result = Value.ToDataValue(); + + // Assert + BooleanDataValue boolValue = Assert.IsType(result); + Assert.True(boolValue.Value); + } + + [Fact] + public void ToDataValueWithBooleanFalse() + { + // Arrange + const bool Value = false; + + // Act + DataValue result = Value.ToDataValue(); + + // Assert + BooleanDataValue boolValue = Assert.IsType(result); + Assert.False(boolValue.Value); + } + + [Fact] + public void ToDataValueWithInt() + { + // Arrange + const int Value = 42; + + // Act + DataValue result = Value.ToDataValue(); + + // Assert + NumberDataValue numberValue = Assert.IsType(result); + Assert.Equal(42, numberValue.Value); + } + + [Fact] + public void ToDataValueWithLong() + { + // Arrange + const long Value = 9876543210L; + + // Act + DataValue result = Value.ToDataValue(); + + // Assert + NumberDataValue numberValue = Assert.IsType(result); + Assert.Equal(9876543210L, numberValue.Value); + } + + [Fact] + public void ToDataValueWithFloat() + { + // Arrange + const float Value = 3.14f; + + // Act + DataValue result = Value.ToDataValue(); + + // Assert + FloatDataValue floatValue = Assert.IsType(result); + Assert.Equal(3.14f, floatValue.Value, precision: 2); + } + + [Fact] + public void ToDataValueWithDecimal() + { + // Arrange + const decimal Value = 123.456m; + + // Act + DataValue result = Value.ToDataValue(); + + // Assert + NumberDataValue numberValue = Assert.IsType(result); + Assert.Equal(123.456m, numberValue.Value); + } + + [Fact] + public void ToDataValueWithDouble() + { + // Arrange + const double Value = 2.71828; + + // Act + DataValue result = Value.ToDataValue(); + + // Assert + FloatDataValue floatValue = Assert.IsType(result); + Assert.Equal(2.71828, floatValue.Value, precision: 5); + } + + [Fact] + public void ToDataValueWithString() + { + // Arrange + const string Value = "Test String"; + + // Act + DataValue result = Value.ToDataValue(); + + // Assert + StringDataValue stringValue = Assert.IsType(result); + Assert.Equal("Test String", stringValue.Value); + } + + [Fact] + public void ToDataValueWithDateTimeZeroTime() + { + // Arrange + DateTime value = new(2025, 10, 17, 0, 0, 0); + + // Act + DataValue result = value.ToDataValue(); + + // Assert + DateDataValue dateValue = Assert.IsType(result); + Assert.Equal(new DateTime(2025, 10, 17), dateValue.Value); + } + + [Fact] + public void ToDataValueWithDateTimeNonZeroTime() + { + // Arrange + DateTime value = new(2025, 10, 17, 14, 30, 45); + + // Act + DataValue result = value.ToDataValue(); + + // Assert + DateTimeDataValue dateTimeValue = Assert.IsType(result); + Assert.Equal(new DateTime(2025, 10, 17, 14, 30, 45), dateTimeValue.Value.DateTime); + } + + [Fact] + public void ToDataValueWithTimeSpan() + { + // Arrange + TimeSpan value = TimeSpan.FromHours(2.5); + + // Act + DataValue result = value.ToDataValue(); + + // Assert + TimeDataValue timeValue = Assert.IsType(result); + Assert.Equal(TimeSpan.FromHours(2.5), timeValue.Value); + } + + [Fact] + public void ToDataValueWithDataValue() + { + // Arrange + DataValue value = StringDataValue.Create("Already a DataValue"); + + // Act + DataValue result = value.ToDataValue(); + + // Assert + Assert.Same(value, result); + } + + [Fact] + public void ToDataValueWithFormulaValue() + { + // Arrange + FormulaValue value = FormulaValue.New(123); + + // Act + DataValue result = value.ToDataValue(); + + // Assert + NumberDataValue numberValue = Assert.IsType(result); + Assert.Equal(123, numberValue.Value); + } + + [Fact] + public void ToFormulaWithNull() + { + // Arrange + DataValue? value = null; + + // Act + FormulaValue result = value.ToFormula(); + + // Assert + Assert.IsType(result); + } + + [Fact] + public void ToFormulaWithBlankDataValue() + { + // Arrange + DataValue value = DataValue.Blank(); + + // Act + FormulaValue result = value.ToFormula(); + + // Assert + Assert.IsType(result); + } + + [Fact] + public void ToFormulaWithBooleanDataValue() + { + // Arrange + DataValue value = BooleanDataValue.Create(true); + + // Act + FormulaValue result = value.ToFormula(); + + // Assert + BooleanValue boolValue = Assert.IsType(result); + Assert.True(boolValue.Value); + } + + [Fact] + public void ToFormulaWithNumberDataValue() + { + // Arrange + DataValue value = NumberDataValue.Create(99.5m); + + // Act + FormulaValue result = value.ToFormula(); + + // Assert + DecimalValue decimalValue = Assert.IsType(result); + Assert.Equal(99.5m, decimalValue.Value); + } + + [Fact] + public void ToFormulaWithFloatDataValue() + { + // Arrange + DataValue value = FloatDataValue.Create(1.23); + + // Act + FormulaValue result = value.ToFormula(); + + // Assert + NumberValue numberValue = Assert.IsType(result); + Assert.Equal(1.23, numberValue.Value, precision: 2); + } + + [Fact] + public void ToFormulaWithStringDataValue() + { + // Arrange + DataValue value = StringDataValue.Create("Test"); + + // Act + FormulaValue result = value.ToFormula(); + + // Assert + StringValue stringValue = Assert.IsType(result); + Assert.Equal("Test", stringValue.Value); + } + + [Fact] + public void ToFormulaWithDateTimeDataValue() + { + // Arrange + DateTime dateTime = new(2025, 10, 17, 12, 0, 0); + DataValue value = DateTimeDataValue.Create(dateTime); + + // Act + FormulaValue result = value.ToFormula(); + + // Assert + DateTimeValue dateTimeValue = Assert.IsType(result); + Assert.Equal(dateTime, dateTimeValue.GetConvertedValue(TimeZoneInfo.Utc)); + } + + [Fact] + public void ToFormulaWithDateDataValue() + { + // Arrange + DateTime date = new(2025, 10, 17); + DataValue value = DateDataValue.Create(date); + + // Act + FormulaValue result = value.ToFormula(); + + // Assert + DateValue dateValue = Assert.IsType(result); + Assert.Equal(date, dateValue.GetConvertedValue(TimeZoneInfo.Utc)); + } + + [Fact] + public void ToFormulaWithTimeDataValue() + { + // Arrange + TimeSpan time = TimeSpan.FromHours(3); + DataValue value = TimeDataValue.Create(time); + + // Act + FormulaValue result = value.ToFormula(); + + // Assert + TimeValue timeValue = Assert.IsType(result); + Assert.Equal(time, timeValue.Value); + } + + [Fact] + public void ToFormulaWithRecordDataValue() + { + // Arrange + DataValue value = DataValue.RecordFromFields( + new KeyValuePair("Name", StringDataValue.Create("John")), + new KeyValuePair("Age", NumberDataValue.Create(30))); + + // Act + FormulaValue result = value.ToFormula(); + + // Assert + RecordValue recordValue = Assert.IsType(result, exactMatch: false); + Assert.Equal(2, recordValue.Fields.Count()); + } + + [Fact] + public void ToFormulaWithTableDataValue() + { + // Arrange + RecordDataValue record = DataValue.RecordFromFields( + new KeyValuePair("Field", StringDataValue.Create("Value"))); + DataValue value = DataValue.TableFromRecords(ImmutableArray.Create(record)); + + // Act + FormulaValue result = value.ToFormula(); + + // Assert + TableValue tableValue = Assert.IsType(result, exactMatch: false); + Assert.Single(tableValue.Rows); + } + + [Fact] + public void ToFormulaTypeWithNull() + { + // Arrange + DataValue? value = null; + + // Act + FormulaType result = value.ToFormulaType(); + + // Assert + Assert.Equal(FormulaType.Blank, result); + } + + [Fact] + public void ToFormulaTypeWithBooleanDataValue() + { + // Arrange + DataValue value = BooleanDataValue.Create(true); + + // Act + FormulaType result = value.ToFormulaType(); + + // Assert + Assert.Equal(FormulaType.Boolean, result); + } + + [Fact] + public void ToFormulaTypeWithStringDataValue() + { + // Arrange + DataValue value = StringDataValue.Create("Test"); + + // Act + FormulaType result = value.ToFormulaType(); + + // Assert + Assert.Equal(FormulaType.String, result); + } + + [Fact] + public void DataTypeToFormulaTypeWithNull() + { + // Arrange + DataType? type = null; + + // Act + FormulaType result = type.ToFormulaType(); + + // Assert + Assert.Equal(FormulaType.Blank, result); + } + + [Fact] + public void DataTypeToFormulaTypeWithBooleanDataType() + { + // Arrange + DataType type = BooleanDataType.Instance; + + // Act + FormulaType result = type.ToFormulaType(); + + // Assert + Assert.Equal(FormulaType.Boolean, result); + } + + [Fact] + public void DataTypeToFormulaTypeWithNumberDataType() + { + // Arrange + DataType type = NumberDataType.Instance; + + // Act + FormulaType result = type.ToFormulaType(); + + // Assert + Assert.Equal(FormulaType.Decimal, result); + } + + [Fact] + public void DataTypeToFormulaTypeWithFloatDataType() + { + // Arrange + DataType type = FloatDataType.Instance; + + // Act + FormulaType result = type.ToFormulaType(); + + // Assert + Assert.Equal(FormulaType.Number, result); + } + + [Fact] + public void DataTypeToFormulaTypeWithStringDataType() + { + // Arrange + DataType type = StringDataType.Instance; + + // Act + FormulaType result = type.ToFormulaType(); + + // Assert + Assert.Equal(FormulaType.String, result); + } + + [Fact] + public void DataTypeToFormulaTypeWithDateTimeDataType() + { + // Arrange + DataType type = DateTimeDataType.Instance; + + // Act + FormulaType result = type.ToFormulaType(); + + // Assert + Assert.Equal(FormulaType.DateTime, result); + } + + [Fact] + public void DataTypeToFormulaTypeWithDateDataType() + { + // Arrange + DataType type = DateDataType.Instance; + + // Act + FormulaType result = type.ToFormulaType(); + + // Assert + Assert.Equal(FormulaType.Date, result); + } + + [Fact] + public void DataTypeToFormulaTypeWithTimeDataType() + { + // Arrange + DataType type = TimeDataType.Instance; + + // Act + FormulaType result = type.ToFormulaType(); + + // Assert + Assert.Equal(FormulaType.Time, result); + } + + [Fact] + public void ToObjectWithNull() + { + // Arrange + DataValue? value = null; + + // Act + object? result = value.ToObject(); + + // Assert + Assert.Null(result); + } + + [Fact] + public void ToObjectWithBlankDataValue() + { + // Arrange + DataValue value = DataValue.Blank(); + + // Act + object? result = value.ToObject(); + + // Assert + Assert.Null(result); + } + + [Fact] + public void ToObjectWithBooleanDataValue() + { + // Arrange + DataValue value = BooleanDataValue.Create(true); + + // Act + object? result = value.ToObject(); + + // Assert + Assert.IsType(result); + Assert.True((bool)result); + } + + [Fact] + public void ToObjectWithNumberDataValue() + { + // Arrange + DataValue value = NumberDataValue.Create(42.5m); + + // Act + object? result = value.ToObject(); + + // Assert + Assert.IsType(result); + Assert.Equal(42.5m, (decimal)result); + } + + [Fact] + public void ToObjectWithStringDataValue() + { + // Arrange + DataValue value = StringDataValue.Create("Hello"); + + // Act + object? result = value.ToObject(); + + // Assert + Assert.IsType(result); + Assert.Equal("Hello", (string)result); + } + + [Fact] + public void ToClrTypeWithBooleanDataType() + { + // Arrange + DataType type = BooleanDataType.Instance; + + // Act + Type result = type.ToClrType(); + + // Assert + Assert.Equal(typeof(bool), result); + } + + [Fact] + public void ToClrTypeWithNumberDataType() + { + // Arrange + DataType type = NumberDataType.Instance; + + // Act + Type result = type.ToClrType(); + + // Assert + Assert.Equal(typeof(decimal), result); + } + + [Fact] + public void ToClrTypeWithFloatDataType() + { + // Arrange + DataType type = FloatDataType.Instance; + + // Act + Type result = type.ToClrType(); + + // Assert + Assert.Equal(typeof(double), result); + } + + [Fact] + public void ToClrTypeWithStringDataType() + { + // Arrange + DataType type = StringDataType.Instance; + + // Act + Type result = type.ToClrType(); + + // Assert + Assert.Equal(typeof(string), result); + } + + [Fact] + public void ToClrTypeWithDateTimeDataType() + { + // Arrange + DataType type = DateTimeDataType.Instance; + + // Act + Type result = type.ToClrType(); + + // Assert + Assert.Equal(typeof(DateTime), result); + } + + [Fact] + public void ToClrTypeWithTimeDataType() + { + // Arrange + DataType type = TimeDataType.Instance; + + // Act + Type result = type.ToClrType(); + + // Assert + Assert.Equal(typeof(TimeSpan), result); + } + + [Fact] + public void AsListWithNull() + { + // Arrange + DataValue? value = null; + + // Act + IList? result = value.AsList(); + + // Assert + Assert.Null(result); + } + + [Fact] + public void AsListWithBlankDataValue() + { + // Arrange + DataValue value = DataValue.Blank(); + + // Act + IList? result = value.AsList(); + + // Assert + Assert.Null(result); + } + + [Fact] + public void NewBlankWithNullDataType() + { + // Arrange + DataType? type = null; + + // Act + FormulaValue result = type.NewBlank(); + + // Assert + Assert.IsType(result); + } + + [Fact] + public void NewBlankWithBooleanDataType() + { + // Arrange + DataType type = BooleanDataType.Instance; + + // Act + FormulaValue result = type.NewBlank(); + + // Assert + Assert.IsType(result); + } + + [Fact] + public void ToRecordValueWithRecordDataValue() + { + // Arrange + RecordDataValue recordDataValue = DataValue.RecordFromFields( + new KeyValuePair("Field1", StringDataValue.Create("Value1")), + new KeyValuePair("Field2", NumberDataValue.Create(123))); + + // Act + RecordValue result = recordDataValue.ToRecordValue(); + + // Assert + Assert.NotNull(result); + Assert.Equal(2, result.Fields.Count()); + + Assert.NotNull(result.GetField("Field1")); + Assert.NotNull(result.GetField("Field2")); + } + + [Fact] + public void ToRecordTypeWithRecordDataType() + { + // Arrange + RecordDataType recordDataType = new RecordDataType.Builder + { + Properties = + { + ["Name"] = new PropertyInfo.Builder + { + Type = StringDataType.Instance + }.Build(), + ["Count"] = new PropertyInfo.Builder + { + Type = NumberDataType.Instance + }.Build() + } + }.Build(); + + // Act + RecordType result = recordDataType.ToRecordType(); + + // Assert + Assert.NotNull(result); + IEnumerable fieldTypes = result.GetFieldTypes(); + List fieldTypesList = fieldTypes.ToList(); + Assert.Equal(2, fieldTypesList.Count); + + IEnumerable fieldNames = fieldTypesList.Select(f => f.Name.Value); + Assert.Contains("Name", fieldNames); + Assert.Contains("Count", fieldNames); + + NamedFormulaType nameField = fieldTypesList.First(f => f.Name.Value == "Name"); + NamedFormulaType countField = fieldTypesList.First(f => f.Name.Value == "Count"); + Assert.Equal(FormulaType.String, nameField.Type); + Assert.Equal(FormulaType.Decimal, countField.Type); + } + + [Fact] + public void ToRecordValueWithDictionary() + { + // Arrange + IDictionary dictionary = new Dictionary + { + ["Key1"] = "Value1", + ["Key2"] = 42 + }; + + // Act + RecordDataValue result = dictionary.ToRecordValue(); + + // Assert + Assert.NotNull(result); + Assert.Equal(2, result.Properties.Count); + Assert.True(result.Properties.ContainsKey("Key1")); + Assert.True(result.Properties.ContainsKey("Key2")); + } + + [Fact] + public void ToTableValueWithEmptyEnumerable() + { + // Arrange + IEnumerable enumerable = Array.Empty(); + + // Act + TableDataValue result = enumerable.ToTableValue(); + + // Assert + Assert.NotNull(result); + Assert.Empty(result.Values); + } + + [Fact] + public void ToTableValueWithDictionaryEnumerable() + { + // Arrange + IEnumerable enumerable = new List + { + new Dictionary { ["Name"] = "Alice", ["Age"] = 30 }, + new Dictionary { ["Name"] = "Bob", ["Age"] = 25 } + }; + + // Act + TableDataValue result = enumerable.ToTableValue(); + + // Assert + Assert.NotNull(result); + } + + [Fact] + public void ToTableValueWithPrimitiveEnumerable() + { + // Arrange + IEnumerable enumerable = new List { 1, 2, 3 }; + + // Act + TableDataValue result = enumerable.ToTableValue(); + + // Assert + Assert.NotNull(result); + Assert.Equal(3, result.Values.Length); + } +} diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/Extensions/DeclarativeWorkflowOptionsExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/Extensions/DeclarativeWorkflowOptionsExtensionsTests.cs new file mode 100644 index 0000000000..85fa389283 --- /dev/null +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/Extensions/DeclarativeWorkflowOptionsExtensionsTests.cs @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.Agents.AI.Workflows.Declarative.Extensions; +using Microsoft.PowerFx; +using Moq; + +namespace Microsoft.Agents.AI.Workflows.Declarative.UnitTests.Extensions; + +public sealed class DeclarativeWorkflowOptionsExtensionsTests +{ + [Fact] + public void NullContext_UsesDefaultMaximumExpressionLength() + { + // Arrange + DeclarativeWorkflowOptions? options = null; + + // Act + RecalcEngine engine = options.CreateRecalcEngine(); + + // Assert + Assert.NotNull(engine); + Assert.Equal(10000, engine.Config.MaximumExpressionLength); + } + + [Fact] + public void OptionsWithoutLimits_UsesDefaults() + { + // Arrange + DeclarativeWorkflowOptions options = CreateOptions(); + + // Act + RecalcEngine engine = options.CreateRecalcEngine(); + + // Assert + Assert.NotNull(engine); + Assert.Equal(10000, engine.Config.MaximumExpressionLength); + Assert.True(engine.Config.MaxCallDepth >= 0); + } + + [Fact] + public void OptionsWithBothLimits() + { + // Arrange + const int ExpectedLength = 5000; + const int ExpectedDepth = 12; + DeclarativeWorkflowOptions context = CreateOptions(ExpectedLength, ExpectedDepth); + + // Act + RecalcEngine engine = context.CreateRecalcEngine(); + + // Assert + Assert.Equal(ExpectedLength, engine.Config.MaximumExpressionLength); + Assert.Equal(ExpectedDepth, engine.Config.MaxCallDepth); + } + + // Factory for creating options and mock provider + private static DeclarativeWorkflowOptions CreateOptions( + int? maximumExpressionLength = null, + int? maximumCallDepth = null) + { + Mock providerMock = new(MockBehavior.Strict); + return + new(providerMock.Object) + { + MaximumExpressionLength = maximumExpressionLength, + MaximumCallDepth = maximumCallDepth + }; + } +} diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/Extensions/DialogBaseExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/Extensions/DialogBaseExtensionsTests.cs new file mode 100644 index 0000000000..6d085f6a01 --- /dev/null +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/Extensions/DialogBaseExtensionsTests.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.Agents.AI.Workflows.Declarative.Extensions; +using Microsoft.Bot.ObjectModel; + +namespace Microsoft.Agents.AI.Workflows.Declarative.UnitTests.Extensions; + +/// +/// Tests for . +/// +public sealed class DialogBaseExtensionsTests +{ + [Fact] + public void WrapWithBotCreatesValidBotDefinition() + { + // Arrange + AdaptiveDialog dialog = new AdaptiveDialog.Builder() + { + BeginDialog = new OnActivity.Builder() + { + Id = "test_dialog", + }, + }.Build(); + + // Assert + Assert.False(dialog.HasSchemaName); + + // Act + AdaptiveDialog wrappedDialog = dialog.WrapWithBot(); + + // Assert + VerifyWrappedDialog(wrappedDialog); + + // Act & Assert + VerifyWrappedDialog(wrappedDialog.WrapWithBot()); + } + + private static void VerifyWrappedDialog(AdaptiveDialog wrappedDialog) + { + Assert.NotNull(wrappedDialog); + Assert.NotNull(wrappedDialog.BeginDialog); + Assert.Equal("test_dialog", wrappedDialog.BeginDialog.Id); + Assert.True(wrappedDialog.HasSchemaName); + } +} diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/Extensions/ExpandoObjectExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/Extensions/ExpandoObjectExtensionsTests.cs new file mode 100644 index 0000000000..51d3e87962 --- /dev/null +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/Extensions/ExpandoObjectExtensionsTests.cs @@ -0,0 +1,217 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Collections.Generic; +using System.Dynamic; +using System.Linq; +using Microsoft.Agents.AI.Workflows.Declarative.Extensions; +using Microsoft.PowerFx.Types; + +namespace Microsoft.Agents.AI.Workflows.Declarative.UnitTests.Extensions; + +public sealed class ExpandoObjectExtensionsTests +{ + [Fact] + public void ToRecordTypeWithEmptyExpandoObject() + { + // Arrange + ExpandoObject expando = new(); + + // Act + RecordType recordType = expando.ToRecordType(); + + // Assert + Assert.NotNull(recordType); + Assert.Empty(recordType.GetFieldTypes()); + } + + [Fact] + public void ToRecordTypeWithStringProperty() + { + // Arrange + dynamic expando = new ExpandoObject(); + expando.Name = "John Doe"; + + // Act + RecordType recordType = ((ExpandoObject)expando).ToRecordType(); + + // Assert + Assert.NotNull(recordType); + IEnumerable fieldTypes = recordType.GetFieldTypes(); + Assert.Single(fieldTypes); + NamedFormulaType field = fieldTypes.First(); + Assert.Equal("Name", field.Name.Value); + Assert.Equal(FormulaType.String, field.Type); + } + + [Fact] + public void ToRecordTypeWithMultipleProperties() + { + // Arrange + dynamic expando = new ExpandoObject(); + expando.Name = "Alice"; + expando.Age = 30; + expando.IsActive = true; + + // Act + RecordType recordType = ((ExpandoObject)expando).ToRecordType(); + + // Assert + Assert.NotNull(recordType); + IEnumerable fieldTypes = recordType.GetFieldTypes(); + Assert.Equal(3, fieldTypes.Count()); + IEnumerable fieldNames = fieldTypes.Select(f => f.Name.Value); + Assert.Contains("Name", fieldNames); + Assert.Contains("Age", fieldNames); + Assert.Contains("IsActive", fieldNames); + } + + [Fact] + public void ToRecordTypeWithNullProperty() + { + // Arrange + dynamic expando = new ExpandoObject(); + expando.Name = "Test"; + expando.NullValue = null; + + // Act + RecordType recordType = ((ExpandoObject)expando).ToRecordType(); + + // Assert + Assert.NotNull(recordType); + IEnumerable fieldTypes = recordType.GetFieldTypes(); + Assert.Equal(2, fieldTypes.Count()); + IEnumerable fieldNames = fieldTypes.Select(f => f.Name.Value); + Assert.Contains("Name", fieldNames); + Assert.Contains("NullValue", fieldNames); + } + + [Fact] + public void ToRecordWithEmptyExpandoObject() + { + // Arrange + ExpandoObject expando = new(); + + // Act + RecordValue recordValue = expando.ToRecord(); + + // Assert + Assert.NotNull(recordValue); + Assert.Empty(recordValue.Fields); + } + + [Fact] + public void ToRecordWithStringProperty() + { + // Arrange + dynamic expando = new ExpandoObject(); + expando.Message = "Hello World"; + + // Act + RecordValue recordValue = ((ExpandoObject)expando).ToRecord(); + + // Assert + Assert.NotNull(recordValue); + Assert.Single(recordValue.Fields); + NamedValue field = recordValue.Fields.First(); + Assert.Equal("Message", field.Name); + StringValue stringValue = Assert.IsType(field.Value); + Assert.Equal("Hello World", stringValue.Value); + } + + [Fact] + public void ToRecordWithMultiplePropertiesOfDifferentTypes() + { + // Arrange + dynamic expando = new ExpandoObject(); + expando.Name = "Bob"; + expando.Count = 42; + expando.Active = true; + + // Act + RecordValue recordValue = ((ExpandoObject)expando).ToRecord(); + + // Assert + Assert.NotNull(recordValue); + Assert.Equal(3, recordValue.Fields.Count()); + + FormulaValue nameField = recordValue.GetField("Name"); + StringValue nameValue = Assert.IsType(nameField); + Assert.Equal("Bob", nameValue.Value); + + FormulaValue countField = recordValue.GetField("Count"); + DecimalValue countValue = Assert.IsType(countField); + Assert.Equal(42, countValue.Value); + + FormulaValue activeField = recordValue.GetField("Active"); + BooleanValue activeValue = Assert.IsType(activeField); + Assert.True(activeValue.Value); + } + + [Fact] + public void ToRecordWithNestedExpandoObject() + { + // Arrange + dynamic nested = new ExpandoObject(); + nested.InnerValue = "Inner"; + + dynamic expando = new ExpandoObject(); + expando.Outer = "Outer"; + expando.Nested = nested; + + // Act + RecordValue recordValue = ((ExpandoObject)expando).ToRecord(); + + // Assert + Assert.NotNull(recordValue); + Assert.Equal(2, recordValue.Fields.Count()); + + Assert.NotNull(recordValue.GetField("Outer")); + FormulaValue nestedField = recordValue.GetField("Nested"); + Assert.NotNull(nestedField); + + RecordValue nestedRecord = Assert.IsType(nestedField, exactMatch: false); + Assert.Single(nestedRecord.Fields); + } + + [Fact] + public void ToRecordWithNullProperty() + { + // Arrange + dynamic expando = new ExpandoObject(); + expando.Name = "Test"; + expando.NullValue = null; + + // Act + RecordValue recordValue = ((ExpandoObject)expando).ToRecord(); + + // Assert + Assert.NotNull(recordValue); + Assert.Equal(2, recordValue.Fields.Count()); + + FormulaValue nullField = recordValue.GetField("NullValue"); + Assert.IsType(nullField); + } + + [Fact] + public void ToRecordTypeAndToRecordAreConsistent() + { + // Arrange + dynamic expando = new ExpandoObject(); + expando.StringField = "Value"; + expando.IntField = 123; + expando.BoolField = false; + + // Act + RecordType recordType = ((ExpandoObject)expando).ToRecordType(); + RecordValue recordValue = ((ExpandoObject)expando).ToRecord(); + + // Assert + List fieldTypesList = recordType.GetFieldTypes().ToList(); + Assert.Equal(fieldTypesList.Count, recordValue.Fields.Count()); + + foreach (NamedFormulaType fieldType in fieldTypesList) + { + Assert.Contains(recordValue.Fields, f => f.Name == fieldType.Name.Value); + } + } +} diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/Extensions/TemplateExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/Extensions/TemplateExtensionsTests.cs new file mode 100644 index 0000000000..86591587c2 --- /dev/null +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/Extensions/TemplateExtensionsTests.cs @@ -0,0 +1,141 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Collections.Generic; +using Microsoft.Agents.AI.Workflows.Declarative.Extensions; +using Microsoft.Bot.ObjectModel; +using Microsoft.PowerFx; + +namespace Microsoft.Agents.AI.Workflows.Declarative.UnitTests.Extensions; + +public sealed class TemplateExtensionsTests +{ + [Fact] + public void FormatTemplateWithTextSegments() + { + // Arrange + RecalcEngine engine = new(); + IEnumerable template = new List + { + new TemplateLine.Builder + { + Segments = + { + new TextSegment.Builder { Value = "Hello " }, + new TextSegment.Builder { Value = "World" } + } + }.Build() + }; + + // Act + string result = engine.Format(template); + + // Assert + Assert.Equal("Hello World", result); + } + + [Fact] + public void FormatTemplateWithMultipleLines() + { + // Arrange + RecalcEngine engine = new(); + IEnumerable template = new List + { + new TemplateLine.Builder + { + Segments = + { + new TextSegment.Builder { Value = "Line 1" } + } + }.Build(), + new TemplateLine.Builder + { + Segments = + { + new TextSegment.Builder { Value = "Line 2" } + } + }.Build() + }; + + // Act + string result = engine.Format(template); + + // Assert + Assert.Equal("Line 1Line 2", result); + } + + [Fact] + public void FormatSingleTemplateLineWithNullValue() + { + // Arrange + RecalcEngine engine = new(); + TemplateLine? line = null; + + // Act + string result = engine.Format(line); + + // Assert + Assert.Equal(string.Empty, result); + } + + [Fact] + public void FormatSingleTemplateLineWithTextSegment() + { + // Arrange + RecalcEngine engine = new(); + TemplateLine line = new TemplateLine.Builder + { + Segments = + { + new TextSegment.Builder { Value = "Test" } + } + }.Build(); + + // Act + string result = engine.Format(line); + + // Assert + Assert.Equal("Test", result); + } + + [Fact] + public void FormatTextSegmentWithNullValue() + { + // Arrange + RecalcEngine engine = new(); + TextSegment segment = new TextSegment.Builder { Value = null }.Build(); + + // Act + string result = engine.Format(segment); + + // Assert + Assert.Equal(string.Empty, result); + } + + [Fact] + public void FormatTextSegmentWithEmptyValue() + { + // Arrange + RecalcEngine engine = new(); + TextSegment segment = new TextSegment.Builder { Value = "" }.Build(); + + // Act + string result = engine.Format(segment); + + // Assert + Assert.Equal(string.Empty, result); + } + + [Fact] + public void FormatTextSegmentWithValue() + { + // Arrange + RecalcEngine engine = new(); + TextSegment segment = new TextSegment.Builder { Value = "Hello World" }.Build(); + + // Act + string result = engine.Format(segment); + + // Assert + Assert.Equal("Hello World", result); + } +} diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/PowerFx/Functions/UserMessageTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/PowerFx/Functions/UserMessageTests.cs new file mode 100644 index 0000000000..712fb139bf --- /dev/null +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/PowerFx/Functions/UserMessageTests.cs @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Collections.Generic; +using System.Linq; +using Microsoft.Agents.AI.Workflows.Declarative.PowerFx; +using Microsoft.Agents.AI.Workflows.Declarative.PowerFx.Functions; +using Microsoft.Extensions.AI; +using Microsoft.PowerFx.Types; + +namespace Microsoft.Agents.AI.Workflows.Declarative.UnitTests.PowerFx.Functions; + +public class UserMessageTests +{ + [Fact] + public void Construct_Function() + { + UserMessage function = new(); + Assert.NotNull(function); + } + + [Fact] + public void Execute_ReturnsBlank_ForEmptyInput() + { + // Arrange + FormulaValue sourceValue = FormulaValue.New(string.Empty); + StringValue stringValue = Assert.IsType(sourceValue); + + // Act + FormulaValue result = UserMessage.Execute(stringValue); + + // Assert + Assert.IsType(result); + } + + [Fact] + public void Execute_ReturnsExpectedRecord_ForNonEmptyInput() + { + const string Text = "Hello"; + FormulaValue sourceValue = FormulaValue.New(Text); + StringValue stringValue = Assert.IsType(sourceValue); + + FormulaValue result = UserMessage.Execute(stringValue); + + RecordValue recordResult = Assert.IsType(result, exactMatch: false); + + // Discriminator + FormulaValue discriminator = recordResult.GetField(TypeSchema.Discriminator); + StringValue discriminatorValue = Assert.IsType(discriminator); + Assert.Equal(nameof(ChatMessage), discriminatorValue.Value); + + // Role + FormulaValue role = recordResult.GetField(TypeSchema.Message.Fields.Role); + StringValue roleValue = Assert.IsType(role); + Assert.Equal(ChatRole.User.Value, roleValue.Value); + + // Content table + FormulaValue content = recordResult.GetField(TypeSchema.Message.Fields.Content); + TableValue table = Assert.IsType(content, exactMatch: false); + + List rows = table.Rows.Select(value => value.Value).ToList(); + Assert.Single(rows); + + StringValue contentType = Assert.IsType(rows[0].GetField(TypeSchema.Message.Fields.ContentType)); + Assert.Equal(TypeSchema.Message.ContentTypes.Text, contentType.Value); + + StringValue contentValue = Assert.IsType(rows[0].GetField(TypeSchema.Message.Fields.ContentValue)); + Assert.Equal(Text, contentValue.Value); + } +}