.NET: Improve unit test coverage for Microsoft.Agents.AI.AzureAI to 85.6% (#3383)

* Initial plan

* Add unit tests to improve Microsoft.Agents.AI.AzureAI coverage to 85.6%

Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com>

* Fix pragma warning placement for CA1812 per code review feedback

Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com>

* Fix ValueTask.CompletedTask for net472 compatibility by using default

Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com>

* Merge AzureAIProjectChatClientExtensionsCoverageTests into AzureAIProjectChatClientExtensionsTests

* Update tests to use renamed ChatHistoryProvider instead of ChatMessageStore

* Simplify type names in TestChatHistoryProvider to fix IDE0001

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com>
This commit is contained in:
Copilot
2026-01-28 11:54:25 +00:00
committed by GitHub
Unverified
parent 4245561013
commit 15b43f2abe
@@ -1803,6 +1803,863 @@ public sealed class AzureAIProjectChatClientExtensionsTests
#endregion
#region GetAIAgentAsync - Empty Name Tests
/// <summary>
/// Verify that GetAIAgentAsync with ChatClientAgentOptions throws ArgumentException when name is null.
/// </summary>
[Fact]
public async Task GetAIAgentAsync_WithOptions_WithNullName_ThrowsArgumentExceptionAsync()
{
// Arrange
AIProjectClient client = this.CreateTestAgentClient();
var options = new ChatClientAgentOptions { Name = null };
// Act & Assert
ArgumentException exception = await Assert.ThrowsAsync<ArgumentException>(() =>
client.GetAIAgentAsync(options));
Assert.Equal("options", exception.ParamName);
Assert.Contains("Agent name must be provided", exception.Message);
}
/// <summary>
/// Verify that GetAIAgentAsync with ChatClientAgentOptions throws ArgumentException when name is empty.
/// </summary>
[Fact]
public async Task GetAIAgentAsync_WithOptions_WithEmptyName_ThrowsArgumentExceptionAsync()
{
// Arrange
AIProjectClient client = this.CreateTestAgentClient();
var options = new ChatClientAgentOptions { Name = string.Empty };
// Act & Assert
ArgumentException exception = await Assert.ThrowsAsync<ArgumentException>(() =>
client.GetAIAgentAsync(options));
Assert.Equal("options", exception.ParamName);
Assert.Contains("Agent name must be provided", exception.Message);
}
/// <summary>
/// Verify that GetAIAgentAsync with ChatClientAgentOptions throws ArgumentException when name is whitespace.
/// </summary>
[Fact]
public async Task GetAIAgentAsync_WithOptions_WithWhitespaceName_ThrowsArgumentExceptionAsync()
{
// Arrange
AIProjectClient client = this.CreateTestAgentClient();
var options = new ChatClientAgentOptions { Name = " " };
// Act & Assert
ArgumentException exception = await Assert.ThrowsAsync<ArgumentException>(() =>
client.GetAIAgentAsync(options));
Assert.Equal("options", exception.ParamName);
Assert.Contains("Agent name must be provided", exception.Message);
}
#endregion
#region CreateAIAgentAsync - Empty Name Tests
/// <summary>
/// Verify that CreateAIAgentAsync with model and options throws ArgumentException when name is null.
/// </summary>
[Fact]
public async Task CreateAIAgentAsync_WithModelAndOptions_WithNullName_ThrowsArgumentExceptionAsync()
{
// Arrange
AIProjectClient client = this.CreateTestAgentClient();
var options = new ChatClientAgentOptions
{
Name = null,
ChatOptions = new ChatOptions { Instructions = "Test" }
};
// Act & Assert
ArgumentException exception = await Assert.ThrowsAsync<ArgumentException>(() =>
client.CreateAIAgentAsync("test-model", options));
Assert.Equal("options", exception.ParamName);
Assert.Contains("Agent name must be provided", exception.Message);
}
/// <summary>
/// Verify that CreateAIAgentAsync with model and options throws ArgumentException when name is empty.
/// </summary>
[Fact]
public async Task CreateAIAgentAsync_WithModelAndOptions_WithEmptyName_ThrowsArgumentExceptionAsync()
{
// Arrange
AIProjectClient client = this.CreateTestAgentClient();
var options = new ChatClientAgentOptions
{
Name = string.Empty,
ChatOptions = new ChatOptions { Instructions = "Test" }
};
// Act & Assert
ArgumentException exception = await Assert.ThrowsAsync<ArgumentException>(() =>
client.CreateAIAgentAsync("test-model", options));
Assert.Equal("options", exception.ParamName);
Assert.Contains("Agent name must be provided", exception.Message);
}
/// <summary>
/// Verify that CreateAIAgentAsync with model and options throws ArgumentException when name is whitespace.
/// </summary>
[Fact]
public async Task CreateAIAgentAsync_WithModelAndOptions_WithWhitespaceName_ThrowsArgumentExceptionAsync()
{
// Arrange
AIProjectClient client = this.CreateTestAgentClient();
var options = new ChatClientAgentOptions
{
Name = " ",
ChatOptions = new ChatOptions { Instructions = "Test" }
};
// Act & Assert
ArgumentException exception = await Assert.ThrowsAsync<ArgumentException>(() =>
client.CreateAIAgentAsync("test-model", options));
Assert.Equal("options", exception.ParamName);
Assert.Contains("Agent name must be provided", exception.Message);
}
#endregion
#region CreateAIAgentAsync - Response Format Tests
/// <summary>
/// Verify that CreateAIAgentAsync with ChatResponseFormatText response format creates agent successfully.
/// </summary>
[Fact]
public async Task CreateAIAgentAsync_WithTextResponseFormat_CreatesAgentSuccessfullyAsync()
{
// Arrange
AIProjectClient client = this.CreateTestAgentClient();
var options = new ChatClientAgentOptions
{
Name = "test-agent",
ChatOptions = new ChatOptions
{
Instructions = "Test",
ResponseFormat = ChatResponseFormat.Text
}
};
// Act
ChatClientAgent agent = await client.CreateAIAgentAsync("test-model", options);
// Assert
Assert.NotNull(agent);
Assert.IsType<ChatClientAgent>(agent);
}
/// <summary>
/// Verify that CreateAIAgentAsync with ChatResponseFormatJson response format without schema creates agent successfully.
/// </summary>
[Fact]
public async Task CreateAIAgentAsync_WithJsonResponseFormatWithoutSchema_CreatesAgentSuccessfullyAsync()
{
// Arrange
AIProjectClient client = this.CreateTestAgentClient();
var options = new ChatClientAgentOptions
{
Name = "test-agent",
ChatOptions = new ChatOptions
{
Instructions = "Test",
ResponseFormat = ChatResponseFormat.Json
}
};
// Act
ChatClientAgent agent = await client.CreateAIAgentAsync("test-model", options);
// Assert
Assert.NotNull(agent);
Assert.IsType<ChatClientAgent>(agent);
}
/// <summary>
/// Verify that CreateAIAgentAsync with ChatResponseFormatJson with schema creates agent successfully.
/// </summary>
[Fact]
public async Task CreateAIAgentAsync_WithJsonResponseFormatWithSchema_CreatesAgentSuccessfullyAsync()
{
// Arrange
AIProjectClient client = this.CreateTestAgentClient();
JsonElement schemaElement = AIJsonUtilities.CreateJsonSchema(typeof(TestSchema));
var jsonFormat = ChatResponseFormat.ForJsonSchema(schemaElement, "test_schema", "A test schema");
var options = new ChatClientAgentOptions
{
Name = "test-agent",
ChatOptions = new ChatOptions
{
Instructions = "Test",
ResponseFormat = jsonFormat
}
};
// Act
ChatClientAgent agent = await client.CreateAIAgentAsync("test-model", options);
// Assert
Assert.NotNull(agent);
Assert.IsType<ChatClientAgent>(agent);
}
/// <summary>
/// Verify that CreateAIAgentAsync with ChatResponseFormatJson with schema and strict mode creates agent successfully.
/// </summary>
[Fact]
public async Task CreateAIAgentAsync_WithJsonResponseFormatWithSchemaAndStrictMode_CreatesAgentSuccessfullyAsync()
{
// Arrange
AIProjectClient client = this.CreateTestAgentClient();
JsonElement schemaElement = AIJsonUtilities.CreateJsonSchema(typeof(TestSchema));
var jsonFormat = ChatResponseFormat.ForJsonSchema(schemaElement, "test_schema", "A test schema");
var additionalProps = new AdditionalPropertiesDictionary
{
["strictJsonSchema"] = true
};
var options = new ChatClientAgentOptions
{
Name = "test-agent",
ChatOptions = new ChatOptions
{
Instructions = "Test",
ResponseFormat = jsonFormat,
AdditionalProperties = additionalProps
}
};
// Act
ChatClientAgent agent = await client.CreateAIAgentAsync("test-model", options);
// Assert
Assert.NotNull(agent);
Assert.IsType<ChatClientAgent>(agent);
}
/// <summary>
/// Verify that CreateAIAgentAsync with ChatResponseFormatJson with schema and strict mode false creates agent successfully.
/// </summary>
[Fact]
public async Task CreateAIAgentAsync_WithJsonResponseFormatWithSchemaAndStrictModeFalse_CreatesAgentSuccessfullyAsync()
{
// Arrange
AIProjectClient client = this.CreateTestAgentClient();
JsonElement schemaElement = AIJsonUtilities.CreateJsonSchema(typeof(TestSchema));
var jsonFormat = ChatResponseFormat.ForJsonSchema(schemaElement, "test_schema", "A test schema");
var additionalProps = new AdditionalPropertiesDictionary
{
["strictJsonSchema"] = false
};
var options = new ChatClientAgentOptions
{
Name = "test-agent",
ChatOptions = new ChatOptions
{
Instructions = "Test",
ResponseFormat = jsonFormat,
AdditionalProperties = additionalProps
}
};
// Act
ChatClientAgent agent = await client.CreateAIAgentAsync("test-model", options);
// Assert
Assert.NotNull(agent);
Assert.IsType<ChatClientAgent>(agent);
}
#endregion
#region CreateAIAgentAsync - RawRepresentationFactory Tests
/// <summary>
/// Verify that CreateAIAgentAsync with RawRepresentationFactory that returns CreateResponseOptions creates agent successfully.
/// </summary>
[Fact]
public async Task CreateAIAgentAsync_WithRawRepresentationFactory_CreatesAgentSuccessfullyAsync()
{
// Arrange
AIProjectClient client = this.CreateTestAgentClient();
var options = new ChatClientAgentOptions
{
Name = "test-agent",
ChatOptions = new ChatOptions
{
Instructions = "Test",
RawRepresentationFactory = _ => new CreateResponseOptions()
}
};
// Act
ChatClientAgent agent = await client.CreateAIAgentAsync("test-model", options);
// Assert
Assert.NotNull(agent);
Assert.IsType<ChatClientAgent>(agent);
}
/// <summary>
/// Verify that CreateAIAgentAsync with RawRepresentationFactory that returns null does not fail.
/// </summary>
[Fact]
public async Task CreateAIAgentAsync_WithRawRepresentationFactoryReturningNull_CreatesAgentSuccessfullyAsync()
{
// Arrange
AIProjectClient client = this.CreateTestAgentClient();
var options = new ChatClientAgentOptions
{
Name = "test-agent",
ChatOptions = new ChatOptions
{
Instructions = "Test",
RawRepresentationFactory = _ => null
}
};
// Act
ChatClientAgent agent = await client.CreateAIAgentAsync("test-model", options);
// Assert
Assert.NotNull(agent);
Assert.IsType<ChatClientAgent>(agent);
}
/// <summary>
/// Verify that CreateAIAgentAsync with RawRepresentationFactory that returns non-CreateResponseOptions does not fail.
/// </summary>
[Fact]
public async Task CreateAIAgentAsync_WithRawRepresentationFactoryReturningNonCreateResponseOptions_CreatesAgentSuccessfullyAsync()
{
// Arrange
AIProjectClient client = this.CreateTestAgentClient();
var options = new ChatClientAgentOptions
{
Name = "test-agent",
ChatOptions = new ChatOptions
{
Instructions = "Test",
RawRepresentationFactory = _ => new object()
}
};
// Act
ChatClientAgent agent = await client.CreateAIAgentAsync("test-model", options);
// Assert
Assert.NotNull(agent);
Assert.IsType<ChatClientAgent>(agent);
}
#endregion
#region CreateAIAgentAsync - Description Tests
/// <summary>
/// Verify that CreateAIAgentAsync with description sets description on the agent.
/// </summary>
[Fact]
public async Task CreateAIAgentAsync_WithDescription_SetsDescriptionAsync()
{
// Arrange
AIProjectClient client = this.CreateTestAgentClient(description: "Test description");
var options = new ChatClientAgentOptions
{
Name = "test-agent",
Description = "Test description",
ChatOptions = new ChatOptions { Instructions = "Test" }
};
// Act
ChatClientAgent agent = await client.CreateAIAgentAsync("test-model", options);
// Assert
Assert.NotNull(agent);
Assert.Equal("Test description", agent.Description);
}
/// <summary>
/// Verify that CreateAIAgentAsync without description still creates agent successfully.
/// </summary>
[Fact]
public async Task CreateAIAgentAsync_WithoutDescription_CreatesAgentSuccessfullyAsync()
{
// Arrange
AIProjectClient client = this.CreateTestAgentClient();
var options = new ChatClientAgentOptions
{
Name = "test-agent",
ChatOptions = new ChatOptions { Instructions = "Test" }
};
// Act
ChatClientAgent agent = await client.CreateAIAgentAsync("test-model", options);
// Assert
Assert.NotNull(agent);
}
#endregion
#region CreateChatClientAgentOptions - Missing Tools Tests
/// <summary>
/// Verify that when invocable tools are required but not provided, an exception is thrown.
/// </summary>
[Fact]
public async Task GetAIAgentAsync_WithToolsRequiredButNotProvided_ThrowsArgumentExceptionAsync()
{
// Arrange
PromptAgentDefinition definition = new("test-model") { Instructions = "Test" };
definition.Tools.Add(ResponseTool.CreateFunctionTool("required_function", BinaryData.FromString("{}"), strictModeEnabled: false));
AIProjectClient client = this.CreateTestAgentClient(agentDefinitionResponse: definition);
var options = new ChatClientAgentOptions
{
Name = "test-agent",
ChatOptions = new ChatOptions { Instructions = "Test" }
};
// Act & Assert
ArgumentException exception = await Assert.ThrowsAsync<ArgumentException>(() =>
client.GetAIAgentAsync(options));
Assert.Contains("in-process tools must be provided", exception.Message);
}
/// <summary>
/// Verify that when specific invocable tools are required but wrong ones are provided, InvalidOperationException is thrown.
/// </summary>
[Fact]
public async Task GetAIAgentAsync_WithWrongToolsProvided_ThrowsInvalidOperationExceptionAsync()
{
// Arrange
PromptAgentDefinition definition = new("test-model") { Instructions = "Test" };
definition.Tools.Add(ResponseTool.CreateFunctionTool("required_function", BinaryData.FromString("{}"), strictModeEnabled: false));
AIProjectClient client = this.CreateTestAgentClient(agentDefinitionResponse: definition);
var tools = new List<AITool>
{
AIFunctionFactory.Create(() => "test", "wrong_function", "Wrong function")
};
var options = new ChatClientAgentOptions
{
Name = "test-agent",
ChatOptions = new ChatOptions
{
Instructions = "Test",
Tools = tools
}
};
// Act & Assert
InvalidOperationException exception = await Assert.ThrowsAsync<InvalidOperationException>(() =>
client.GetAIAgentAsync(options));
Assert.Contains("required_function", exception.Message);
Assert.Contains("were not provided", exception.Message);
}
/// <summary>
/// Verify that when tools are provided that match the definition, agent is created successfully.
/// </summary>
[Fact]
public async Task GetAIAgentAsync_WithMatchingToolsProvided_CreatesAgentSuccessfullyAsync()
{
// Arrange
PromptAgentDefinition definition = new("test-model") { Instructions = "Test" };
definition.Tools.Add(ResponseTool.CreateFunctionTool("required_function", BinaryData.FromString("{}"), strictModeEnabled: false));
AIProjectClient client = this.CreateTestAgentClient(agentDefinitionResponse: definition);
var tools = new List<AITool>
{
AIFunctionFactory.Create(() => "test", "required_function", "Required function")
};
var options = new ChatClientAgentOptions
{
Name = "test-agent",
ChatOptions = new ChatOptions
{
Instructions = "Test",
Tools = tools
}
};
// Act
ChatClientAgent agent = await client.GetAIAgentAsync(options);
// Assert
Assert.NotNull(agent);
Assert.IsType<ChatClientAgent>(agent);
}
#endregion
#region CreateChatClientAgentOptions - Options Preservation Tests
/// <summary>
/// Verify that CreateChatClientAgentOptions preserves AIContextProviderFactory.
/// </summary>
[Fact]
public async Task GetAIAgentAsync_WithAIContextProviderFactory_PreservesFactoryAsync()
{
// Arrange
AIProjectClient client = this.CreateTestAgentClient();
bool factoryInvoked = false;
var options = new ChatClientAgentOptions
{
Name = "test-agent",
ChatOptions = new ChatOptions { Instructions = "Test" },
AIContextProviderFactory = (_, _) =>
{
factoryInvoked = true;
return new ValueTask<AIContextProvider>(new TestAIContextProvider());
}
};
// Act
ChatClientAgent agent = await client.GetAIAgentAsync(options);
// Assert
Assert.NotNull(agent);
// Verify the factory was captured (though not necessarily invoked yet)
Assert.False(factoryInvoked); // Factory is not invoked during creation
}
/// <summary>
/// Verify that CreateChatClientAgentOptions preserves ChatHistoryProviderFactory.
/// </summary>
[Fact]
public async Task GetAIAgentAsync_WithChatHistoryProviderFactory_PreservesFactoryAsync()
{
// Arrange
AIProjectClient client = this.CreateTestAgentClient();
var options = new ChatClientAgentOptions
{
Name = "test-agent",
ChatOptions = new ChatOptions { Instructions = "Test" },
ChatHistoryProviderFactory = (_, _) => new ValueTask<ChatHistoryProvider>(new TestChatHistoryProvider())
};
// Act
ChatClientAgent agent = await client.GetAIAgentAsync(options);
// Assert
Assert.NotNull(agent);
}
/// <summary>
/// Verify that CreateChatClientAgentOptions preserves UseProvidedChatClientAsIs.
/// </summary>
[Fact]
public async Task GetAIAgentAsync_WithUseProvidedChatClientAsIs_PreservesSettingAsync()
{
// Arrange
AIProjectClient client = this.CreateTestAgentClient();
var options = new ChatClientAgentOptions
{
Name = "test-agent",
ChatOptions = new ChatOptions { Instructions = "Test" },
UseProvidedChatClientAsIs = true
};
// Act
ChatClientAgent agent = await client.GetAIAgentAsync(options);
// Assert
Assert.NotNull(agent);
}
#endregion
#region ApplyToolsToAgentDefinition Tests
/// <summary>
/// Verify that CreateAIAgentAsync with non-PromptAgentDefinition and tools throws ArgumentException.
/// </summary>
[Fact]
public async Task CreateAIAgentAsync_WithNonPromptAgentDefinitionAndTools_ThrowsArgumentExceptionAsync()
{
// Arrange
var tools = new List<AITool>
{
AIFunctionFactory.Create(() => "test", "test_function", "A test function")
};
using HttpHandlerAssert httpHandler = new(_ => new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(TestDataUtil.GetAgentVersionResponseJson(), Encoding.UTF8, "application/json")
});
#pragma warning disable CA5399
using HttpClient httpClient = new(httpHandler);
#pragma warning restore CA5399
AIProjectClient client = new(new Uri("https://test.openai.azure.com/"), new FakeAuthenticationTokenProvider(), new() { Transport = new HttpClientPipelineTransport(httpClient) });
// Create a mock AgentDefinition that is not PromptAgentDefinition
// Since we can't easily create a non-PromptAgentDefinition in the public API, we test this path via the CreateAIAgentAsync that builds a PromptAgentDefinition
// The ApplyToolsToAgentDefinition is only called when tools.Count > 0, and we provide tools
// But PromptAgentDefinition is always created by CreateAIAgentAsync(name, model, instructions, tools)
// So this path is hard to hit without mocking. Let's test the declarative function rejection instead.
var declarativeFunction = AIFunctionFactory.CreateDeclaration("test_function", "A test function", JsonDocument.Parse("{}").RootElement);
// Act & Assert
InvalidOperationException exception = await Assert.ThrowsAsync<InvalidOperationException>(() =>
client.CreateAIAgentAsync(
name: "test-agent",
model: "test-model",
instructions: "Test",
tools: [declarativeFunction]));
Assert.Contains("invokable AIFunctions", exception.Message);
}
/// <summary>
/// Verify that CreateAIAgentAsync with AIFunctionDeclaration tools throws InvalidOperationException.
/// </summary>
[Fact]
public async Task CreateAIAgentAsync_WithAIFunctionDeclarationTool_ThrowsInvalidOperationExceptionAsync()
{
// Arrange
using var doc = JsonDocument.Parse("{}");
var declarativeFunction = AIFunctionFactory.CreateDeclaration("test_function", "A test function", doc.RootElement);
using HttpHandlerAssert httpHandler = new(_ => new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(TestDataUtil.GetAgentVersionResponseJson(), Encoding.UTF8, "application/json")
});
#pragma warning disable CA5399
using HttpClient httpClient = new(httpHandler);
#pragma warning restore CA5399
AIProjectClient client = new(new Uri("https://test.openai.azure.com/"), new FakeAuthenticationTokenProvider(), new() { Transport = new HttpClientPipelineTransport(httpClient) });
// Act & Assert
InvalidOperationException exception = await Assert.ThrowsAsync<InvalidOperationException>(() =>
client.CreateAIAgentAsync(
name: "test-agent",
model: "test-model",
instructions: "Test",
tools: [declarativeFunction]));
Assert.Contains("invokable AIFunctions", exception.Message);
}
/// <summary>
/// Verify that CreateAIAgentAsync with ResponseTool converted via AsAITool works.
/// </summary>
[Fact]
public async Task CreateAIAgentAsync_WithResponseToolAsAITool_CreatesAgentSuccessfullyAsync()
{
// Arrange
ResponseTool responseTool = ResponseTool.CreateFunctionTool("response_tool", BinaryData.FromString("{}"), strictModeEnabled: false);
AITool convertedTool = responseTool.AsAITool();
// Create a definition with the function tool already in it
PromptAgentDefinition definition = new("test-model") { Instructions = "Test" };
definition.Tools.Add(responseTool);
AIProjectClient client = this.CreateTestAgentClient(agentDefinitionResponse: definition);
// Matching invokable tool must be provided
var invokableTool = AIFunctionFactory.Create(() => "test", "response_tool", "Invokable version of the tool");
var options = new ChatClientAgentOptions
{
Name = "test-agent",
ChatOptions = new ChatOptions
{
Instructions = "Test",
Tools = [invokableTool]
}
};
// Act
ChatClientAgent agent = await client.GetAIAgentAsync(options);
// Assert
Assert.NotNull(agent);
Assert.IsType<ChatClientAgent>(agent);
}
/// <summary>
/// Verify that CreateAIAgentAsync with hosted tool types works correctly.
/// </summary>
[Fact]
public async Task CreateAIAgentAsync_WithHostedToolTypes_CreatesAgentSuccessfullyAsync()
{
// Arrange
AIProjectClient client = this.CreateTestAgentClient();
var webSearchTool = new HostedWebSearchTool();
var options = new ChatClientAgentOptions
{
Name = "test-agent",
ChatOptions = new ChatOptions
{
Instructions = "Test",
Tools = [webSearchTool]
}
};
// Act
ChatClientAgent agent = await client.CreateAIAgentAsync("test-model", options);
// Assert
Assert.NotNull(agent);
Assert.IsType<ChatClientAgent>(agent);
}
/// <summary>
/// Verify that when the server returns tools but matching tools are provided, the agent is created.
/// </summary>
[Fact]
public async Task GetAIAgentAsync_WithServerDefinedToolsAndMatchingProvidedTools_CreatesAgentAsync()
{
// Arrange
PromptAgentDefinition definition = new("test-model") { Instructions = "Test" };
// Add multiple function tools
definition.Tools.Add(ResponseTool.CreateFunctionTool("tool_one", BinaryData.FromString("{}"), strictModeEnabled: false));
definition.Tools.Add(ResponseTool.CreateFunctionTool("tool_two", BinaryData.FromString("{}"), strictModeEnabled: false));
AIProjectClient client = this.CreateTestAgentClient(agentDefinitionResponse: definition);
var tools = new List<AITool>
{
AIFunctionFactory.Create(() => "one", "tool_one", "Tool one"),
AIFunctionFactory.Create(() => "two", "tool_two", "Tool two")
};
var options = new ChatClientAgentOptions
{
Name = "test-agent",
ChatOptions = new ChatOptions
{
Instructions = "Test",
Tools = tools
}
};
// Act
ChatClientAgent agent = await client.GetAIAgentAsync(options);
// Assert
Assert.NotNull(agent);
Assert.IsType<ChatClientAgent>(agent);
}
/// <summary>
/// Verify that when the server returns mixed tools (function and hosted), the agent handles them correctly.
/// </summary>
[Fact]
public async Task GetAIAgentAsync_WithMixedServerTools_MatchesFunctionToolsOnlyAsync()
{
// Arrange
PromptAgentDefinition definition = new("test-model") { Instructions = "Test" };
// Add a function tool
definition.Tools.Add(ResponseTool.CreateFunctionTool("function_tool", BinaryData.FromString("{}"), strictModeEnabled: false));
// Add a hosted tool
definition.Tools.Add(new HostedWebSearchTool().GetService<ResponseTool>() ?? new HostedWebSearchTool().AsOpenAIResponseTool());
AIProjectClient client = this.CreateTestAgentClient(agentDefinitionResponse: definition);
var tools = new List<AITool>
{
AIFunctionFactory.Create(() => "result", "function_tool", "The function tool")
};
var options = new ChatClientAgentOptions
{
Name = "test-agent",
ChatOptions = new ChatOptions
{
Instructions = "Test",
Tools = tools
}
};
// Act
ChatClientAgent agent = await client.GetAIAgentAsync(options);
// Assert
Assert.NotNull(agent);
Assert.IsType<ChatClientAgent>(agent);
}
/// <summary>
/// Verify that when partial tools are provided (some missing), InvalidOperationException is thrown listing missing tools.
/// </summary>
[Fact]
public async Task GetAIAgentAsync_WithPartialToolsProvided_ThrowsInvalidOperationWithMissingToolNamesAsync()
{
// Arrange
PromptAgentDefinition definition = new("test-model") { Instructions = "Test" };
definition.Tools.Add(ResponseTool.CreateFunctionTool("provided_tool", BinaryData.FromString("{}"), strictModeEnabled: false));
definition.Tools.Add(ResponseTool.CreateFunctionTool("missing_tool", BinaryData.FromString("{}"), strictModeEnabled: false));
AIProjectClient client = this.CreateTestAgentClient(agentDefinitionResponse: definition);
var tools = new List<AITool>
{
// Only providing one of two required tools
AIFunctionFactory.Create(() => "result", "provided_tool", "The provided tool")
};
var options = new ChatClientAgentOptions
{
Name = "test-agent",
ChatOptions = new ChatOptions
{
Instructions = "Test",
Tools = tools
}
};
// Act & Assert
InvalidOperationException exception = await Assert.ThrowsAsync<InvalidOperationException>(() =>
client.GetAIAgentAsync(options));
Assert.Contains("missing_tool", exception.Message);
Assert.DoesNotContain("provided_tool", exception.Message);
}
/// <summary>
/// Verify that when AsAIAgent is called without requireInvocableTools, hosted tools are correctly added.
/// </summary>
[Fact]
public void AsAIAgent_WithServerHostedTools_AddsToolsToAgentOptions()
{
// Arrange
PromptAgentDefinition definition = new("test-model") { Instructions = "Test" };
definition.Tools.Add(new HostedWebSearchTool().GetService<ResponseTool>() ?? new HostedWebSearchTool().AsOpenAIResponseTool());
AIProjectClient client = this.CreateTestAgentClient();
AgentVersion agentVersion = ModelReaderWriter.Read<AgentVersion>(BinaryData.FromString(TestDataUtil.GetAgentVersionResponseJson(agentDefinition: definition)))!;
// Act - no tools provided, but requireInvocableTools is false when no tools param is passed
ChatClientAgent agent = client.AsAIAgent(agentVersion);
// Assert
Assert.NotNull(agent);
Assert.IsType<ChatClientAgent>(agent);
}
#endregion
#region Helper Methods
/// <summary>
@@ -2056,6 +2913,49 @@ public sealed class AzureAIProjectChatClientExtensionsTests
return chatOptionsProperty?.GetValue(agent) as ChatOptions;
}
/// <summary>
/// Test schema for JSON response format tests.
/// </summary>
#pragma warning disable CA1812 // Avoid uninstantiated internal classes - used via reflection by AIJsonUtilities
private sealed class TestSchema
{
public string? Name { get; set; }
public int Value { get; set; }
}
#pragma warning restore CA1812
/// <summary>
/// Test AIContextProvider for options preservation tests.
/// </summary>
private sealed class TestAIContextProvider : AIContextProvider
{
public override ValueTask<AIContext> InvokingAsync(InvokingContext context, CancellationToken cancellationToken = default)
{
return new ValueTask<AIContext>(new AIContext());
}
}
/// <summary>
/// Test ChatHistoryProvider for options preservation tests.
/// </summary>
private sealed class TestChatHistoryProvider : ChatHistoryProvider
{
public override ValueTask<IEnumerable<ChatMessage>> InvokingAsync(InvokingContext context, CancellationToken cancellationToken = default)
{
return new ValueTask<IEnumerable<ChatMessage>>(Array.Empty<ChatMessage>());
}
public override ValueTask InvokedAsync(InvokedContext context, CancellationToken cancellationToken = default)
{
return default;
}
public override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
{
return default;
}
}
}
/// <summary>