test
Some checks failed
CodeQL / Analyze (csharp) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
dotnet-build-and-test / paths-filter (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test (Debug, windows-latest, net9.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test (Release, integration, true, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test (Release, integration, true, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test (Release, ubuntu-latest, net8.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test-check (push) Has been cancelled
Python - Merge - Tests / paths-filter (push) Has been cancelled
Python - Merge - Tests / Python Tests - Core (integration, ubuntu-latest, 3.10) (push) Has been cancelled
Python - Merge - Tests / Python Tests - Azure AI (integration, ubuntu-latest, 3.10) (push) Has been cancelled
Python - Merge - Tests / python-integration-tests-check (push) Has been cancelled
Python - Lab Tests / paths-filter (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (ubuntu-latest, 3.10) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (ubuntu-latest, 3.11) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (ubuntu-latest, 3.12) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (ubuntu-latest, 3.13) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (ubuntu-latest, 3.14) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (windows-latest, 3.10) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (windows-latest, 3.11) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (windows-latest, 3.12) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (windows-latest, 3.13) (push) Has been cancelled
Python - Lab Tests / Python Lab Tests (windows-latest, 3.14) (push) Has been cancelled
Check .md links / markdown-link-check (push) Has been cancelled

This commit is contained in:
2026-01-24 03:05:12 +11:00
parent f78f2388b3
commit 539852f81c
2584 changed files with 287471 additions and 0 deletions

View File

@@ -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<StringValue>(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<StringValue>(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<ChatMessage> 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<ChatMessage> messages = [];
// Act
TableValue result = messages.ToTable();
// Assert
Assert.NotNull(result);
Assert.Empty(result.Rows);
}
[Fact]
public void ToChatMessagesWithNull()
{
// Arrange
DataValue? value = null;
// Act
IEnumerable<ChatMessage>? result = value.ToChatMessages();
// Assert
Assert.Null(result);
}
[Fact]
public void ToChatMessagesWithBlankDataValue()
{
// Arrange
DataValue value = DataValue.Blank();
// Act
IEnumerable<ChatMessage>? result = value.ToChatMessages();
// Assert
Assert.Null(result);
}
[Fact]
public void ToChatMessagesWithStringDataValue()
{
// Arrange
DataValue value = StringDataValue.Create("Hello");
// Act
IEnumerable<ChatMessage>? 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<ChatMessage>? 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<ChatMessage>? 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<ChatMessage>? 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<ChatMessage>? 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<DeclarativeActionException>(() => value.ToChatMessage());
}
[Fact]
public void ToChatMessageFromRecordDataValue()
{
// Arrange
// Note: Use "Agent" not "Assistant" - AgentMessageRole.Agent maps to ChatRole.Assistant
RecordDataValue record = DataValue.RecordFromFields(
new KeyValuePair<string, DataValue>(TypeSchema.Message.Fields.Role, StringDataValue.Create("Agent")),
new KeyValuePair<string, DataValue>(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<UriContent>(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<DataContent>(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<HostedFileContent>(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<TableDataValue>(contentValue, exactMatch: false);
RecordDataValue badContent = DataValue.RecordFromFields(
new KeyValuePair<string, DataValue>(TypeSchema.Message.Fields.ContentType, StringDataValue.Create(TypeSchema.Message.ContentTypes.Text)),
new KeyValuePair<string, DataValue>(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<string, DataValue>("key1", StringDataValue.Create("value1")),
new KeyValuePair<string, DataValue>("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<TextContent>(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<UriContent>(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<DataContent>(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<HostedFileContent>(result);
Assert.Equal("file-id-123", fileContent.FileId);
}
[Fact]
public void ToChatMessageFromFunctionResultContents()
{
// Arrange
IEnumerable<FunctionResultContent> 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<ChatMessage> 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<string, DataValue>(TypeSchema.Message.Fields.Role, StringDataValue.Create("User")),
new KeyValuePair<string, DataValue>(TypeSchema.Message.Fields.Content, DataValue.EmptyTable));
RecordDataValue record2 = DataValue.RecordFromFields(
new KeyValuePair<string, DataValue>(TypeSchema.Message.Fields.Role, StringDataValue.Create("Assistant")),
new KeyValuePair<string, DataValue>(TypeSchema.Message.Fields.Content, DataValue.EmptyTable));
TableDataValue table = DataValue.TableFromRecords(record1, record2);
// Act
IEnumerable<ChatMessage> result = table.ToChatMessages();
// Assert
Assert.NotNull(result);
Assert.Equal(2, result.Count());
}
[Fact]
public void ToChatMessagesFromTableDataValueWithSingleColumnRecords()
{
// Arrange
RecordDataValue innerRecord = DataValue.RecordFromFields(
new KeyValuePair<string, DataValue>(TypeSchema.Message.Fields.Role, StringDataValue.Create("User")),
new KeyValuePair<string, DataValue>(TypeSchema.Message.Fields.Content, DataValue.EmptyTable));
RecordDataValue wrappedRecord = DataValue.RecordFromFields(
new KeyValuePair<string, DataValue>("Value", innerRecord));
TableDataValue table = DataValue.TableFromRecords(wrappedRecord);
// Act
IEnumerable<ChatMessage> 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<TableValue>(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<TableValue>(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<TableValue>(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<RecordValue>(metadataField, exactMatch: false);
Assert.Equal(2, metadataRecord.Fields.Count());
}
}

View File

@@ -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<BlankDataValue>(result);
}
[Fact]
public void ToDataValueWithUnassignedValue()
{
// Arrange
object value = UnassignedValue.Instance;
// Act
DataValue result = value.ToDataValue();
// Assert
Assert.IsType<BlankDataValue>(result);
}
[Fact]
public void ToDataValueWithBooleanTrue()
{
// Arrange
const bool Value = true;
// Act
DataValue result = Value.ToDataValue();
// Assert
BooleanDataValue boolValue = Assert.IsType<BooleanDataValue>(result);
Assert.True(boolValue.Value);
}
[Fact]
public void ToDataValueWithBooleanFalse()
{
// Arrange
const bool Value = false;
// Act
DataValue result = Value.ToDataValue();
// Assert
BooleanDataValue boolValue = Assert.IsType<BooleanDataValue>(result);
Assert.False(boolValue.Value);
}
[Fact]
public void ToDataValueWithInt()
{
// Arrange
const int Value = 42;
// Act
DataValue result = Value.ToDataValue();
// Assert
NumberDataValue numberValue = Assert.IsType<NumberDataValue>(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<NumberDataValue>(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<FloatDataValue>(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<NumberDataValue>(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<FloatDataValue>(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<StringDataValue>(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<DateDataValue>(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<DateTimeDataValue>(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<TimeDataValue>(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<NumberDataValue>(result);
Assert.Equal(123, numberValue.Value);
}
[Fact]
public void ToFormulaWithNull()
{
// Arrange
DataValue? value = null;
// Act
FormulaValue result = value.ToFormula();
// Assert
Assert.IsType<BlankValue>(result);
}
[Fact]
public void ToFormulaWithBlankDataValue()
{
// Arrange
DataValue value = DataValue.Blank();
// Act
FormulaValue result = value.ToFormula();
// Assert
Assert.IsType<BlankValue>(result);
}
[Fact]
public void ToFormulaWithBooleanDataValue()
{
// Arrange
DataValue value = BooleanDataValue.Create(true);
// Act
FormulaValue result = value.ToFormula();
// Assert
BooleanValue boolValue = Assert.IsType<BooleanValue>(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<DecimalValue>(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<NumberValue>(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<StringValue>(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<DateTimeValue>(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<DateValue>(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<TimeValue>(result);
Assert.Equal(time, timeValue.Value);
}
[Fact]
public void ToFormulaWithRecordDataValue()
{
// Arrange
DataValue value = DataValue.RecordFromFields(
new KeyValuePair<string, DataValue>("Name", StringDataValue.Create("John")),
new KeyValuePair<string, DataValue>("Age", NumberDataValue.Create(30)));
// Act
FormulaValue result = value.ToFormula();
// Assert
RecordValue recordValue = Assert.IsType<RecordValue>(result, exactMatch: false);
Assert.Equal(2, recordValue.Fields.Count());
}
[Fact]
public void ToFormulaWithTableDataValue()
{
// Arrange
RecordDataValue record = DataValue.RecordFromFields(
new KeyValuePair<string, DataValue>("Field", StringDataValue.Create("Value")));
DataValue value = DataValue.TableFromRecords(ImmutableArray.Create(record));
// Act
FormulaValue result = value.ToFormula();
// Assert
TableValue tableValue = Assert.IsType<TableValue>(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<bool>(result);
Assert.True((bool)result);
}
[Fact]
public void ToObjectWithNumberDataValue()
{
// Arrange
DataValue value = NumberDataValue.Create(42.5m);
// Act
object? result = value.ToObject();
// Assert
Assert.IsType<decimal>(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<string>(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<string>? result = value.AsList<string>();
// Assert
Assert.Null(result);
}
[Fact]
public void AsListWithBlankDataValue()
{
// Arrange
DataValue value = DataValue.Blank();
// Act
IList<string>? result = value.AsList<string>();
// Assert
Assert.Null(result);
}
[Fact]
public void NewBlankWithNullDataType()
{
// Arrange
DataType? type = null;
// Act
FormulaValue result = type.NewBlank();
// Assert
Assert.IsType<BlankValue>(result);
}
[Fact]
public void NewBlankWithBooleanDataType()
{
// Arrange
DataType type = BooleanDataType.Instance;
// Act
FormulaValue result = type.NewBlank();
// Assert
Assert.IsType<BlankValue>(result);
}
[Fact]
public void ToRecordValueWithRecordDataValue()
{
// Arrange
RecordDataValue recordDataValue = DataValue.RecordFromFields(
new KeyValuePair<string, DataValue>("Field1", StringDataValue.Create("Value1")),
new KeyValuePair<string, DataValue>("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<NamedFormulaType> fieldTypes = result.GetFieldTypes();
List<NamedFormulaType> fieldTypesList = fieldTypes.ToList();
Assert.Equal(2, fieldTypesList.Count);
IEnumerable<string> 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<string, object>
{
["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<object>();
// Act
TableDataValue result = enumerable.ToTableValue();
// Assert
Assert.NotNull(result);
Assert.Empty(result.Values);
}
[Fact]
public void ToTableValueWithDictionaryEnumerable()
{
// Arrange
IEnumerable enumerable = new List<IDictionary>
{
new Dictionary<string, object> { ["Name"] = "Alice", ["Age"] = 30 },
new Dictionary<string, object> { ["Name"] = "Bob", ["Age"] = 25 }
};
// Act
TableDataValue result = enumerable.ToTableValue();
// Assert
Assert.NotNull(result);
}
[Fact]
public void ToTableValueWithPrimitiveEnumerable()
{
// Arrange
IEnumerable enumerable = new List<int> { 1, 2, 3 };
// Act
TableDataValue result = enumerable.ToTableValue();
// Assert
Assert.NotNull(result);
Assert.Equal(3, result.Values.Length);
}
}

View File

@@ -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<WorkflowAgentProvider> providerMock = new(MockBehavior.Strict);
return
new(providerMock.Object)
{
MaximumExpressionLength = maximumExpressionLength,
MaximumCallDepth = maximumCallDepth
};
}
}

View File

@@ -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;
/// <summary>
/// Tests for <see cref="DialogBaseExtensions"/>.
/// </summary>
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);
}
}

View File

@@ -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<NamedFormulaType> 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<NamedFormulaType> fieldTypes = recordType.GetFieldTypes();
Assert.Equal(3, fieldTypes.Count());
IEnumerable<string> 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<NamedFormulaType> fieldTypes = recordType.GetFieldTypes();
Assert.Equal(2, fieldTypes.Count());
IEnumerable<string> 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<StringValue>(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<StringValue>(nameField);
Assert.Equal("Bob", nameValue.Value);
FormulaValue countField = recordValue.GetField("Count");
DecimalValue countValue = Assert.IsType<DecimalValue>(countField);
Assert.Equal(42, countValue.Value);
FormulaValue activeField = recordValue.GetField("Active");
BooleanValue activeValue = Assert.IsType<BooleanValue>(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<RecordValue>(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<BlankValue>(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<NamedFormulaType> 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);
}
}
}

View File

@@ -0,0 +1,215 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Agents.AI.Workflows.Declarative.Extensions;
using Microsoft.Bot.ObjectModel;
using Microsoft.PowerFx.Types;
namespace Microsoft.Agents.AI.Workflows.Declarative.UnitTests.Extensions;
public class FormulaValueExtensionsTests
{
[Fact]
public void BooleanValue()
{
BooleanValue formulaValue = FormulaValue.New(true);
DataValue dataValue = formulaValue.ToDataValue();
BooleanDataValue typedValue = Assert.IsType<BooleanDataValue>(dataValue);
Assert.Equal(formulaValue.Value, typedValue.Value);
BooleanValue formulaCopy = Assert.IsType<BooleanValue>(dataValue.ToFormula());
Assert.Equal(typedValue.Value, formulaCopy.Value);
Assert.Equal(bool.TrueString, formulaValue.Format());
}
[Fact]
public void StringValues()
{
StringValue formulaValue = FormulaValue.New("test value");
Assert.Equal(StringDataType.Instance, formulaValue.GetDataType());
DataValue dataValue = formulaValue.ToDataValue();
StringDataValue typedValue = Assert.IsType<StringDataValue>(dataValue);
Assert.Equal(formulaValue.Value, typedValue.Value);
StringValue formulaCopy = Assert.IsType<StringValue>(typedValue.ToFormula());
Assert.Equal(typedValue.Value, formulaCopy.Value);
Assert.Equal(formulaValue.Value, formulaValue.Format());
}
[Fact]
public void DecimalValues()
{
DecimalValue formulaValue = FormulaValue.New(45.3m);
Assert.Equal(NumberDataType.Instance, formulaValue.GetDataType());
DataValue dataValue = formulaValue.ToDataValue();
NumberDataValue typedValue = Assert.IsType<NumberDataValue>(dataValue);
Assert.Equal(formulaValue.Value, typedValue.Value);
DecimalValue formulaCopy = Assert.IsType<DecimalValue>(typedValue.ToFormula());
Assert.Equal(typedValue.Value, formulaCopy.Value);
Assert.Equal("45.3", formulaValue.Format());
}
[Fact]
public void NumberValues()
{
NumberValue formulaValue = FormulaValue.New(3.1415926535897);
Assert.Equal(FloatDataType.Instance, formulaValue.GetDataType());
DataValue dataValue = formulaValue.ToDataValue();
FloatDataValue typedValue = Assert.IsType<FloatDataValue>(dataValue);
Assert.Equal(formulaValue.Value, typedValue.Value);
NumberValue formulaCopy = Assert.IsType<NumberValue>(typedValue.ToFormula());
Assert.Equal(typedValue.Value, formulaCopy.Value);
Assert.Equal("3.1415926535897", formulaValue.Format());
}
[Fact]
public void BlankValues()
{
BlankValue formulaValue = FormulaValue.NewBlank();
Assert.Equal(DataType.Blank, formulaValue.GetDataType());
Assert.IsType<BlankDataValue>(formulaValue.ToDataValue());
Assert.Equal(string.Empty, formulaValue.Format());
}
[Fact]
public void VoidValues()
{
VoidValue formulaValue = FormulaValue.NewVoid();
Assert.Equal(DataType.Unspecified, formulaValue.GetDataType());
Assert.IsType<BlankDataValue>(formulaValue.ToDataValue());
}
[Fact]
public void DateValues()
{
DateTime timestamp = DateTime.UtcNow.Date;
DateValue formulaValue = FormulaValue.NewDateOnly(timestamp);
Assert.Equal(DataType.Date, formulaValue.GetDataType());
DataValue dataValue = formulaValue.ToDataValue();
DateDataValue typedValue = Assert.IsType<DateDataValue>(dataValue);
Assert.Equal(formulaValue.GetConvertedValue(TimeZoneInfo.Utc), typedValue.Value);
DateValue formulaCopy = Assert.IsType<DateValue>(dataValue.ToFormula());
Assert.Equal(typedValue.Value, formulaCopy.GetConvertedValue(TimeZoneInfo.Utc));
Assert.Equal($"{timestamp}", formulaValue.Format());
}
[Fact]
public void DateTimeValues()
{
DateTime timestamp = DateTime.UtcNow;
DateTimeValue formulaValue = FormulaValue.New(timestamp);
Assert.Equal(DataType.DateTime, formulaValue.GetDataType());
DataValue dataValue = formulaValue.ToDataValue();
DateTimeDataValue typedValue = Assert.IsType<DateTimeDataValue>(dataValue);
Assert.Equal(formulaValue.GetConvertedValue(TimeZoneInfo.Utc), typedValue.Value);
DateTimeValue formulaCopy = Assert.IsType<DateTimeValue>(typedValue.ToFormula());
Assert.Equal(typedValue.Value, formulaCopy.GetConvertedValue(TimeZoneInfo.Utc));
Assert.Equal($"{timestamp}", formulaValue.Format());
}
[Fact]
public void TimeValues()
{
TimeValue formulaValue = FormulaValue.New(TimeSpan.Parse("10:35"));
Assert.Equal(DataType.Time, formulaValue.GetDataType());
DataValue dataValue = formulaValue.ToDataValue();
TimeDataValue typedValue = Assert.IsType<TimeDataValue>(dataValue);
Assert.Equal(formulaValue.Value, typedValue.Value);
TimeValue formulaCopy = Assert.IsType<TimeValue>(typedValue.ToFormula());
Assert.Equal(typedValue.Value, formulaCopy.Value);
Assert.Equal("10:35:00", formulaValue.Format());
}
[Fact]
public void RecordValues()
{
RecordValue formulaValue = FormulaValue.NewRecordFromFields(
new NamedValue("FieldA", FormulaValue.New("Value1")),
new NamedValue("FieldB", FormulaValue.New("Value2")),
new NamedValue("FieldC", FormulaValue.New("Value3")));
Assert.Equal(DataType.EmptyRecord, formulaValue.GetDataType());
RecordDataValue dataValue = formulaValue.ToRecord();
Assert.Equal(formulaValue.Fields.Count(), dataValue.Properties.Count);
foreach (KeyValuePair<string, DataValue> property in dataValue.Properties)
{
Assert.Contains(property.Key, formulaValue.Fields.Select(field => field.Name));
}
RecordValue formulaCopy = Assert.IsType<RecordValue>(dataValue.ToFormula(), exactMatch: false);
Assert.Equal(formulaCopy.Fields.Count(), dataValue.Properties.Count);
foreach (NamedValue field in formulaCopy.Fields)
{
Assert.Contains(field.Name, dataValue.Properties.Keys);
}
Assert.Equal(
"""
{
"FieldA": "Value1",
"FieldB": "Value2",
"FieldC": "Value3"
}
""",
formulaValue.Format().Replace(Environment.NewLine, "\n"));
Dictionary<string, int> source =
new()
{
["FieldA"] = 1,
["FieldB"] = 2,
["FieldC"] = 3
};
FormulaValue formula = source.ToFormula();
Assert.IsType<RecordValue>(formula, exactMatch: false);
}
[Fact]
public void TableValues()
{
RecordValue recordValue = FormulaValue.NewRecordFromFields(
new NamedValue("FieldA", FormulaValue.New("Value1")),
new NamedValue("FieldB", FormulaValue.New("Value2")),
new NamedValue("FieldC", FormulaValue.New("Value3")));
TableValue formulaValue = FormulaValue.NewTable(recordValue.Type, [recordValue]);
TableDataValue dataValue = formulaValue.ToTable();
Assert.Equal(formulaValue.Rows.Count(), dataValue.Values.Length);
TableValue formulaCopy = Assert.IsType<TableValue>(dataValue.ToFormula(), exactMatch: false);
Assert.Equal(formulaCopy.Rows.Count(), dataValue.Values.Length);
Assert.Equal(
"""
[
{
"FieldA": "Value1",
"FieldB": "Value2",
"FieldC": "Value3"
}
]
""",
formulaValue.Format().Replace(Environment.NewLine, "\n"));
}
}

View File

@@ -0,0 +1,387 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using System.Text.Json;
using Microsoft.Agents.AI.Workflows.Declarative.Extensions;
using Microsoft.Agents.AI.Workflows.Declarative.Kit;
namespace Microsoft.Agents.AI.Workflows.Declarative.UnitTests.Extensions;
public sealed class JsonDocumentExtensionsTests
{
[Fact]
public void ParseRecord_Object_PrimitiveFields_Succeeds()
{
// Arrange
VariableType recordType =
VariableType.Record(
[
("text", typeof(string)),
("numberInt", typeof(int)),
("numberLong", typeof(long)),
("numberDecimal", typeof(decimal)),
("numberDouble", typeof(double)),
("flag", typeof(bool)),
("date", typeof(DateTime)),
("time", typeof(TimeSpan))
]);
DateTime expectedDateTime = new(2024, 10, 01, 12, 34, 56, DateTimeKind.Utc);
TimeSpan expectedTimeSpan = new(12, 34, 56);
JsonDocument document = JsonDocument.Parse(
"""
{
"text": "hello",
"numberInt": 7,
"numberLong": 9223372036854775807,
"numberDecimal": 12.5,
"numberDouble": 3.99E99,
"flag": true,
"date": "2024-10-01T12:34:56Z",
"time": "12:34:56"
}
""");
// Act
Dictionary<string, object?> result = document.ParseRecord(recordType);
// Assert
Assert.Equal("hello", result["text"]);
Assert.Equal(7, result["numberInt"]);
Assert.Equal(9223372036854775807L, result["numberLong"]);
Assert.Equal(12.5m, result["numberDecimal"]);
Assert.Equal(3.99E99, result["numberDouble"]);
Assert.Equal(true, result["flag"]);
Assert.Equal(expectedDateTime, result["date"]);
Assert.Equal(expectedTimeSpan, result["time"]);
}
[Fact]
public void ParseRecord_Object_NoSchema_Succeeds()
{
// Arrange
JsonDocument document = JsonDocument.Parse(
"""
{
"text": "hello",
"numberInt": 7,
"numberLong": 9223372036854775807,
"numberDecimal": 12.5,
"numberDouble": 3.99E99,
"flag": true,
"date": "2024-10-01T12:34:56Z",
"time": "12:34:56"
}
""");
// Act
Dictionary<string, object?> result = document.ParseRecord(VariableType.RecordType);
// Assert
Assert.Equal("hello", result["text"]);
Assert.Equal(7, result["numberInt"]);
Assert.Equal(9223372036854775807L, result["numberLong"]);
Assert.Equal(12.5m, result["numberDecimal"]);
Assert.Equal(3.99E99, result["numberDouble"]);
Assert.Equal(true, result["flag"]);
Assert.Equal("2024-10-01T12:34:56Z", result["date"]);
Assert.Equal("12:34:56", result["time"]);
}
[Fact]
public void ParseRecord_Object_NestedRecord_Succeeds()
{
// Arrange
VariableType innerRecord =
VariableType.Record(
[
("innerText", typeof(string)),
("innerNumber", typeof(int))
]);
VariableType outerRecord =
VariableType.Record(
[
("outerText", typeof(string)),
("nested", innerRecord)
]);
JsonDocument document = JsonDocument.Parse(
"""
{
"outerText": "outer",
"nested": {
"innerText": "inner",
"innerNumber": 42
}
}
""");
// Act
Dictionary<string, object?> result = document.ParseRecord(outerRecord);
// Assert
Assert.Equal("outer", result["outerText"]);
Dictionary<string, object?> nested = (Dictionary<string, object?>)result["nested"]!;
Assert.NotNull(nested);
Assert.True(nested.ContainsKey("innerText"));
Assert.Equal("inner", nested["innerText"]);
Assert.Equal(42, nested["innerNumber"]);
}
[Fact]
public void ParseRecord_NullRoot_ReturnsEmpty()
{
// Arrange
VariableType recordType =
VariableType.Record(
[
("text", typeof(string))
]);
JsonDocument document = JsonDocument.Parse("null");
// Act
Dictionary<string, object?> result = document.ParseRecord(recordType);
// Assert
Assert.Empty(result);
}
[Fact]
public void ParseRecord_ArrayWithSingleRecord_Succeeds()
{
// Arrange
VariableType listType =
VariableType.List(
[
("name", typeof(string)),
("value", typeof(int))
]);
JsonDocument document = JsonDocument.Parse(
"""
[
{
"name": "item",
"value": 5
}
]
""");
// Act
List<object?> result = document.ParseList(listType);
// Assert
Assert.Single(result);
Dictionary<string, object?> element = Assert.IsType<Dictionary<string, object?>>(result[0]);
Assert.Equal("item", element["name"]);
Assert.Equal(5, element["value"]);
}
[Fact]
public void ParseRecord_ArrayWithMultipleRecords_Throws()
{
// Arrange
VariableType recordType =
VariableType.Record(
[
("id", typeof(int))
]);
JsonDocument document = JsonDocument.Parse(
"""
[
{ "id": 1 },
{ "id": 2 }
]
""");
// Act / Assert
Assert.Throws<DeclarativeActionException>(() => document.ParseRecord(recordType));
}
[Fact]
public void ParseRecord_InvalidTargetType_Throws()
{
// Arrange
VariableType notARecord = typeof(string);
JsonDocument document = JsonDocument.Parse(
"""
{ "x": 1 }
""");
// Act / Assert
Assert.Throws<DeclarativeActionException>(() => document.ParseRecord(notARecord));
}
[Fact]
public void ParseRecord_InvalidRootKind_Throws()
{
// Arrange
VariableType recordType =
VariableType.Record(
[
("text", typeof(string))
]);
JsonDocument document = JsonDocument.Parse(@"""not-an-object""");
// Act / Assert
Assert.Throws<DeclarativeActionException>(() => document.ParseRecord(recordType));
}
[Fact]
public void ParseRecord_UnsupportedPropertyType_Throws()
{
// Arrange
VariableType recordType =
VariableType.Record(
[
("unsupported", typeof(Guid))
]);
JsonDocument document = JsonDocument.Parse(
"""
{ "unsupported": "C2556C11-210E-4BB6-BF18-4A8968CB45A8" }
""");
// Act / Assert
Assert.Throws<DeclarativeActionException>(() => document.ParseRecord(recordType));
}
[Fact]
public void ParseRecord_MissingRequiredProperty_Throws()
{
// Arrange
VariableType recordType =
VariableType.Record(
[
("required", typeof(bool))
]);
JsonDocument document = JsonDocument.Parse("{}");
// Act / Assert
Assert.Throws<DeclarativeActionException>(() => document.ParseRecord(recordType));
}
[Fact]
public void ParseRecord_MissingNullableProperty_Succeeds()
{
// Arrange
VariableType recordType =
VariableType.Record(
[
("required", typeof(string))
]);
JsonDocument document = JsonDocument.Parse("{}");
// Act
Dictionary<string, object?> result = document.ParseRecord(recordType);
// Assert
Assert.Single(result);
Dictionary<string, object?> element = Assert.IsType<Dictionary<string, object?>>(result);
Assert.Null(element["required"]);
}
[Fact]
public void ParseList_NullRoot_ReturnsEmpty()
{
// Arrange
JsonDocument document = JsonDocument.Parse("null");
// Act
List<object?> result = document.ParseList(typeof(int[]));
// Assert
Assert.Empty(result);
}
[Fact]
public void ParseList_Array_Primitives_Succeeds()
{
// Arrange
JsonDocument document = JsonDocument.Parse("[1,2,3]");
// Act
List<object?> result = document.ParseList(typeof(int[]));
// Assert
Assert.Equal(3, result.Count);
Assert.Equal(1, result[0]);
Assert.Equal(2, result[1]);
Assert.Equal(3, result[2]);
}
[Fact]
public void ParseList_PrimitiveRoot_WrappedAsSingleElement_Succeeds()
{
// Arrange
JsonDocument document = JsonDocument.Parse("7");
// Act
List<object?> result = document.ParseList(typeof(int));
// Assert
Assert.Single(result);
Assert.Equal(7, result[0]);
}
[Fact]
public void ParseList_Array_Records_Succeeds()
{
// Arrange
VariableType listType =
VariableType.List(
[
("id", typeof(int)),
("name", typeof(string))
]);
JsonDocument document = JsonDocument.Parse(
"""
[
{ "id": 1, "name": "a" },
{ "id": 2, "name": "b" }
]
""");
// Act
List<object?> result = document.ParseList(listType);
// Assert
Assert.Equal(2, result.Count);
Dictionary<string, object?> first = (Dictionary<string, object?>)result[0]!;
Dictionary<string, object?> second = (Dictionary<string, object?>)result[1]!;
Assert.NotNull(first);
Assert.Equal(1, first["id"]);
Assert.Equal("a", first["name"]);
Assert.NotNull(second);
Assert.Equal(2, second["id"]);
Assert.Equal("b", second["name"]);
}
[Fact]
public void ParseList_InvalidTargetType_Throws()
{
// Arrange
JsonDocument document = JsonDocument.Parse("[1,2]");
// Act / Assert
Assert.Throws<DeclarativeActionException>(() => document.ParseList(typeof(int)));
}
[Fact]
public void ParseList_Array_MixedTypes_Throws()
{
// Arrange
JsonDocument document = JsonDocument.Parse("[1,\"two\",3]");
// Act / Assert
Assert.Throws<DeclarativeActionException>(() => document.ParseList(typeof(int[])));
}
}

View File

@@ -0,0 +1,124 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using Microsoft.Agents.AI.Workflows.Declarative.Extensions;
using Microsoft.Agents.AI.Workflows.Declarative.Kit;
namespace Microsoft.Agents.AI.Workflows.Declarative.UnitTests.Extensions;
public sealed class ObjectExtensionsTests
{
[Fact]
public void AsListWithNullInput()
{
object[]? nullList = null;
IList<string>? result = nullList.AsList<string>();
Assert.Null(result);
}
[Fact]
public void AsListWithEmptyInput()
{
IList<string>? result = Array.Empty<int>().AsList<string>();
Assert.NotNull(result);
Assert.Empty(result);
}
[Fact]
public void AsListWithSingleElement()
{
const string Value = "Test";
IList<string>? result = Value.AsList<string>();
Assert.NotNull(result);
Assert.Single(result);
Assert.Equal(Value, result[0]);
}
[Fact]
public void AsListWithMultipleInput()
{
object[] inputs = ["33.3", "test"];
IList<string>? result = inputs.AsList<string>();
Assert.NotNull(result);
Assert.Equal(2, result.Count);
}
[Fact]
public void ConvertSame()
{
VerifyConversion(true, typeof(bool), true);
VerifyConversion(32, typeof(int), 32);
VerifyConversion("Test", typeof(string), "Test");
DateTime now = DateTime.Now;
VerifyConversion(now, typeof(DateTime), now);
VerifyConversion(now.TimeOfDay, typeof(TimeSpan), now.TimeOfDay);
}
[Fact]
public void ConvertFailure()
{
VerifyInvalid(32, VariableType.RecordType);
VerifyInvalid(true, VariableType.RecordType);
VerifyInvalid(Guid.NewGuid(), typeof(Guid));
}
[Fact]
public void ConvertToString()
{
VerifyConversion(true, typeof(string), bool.TrueString);
VerifyConversion(32, typeof(string), "32");
VerifyConversion(3.14d, typeof(string), "3.14");
DateTime now = DateTime.Now;
VerifyConversion(now, typeof(string), $"{now:o}");
VerifyConversion(now.TimeOfDay, typeof(string), $"{now.TimeOfDay:c}");
}
[Fact]
public void ConvertFromString()
{
VerifyConversion("true", typeof(bool), true);
VerifyConversion("32", typeof(int), 32);
VerifyConversion("3.14", typeof(double), 3.14D);
DateTime now = DateTime.Now;
VerifyConversion($"{now:o}", typeof(DateTime), now);
VerifyConversion($"{now.TimeOfDay:c}", typeof(TimeSpan), now.TimeOfDay);
}
[Fact]
public void ConvertJson()
{
const string Json =
"""
{
"id": "item1",
"count": 5
}
""";
Dictionary<string, object?> expected =
new()
{
{ "id", "item1"},
{ "count", 5},
};
VerifyConversion(Json, VariableType.Record(("id", typeof(string)), ("count", typeof(int))), expected);
}
private static void VerifyConversion(object? sourceValue, VariableType targetType, object? expectedValue)
{
object? actualValue = sourceValue.ConvertType(targetType);
if (expectedValue is IDictionary<string, object?> or DateTime)
{
Assert.Equivalent(expectedValue, actualValue);
}
else
{
Assert.Equal(expectedValue, actualValue);
}
}
private static void VerifyInvalid(object? sourceValue, VariableType targetType)
{
Assert.Throws<DeclarativeActionException>(() => sourceValue.ConvertType(targetType));
}
}

View File

@@ -0,0 +1,127 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using Microsoft.Agents.AI.Workflows.Declarative.Extensions;
using Microsoft.Agents.AI.Workflows.Declarative.Kit;
using Microsoft.Agents.AI.Workflows.Declarative.PowerFx;
using Microsoft.Extensions.AI;
using Microsoft.PowerFx.Types;
namespace Microsoft.Agents.AI.Workflows.Declarative.UnitTests.Extensions;
public sealed class PortableValueExtensionsTests
{
[Fact]
public void InvalidType() => TestInvalidType(IPAddress.Loopback);
[Fact]
public void NullType() => TestValidType<object>(null, FormulaType.Blank);
[Fact]
public void BooleanType() => TestValidType(true, FormulaType.Boolean);
[Fact]
public void StringType() => TestValidType("Hello, World!", FormulaType.String);
[Fact]
public void IntType() => TestValidType(int.MinValue, FormulaType.Decimal);
[Fact]
public void LongType() => TestValidType(long.MaxValue, FormulaType.Decimal);
[Fact]
public void DecimalType() => TestValidType(decimal.MaxValue, FormulaType.Decimal);
[Fact]
public void FloatType() => TestValidType(float.MaxValue, FormulaType.Number);
[Fact]
public void DoubleType() => TestValidType(double.MinValue, FormulaType.Number);
[Fact]
public void DateType() => TestValidType(DateTime.UtcNow.Date, FormulaType.Date);
[Fact]
public void DateTimeType() => TestValidType(DateTime.UtcNow, FormulaType.DateTime);
[Fact]
public void TimeSpanType() => TestValidType(DateTime.UtcNow.TimeOfDay, FormulaType.Time);
[Fact]
public void ChatMessageType() => TestValidType(new ChatMessage(ChatRole.User, "input"), RecordType.Empty());
[Fact]
public void ListEmptyType()
{
TableValue convertedValue = (TableValue)TestValidType(Array.Empty<int>(), TableType.Empty());
Assert.Equal(0, convertedValue.Count());
}
[Fact]
public void ListSimpleType()
{
TableValue convertedValue = (TableValue)TestValidType(new List<int> { 1, 2, 3 }, TableType.Empty());
Assert.Equal(3, convertedValue.Count());
RecordValue firstElement = convertedValue.Rows.First().Value;
NamedValue recordElement = Assert.Single(firstElement.Fields);
Assert.Equal("Value", recordElement.Name);
DecimalValue recordValue = Assert.IsType<DecimalValue>(recordElement.Value);
Assert.Equal(1, recordValue.Value);
}
[Fact]
public void ListComplexType()
{
TableValue convertedValue = (TableValue)TestValidType(new List<ChatMessage> { new(ChatRole.User, "input"), new(ChatRole.Assistant, "output") }, TableType.Empty());
Assert.Equal(2, convertedValue.Count());
RecordValue firstElement = convertedValue.Rows.First().Value;
StringValue typeValue = Assert.IsType<StringValue>(firstElement.GetField(TypeSchema.Discriminator));
Assert.Equal(nameof(ChatMessage), typeValue.Value);
StringValue textValue = Assert.IsType<StringValue>(firstElement.GetField(TypeSchema.Message.Fields.Text));
Assert.Equal("input", textValue.Value);
}
[Fact]
public void DictionaryType()
{
RecordValue convertedValue = (RecordValue)TestValidType(new Dictionary<string, int> { { "A", 1 }, { "B", 2 } }, RecordType.Empty());
Assert.Equal(2, convertedValue.Fields.Count());
NamedValue firstElement = convertedValue.Fields.First();
Assert.Equal("A", firstElement.Name);
DecimalValue firstElementValue = Assert.IsType<DecimalValue>(firstElement.Value);
Assert.Equal(1, firstElementValue.Value);
}
[Fact]
public void ObjectType()
{
RecordValue convertedValue = (RecordValue)TestValidType(FormulaValue.NewRecordFromFields(new NamedValue("key", FormulaValue.New(3))).ToDataValue().ToObject(), RecordType.Empty());
Assert.Single(convertedValue.Fields);
NamedValue firstElement = convertedValue.Fields.First();
Assert.Equal("key", firstElement.Name);
DecimalValue firstElementValue = Assert.IsType<DecimalValue>(firstElement.Value);
Assert.Equal(3, firstElementValue.Value);
}
private static void TestInvalidType(object? sourceValue)
{
Assert.Throws<DeclarativeModelException>(() => sourceValue.AsPortable());
PortableValue portableValue = new(sourceValue ?? UnassignedValue.Instance);
Assert.Throws<DeclarativeModelException>(() => portableValue.ToFormula());
}
private static FormulaValue TestValidType<TValue>(TValue? sourceValue, FormulaType expectedType) where TValue : notnull
{
object portableObject = sourceValue.AsPortable();
Assert.IsNotType<PortableValue>(portableObject);
PortableValue portableValue = new(portableObject);
FormulaValue formulaValue = portableValue.ToFormula();
Assert.NotNull(formulaValue);
Assert.Equal(expectedType.GetType(), formulaValue.Type.GetType());
return formulaValue;
}
}

View File

@@ -0,0 +1,162 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI.Workflows.Declarative.Extensions;
namespace Microsoft.Agents.AI.Workflows.Declarative.UnitTests.Extensions;
public sealed class StringExtensionsTests
{
[Fact]
public void TrimJsonWithDelimiter()
{
// Arrange
const string Input =
"""
```json
{
"key": "value"
}
```
""";
// Act
string result = Input.TrimJsonDelimiter();
// Assert
Assert.Equal(
"""
{
"key": "value"
}
""",
result);
}
[Fact]
public void TrimJsonWithPadding()
{
// Arrange
const string Input =
"""
```json
{
"key": "value"
}
```
""";
// Act
string result = Input.TrimJsonDelimiter();
// Assert
Assert.Equal(
"""
{
"key": "value"
}
""",
result);
}
[Fact]
public void TrimJsonWithUnqualifiedDelimiter()
{
// Arrange
const string Input =
"""
```
{
"key": "value"
}
```
""";
// Act
string result = Input.TrimJsonDelimiter();
// Assert
Assert.Equal(
"""
{
"key": "value"
}
""",
result);
}
[Fact]
public void TrimJsonWithoutDelimiter()
{
// Arrange
const string Input =
"""
{
"key": "value"
}
""";
// Act
string result = Input.TrimJsonDelimiter();
// Assert
Assert.Equal(
"""
{
"key": "value"
}
""",
result);
}
[Fact]
public void TrimJsonWithoutDelimiterWithPadding()
{
// Arrange
const string Input =
"""
{
"key": "value"
}
""";
// Act
string result = Input.TrimJsonDelimiter();
// Assert
Assert.Equal(
"""
{
"key": "value"
}
""",
result);
}
[Fact]
public void TrimMissingWithDelimiter()
{
// Arrange
const string Input =
"""
```json
```
""";
// Act
string result = Input.TrimJsonDelimiter();
// Assert
Assert.Equal(string.Empty, result);
}
[Fact]
public void TrimEmptyString()
{
// Act
string result = string.Empty.TrimJsonDelimiter();
// Assert
Assert.Equal(string.Empty, result);
}
}

View File

@@ -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<TemplateLine> template =
[
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<TemplateLine> template =
[
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);
}
}

View File

@@ -0,0 +1,65 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using Microsoft.Agents.AI.Workflows.Declarative.Extensions;
namespace Microsoft.Agents.AI.Workflows.Declarative.UnitTests.Extensions;
public sealed class TypeExtensionsTests
{
[Fact]
public void ReferenceType() => VerifyIsNullable(typeof(string));
[Fact]
public void ClassType() => VerifyIsNullable(typeof(object));
[Fact]
public void InterfaceType() => VerifyIsNullable(typeof(IDisposable));
[Fact]
public void ArrayType() => VerifyIsNullable(typeof(int[]));
[Fact]
public void NonNullableValueType() => VerifyNotNullable(typeof(int));
[Fact]
public void NonNullableStructType() => VerifyNotNullable(typeof(DateTime));
[Fact]
public void NonNullableEnumType() => VerifyNotNullable(typeof(DayOfWeek));
[Fact]
public void NullableInt() => VerifyIsNullable(typeof(int?));
[Fact]
public void NullableDateTime() => VerifyIsNullable(typeof(DateTime?));
[Fact]
public void NullableEnum() => VerifyIsNullable(typeof(DayOfWeek?));
[Fact]
public void NullableCustomStruct() => VerifyIsNullable(typeof(TestStruct?));
private static void VerifyNotNullable(Type targetType)
{
// Act
bool result = targetType.IsNullable();
// Assert
Assert.False(result);
}
private static void VerifyIsNullable(Type targetType)
{
// Act
bool result = targetType.IsNullable();
// Assert
Assert.True(result);
}
private struct TestStruct
{
public int Value { get; set; }
}
}