Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -402,6 +470,44 @@ 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 ValueTask<AgentSession> CreateSessionAsync(CancellationToken cancellationToken = default)
=> throw new NotImplementedException();

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

public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null)
=> 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