Skip to content
Open
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (c) Microsoft. All rights reserved.

namespace Microsoft.Agents.AI.Abstractions.UnitTests;

/// <summary>
/// Unit tests for the <see cref="AIAgentMetadata"/> class.
/// </summary>
public class AIAgentMetadataTests
{
[Fact]
public void Constructor_WithNoArguments_SetsProviderNameToNull()
{
// Arrange & Act
AIAgentMetadata metadata = new();

// Assert
Assert.Null(metadata.ProviderName);
}

[Fact]
public void Constructor_WithProviderName_SetsProperty()
{
// Arrange
const string ProviderName = "TestProvider";

// Act
AIAgentMetadata metadata = new(ProviderName);

// Assert
Assert.Equal(ProviderName, metadata.ProviderName);
}

[Fact]
public void Constructor_WithNullProviderName_SetsProviderNameToNull()
{
// Arrange & Act
AIAgentMetadata metadata = new(null);

// Assert
Assert.Null(metadata.ProviderName);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,74 @@ public void GetService_Generic_ReturnsNullForUnrelatedType()

#endregion

#region Name and Description Property Tests

/// <summary>
/// Verify that Name property returns the value from the derived class.
/// </summary>
[Fact]
public void Name_ReturnsValueFromDerivedClass()
{
// Arrange
var agent = new MockAgentWithName("TestAgentName", "TestAgentDescription");

// Act
string? name = agent.Name;

// Assert
Assert.Equal("TestAgentName", name);
}

/// <summary>
/// Verify that Description property returns the value from the derived class.
/// </summary>
[Fact]
public void Description_ReturnsValueFromDerivedClass()
{
// Arrange
var agent = new MockAgentWithName("TestAgentName", "TestAgentDescription");

// Act
string? description = agent.Description;

// Assert
Assert.Equal("TestAgentDescription", description);
}

/// <summary>
/// Verify that Name property returns null when not overridden.
/// </summary>
[Fact]
public void Name_ReturnsNullByDefault()
{
// Arrange
var agent = new MockAgent();

// Act
string? name = agent.Name;

// Assert
Assert.Null(name);
}

/// <summary>
/// Verify that Description property returns null when not overridden.
/// </summary>
[Fact]
public void Description_ReturnsNullByDefault()
{
// Arrange
var agent = new MockAgent();

// Act
string? description = agent.Description;

// Assert
Assert.Null(description);
}

#endregion

/// <summary>
/// Typed mock session.
/// </summary>
Expand Down Expand Up @@ -399,6 +467,41 @@ protected override IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingAsync(
throw new NotImplementedException();
}

private sealed class MockAgentWithName : AIAgent
{
private readonly string? _name;
private readonly string? _description;

public MockAgentWithName(string? name, string? description)
{
this._name = name;
this._description = description;
}

public override string? Name => this._name;
public override string? Description => this._description;

public override async ValueTask<AgentSession> GetNewSessionAsync(CancellationToken cancellationToken = default)
=> throw new NotImplementedException();

public override async ValueTask<AgentSession> DeserializeSessionAsync(JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default)
=> throw new NotImplementedException();

protected override Task<AgentResponse> RunCoreAsync(
IEnumerable<ChatMessage> messages,
AgentSession? session = null,
AgentRunOptions? options = null,
CancellationToken cancellationToken = default) =>
throw new NotImplementedException();

protected override IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingAsync(
IEnumerable<ChatMessage> messages,
AgentSession? session = null,
AgentRunOptions? options = null,
CancellationToken cancellationToken = default) =>
throw new NotImplementedException();
}

private static async IAsyncEnumerable<T> ToAsyncEnumerableAsync<T>(IEnumerable<T> values)
{
await Task.Yield();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -155,6 +156,111 @@ public void GetService_Generic_ReturnsNullForUnrelatedType()

#endregion

#region InvokingContext Tests

[Fact]
public void InvokingContext_RequestMessages_SetterThrowsForNull()
{
// Arrange
var messages = new ReadOnlyCollection<ChatMessage>([new(ChatRole.User, "Hello")]);
var context = new AIContextProvider.InvokingContext(messages);

// Act & Assert
Assert.Throws<ArgumentNullException>(() => context.RequestMessages = null!);
}

[Fact]
public void InvokingContext_RequestMessages_SetterRoundtrips()
{
// Arrange
var initialMessages = new ReadOnlyCollection<ChatMessage>([new(ChatRole.User, "Hello")]);
var newMessages = new List<ChatMessage> { new(ChatRole.User, "New message") };
var context = new AIContextProvider.InvokingContext(initialMessages);

// Act
context.RequestMessages = newMessages;

// Assert
Assert.Same(newMessages, context.RequestMessages);
}

#endregion

#region InvokedContext Tests

[Fact]
public void InvokedContext_RequestMessages_SetterThrowsForNull()
{
// Arrange
var messages = new ReadOnlyCollection<ChatMessage>([new(ChatRole.User, "Hello")]);
var context = new AIContextProvider.InvokedContext(messages, aiContextProviderMessages: null);

// Act & Assert
Assert.Throws<ArgumentNullException>(() => context.RequestMessages = null!);
}

[Fact]
public void InvokedContext_RequestMessages_SetterRoundtrips()
{
// Arrange
var initialMessages = new ReadOnlyCollection<ChatMessage>([new(ChatRole.User, "Hello")]);
var newMessages = new List<ChatMessage> { new(ChatRole.User, "New message") };
var context = new AIContextProvider.InvokedContext(initialMessages, aiContextProviderMessages: null);

// Act
context.RequestMessages = newMessages;

// Assert
Assert.Same(newMessages, context.RequestMessages);
}

[Fact]
public void InvokedContext_AIContextProviderMessages_Roundtrips()
{
// Arrange
var requestMessages = new ReadOnlyCollection<ChatMessage>([new(ChatRole.User, "Hello")]);
var aiContextMessages = new List<ChatMessage> { new(ChatRole.System, "AI context message") };
var context = new AIContextProvider.InvokedContext(requestMessages, aiContextProviderMessages: null);

// Act
context.AIContextProviderMessages = aiContextMessages;

// Assert
Assert.Same(aiContextMessages, context.AIContextProviderMessages);
}

[Fact]
public void InvokedContext_ResponseMessages_Roundtrips()
{
// Arrange
var requestMessages = new ReadOnlyCollection<ChatMessage>([new(ChatRole.User, "Hello")]);
var responseMessages = new List<ChatMessage> { new(ChatRole.Assistant, "Response message") };
var context = new AIContextProvider.InvokedContext(requestMessages, aiContextProviderMessages: null);

// Act
context.ResponseMessages = responseMessages;

// Assert
Assert.Same(responseMessages, context.ResponseMessages);
}

[Fact]
public void InvokedContext_InvokeException_Roundtrips()
{
// Arrange
var requestMessages = new ReadOnlyCollection<ChatMessage>([new(ChatRole.User, "Hello")]);
var exception = new InvalidOperationException("Test exception");
var context = new AIContextProvider.InvokedContext(requestMessages, aiContextProviderMessages: null);

// Act
context.InvokeException = exception;

// Assert
Assert.Same(exception, context.InvokeException);
}

#endregion

private sealed class TestAIContextProvider : AIContextProvider
{
public override ValueTask<AIContext> InvokingAsync(InvokingContext context, CancellationToken cancellationToken = default)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -346,4 +346,91 @@ public void TryParseAsStructuredOutputFailsWithIncorrectTypedJson()
// Act & Assert.
Assert.False(response.TryDeserialize<Animal>(TestJsonSerializerContext.Default.Options, out _));
}

[Fact]
public void UserInputRequests_ReturnsEmptyWhenNoMessages()
{
// Arrange
AgentResponse response = new();

// Act
IEnumerable<UserInputRequestContent> requests = response.UserInputRequests;

// Assert
Assert.Empty(requests);
}

[Fact]
public void UserInputRequests_ReturnsEmptyWhenNoUserInputRequestContent()
{
// Arrange
AgentResponse response = new(new ChatMessage(ChatRole.Assistant, "Hello"));

// Act
IEnumerable<UserInputRequestContent> requests = response.UserInputRequests;

// Assert
Assert.Empty(requests);
}

[Fact]
public void ToAgentResponseUpdatesWithNoMessagesProducesEmptyArray()
{
// Arrange
AgentResponse response = new();

// Act
AgentResponseUpdate[] updates = response.ToAgentResponseUpdates();

// Assert
Assert.Empty(updates);
}

[Fact]
public void ToAgentResponseUpdatesWithUsageOnlyProducesSingleUpdate()
{
// Arrange
AgentResponse response = new()
{
Usage = new UsageDetails { TotalTokenCount = 100 }
};

// Act
AgentResponseUpdate[] updates = response.ToAgentResponseUpdates();

// Assert
AgentResponseUpdate update = Assert.Single(updates);
UsageContent usageContent = Assert.IsType<UsageContent>(update.Contents[0]);
Assert.Equal(100, usageContent.Details.TotalTokenCount);
}

[Fact]
public void ToAgentResponseUpdatesWithAdditionalPropertiesOnlyProducesSingleUpdate()
{
// Arrange
AgentResponse response = new()
{
AdditionalProperties = new() { ["key"] = "value" }
};

// Act
AgentResponseUpdate[] updates = response.ToAgentResponseUpdates();

// Assert
AgentResponseUpdate update = Assert.Single(updates);
Assert.NotNull(update.AdditionalProperties);
Assert.Equal("value", update.AdditionalProperties!["key"]);
}

[Fact]
public void Deserialize_ThrowsWhenDeserializationReturnsNull()
{
// Arrange
AgentResponse response = new(new ChatMessage(ChatRole.Assistant, "null"));

// Act & Assert
InvalidOperationException exception = Assert.Throws<InvalidOperationException>(
() => response.Deserialize<Animal>(TestJsonSerializerContext.Default.Options));
Assert.Equal("The deserialized response is null.", exception.Message);
}
}
Loading
Loading