mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
fb97e93a01
* Foundry.Hosting.UnitTests: extract project from Foundry.UnitTests Move all Hosting/* tests, three toolbox TestData JSONs, and the FakeAuthenticationTokenProvider/HttpHandlerAssert/TestDataUtil helpers (trimmed to toolbox getters) into a new Microsoft.Agents.AI.Foundry.Hosting.UnitTests project. Add it to the slnx and grant the new assembly InternalsVisibleTo from Microsoft.Agents.AI.Foundry and Microsoft.Agents.AI.Foundry.Hosting. * Foundry.Hosting.UnitTests: align namespaces to assembly name Rename namespaces from Microsoft.Agents.AI.Foundry.UnitTests(.Hosting) to Microsoft.Agents.AI.Foundry.Hosting.UnitTests across all moved tests, the duplicated helpers, and the trimmed TestDataUtil. Also fixes the prior namespace inconsistency in FoundryToolboxTests. * Foundry.Hosting.UnitTests: split WorkflowIntegrationTests by SUT Replace the WorkflowIntegrationTests file (an IT-named file inside a UT project) with two SUT-focused files plus a shared test-doubles file: - AgentFrameworkResponseHandlerWorkflowTests.cs - the 5 handler-driven tests that exercise AgentFrameworkResponseHandler with a real workflow agent. - OutputConverterWorkflowTests.cs - the 5 OutputConverter tests driven by hand-crafted update sequences mirroring real workflow patterns. - WorkflowTestAgents.cs - StreamingTextAgent and ThrowingStreamingAgent extracted as internal types used by both files. * Foundry.UnitTests: trim Hosting-related conditionals and dead testdata Now that Hosting tests live in their own project: - drop the Compile Remove guard for the Hosting subfolder, - drop the .NETCoreApp-only PackageReferences (Azure.AI.AgentServer.Responses, Microsoft.AspNetCore.TestHost, OpenTelemetry, OpenTelemetry.Exporter.InMemory), - drop the conditional ProjectReference to Microsoft.Agents.AI.Foundry.Hosting, - delete the three Toolbox JSON files and the matching Toolbox getters in TestDataUtil. * Foundry.Hosting.UnitTests: drop redundant 'using Microsoft.Agents.AI.Foundry.Hosting' The new project namespace is Microsoft.Agents.AI.Foundry.Hosting.UnitTests, which already brings the parent Microsoft.Agents.AI.Foundry.Hosting namespace into scope. The explicit using statement is therefore redundant (IDE0005). Caught by 'dotnet format --verify-no-changes' running on Linux against the .NET 10 SDK. * Foundry.Hosting: drop InternalsVisibleTo to Foundry.UnitTests The non-hosting Foundry.UnitTests project no longer holds any Hosting tests after the split, so it doesn't need access to internal types in Microsoft.Agents.AI.Foundry.Hosting. Only Microsoft.Agents.AI.Foundry.Hosting.UnitTests needs it. * Foundry.Hosting: rename DelegatingResponsesClient to UserAgentResponsesClient Address westey-m's review feedback on PR #5453: `Delegating*` is conventionally reserved for inheritable base classes (mirroring `DelegatingHandler`) where consumers override one or two members. This polyfill is sealed and only injects the User-Agent supplement, so the new name reflects its actual purpose. Renamed via `git mv` to preserve history: * `src/Microsoft.Agents.AI.Foundry.Hosting/DelegatingResponsesClient.cs` to `UserAgentResponsesClient.cs` * `tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/DelegatingResponsesClientTests.cs` to `UserAgentResponsesClientTests.cs` Class, constructor, and all references updated across: * `src/.../UserAgentResponsesClient.cs` (class + constructor + internal log message) * `src/.../ServiceCollectionExtensions.cs` (cref + type check + instantiation) * `src/.../HostedAgentUserAgentPolicy.cs` (cref) * `tests/Foundry.UnitTests/RequestOptionsExtensionsTests.cs` (comment) * `tests/Foundry.Hosting.UnitTests/UserAgentResponsesClientTests.cs` (class + cref + instantiations)
116 lines
4.5 KiB
C#
116 lines
4.5 KiB
C#
// Copyright (c) Microsoft. All rights reserved.
|
|
|
|
using System.ClientModel.Primitives;
|
|
using System.Net;
|
|
using System.Net.Http;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace Microsoft.Agents.AI.Foundry.UnitTests;
|
|
|
|
/// <summary>
|
|
/// Verifies the per-call <c>MeaiUserAgentPolicy</c> exposed via
|
|
/// <see cref="RequestOptionsExtensions.UserAgentPolicy"/>. The policy is reachable through the
|
|
/// public <see cref="FoundryAgent"/> constructors (which add it to the internally-built
|
|
/// <see cref="Azure.AI.Projects.AIProjectClient"/>'s pipeline), so its behavior is part of the
|
|
/// public API surface.
|
|
/// </summary>
|
|
public sealed class RequestOptionsExtensionsTests
|
|
{
|
|
[Fact]
|
|
public async Task MeaiUserAgentPolicy_AddsMeaiSegment_ToOutgoingRequestAsync()
|
|
{
|
|
// Arrange
|
|
using var handler = new RecordingHandler();
|
|
#pragma warning disable CA5399
|
|
using var httpClient = new HttpClient(handler);
|
|
#pragma warning restore CA5399
|
|
var pipeline = ClientPipeline.Create(
|
|
new ClientPipelineOptions { Transport = new HttpClientPipelineTransport(httpClient) },
|
|
perCallPolicies: [RequestOptionsExtensions.UserAgentPolicy],
|
|
perTryPolicies: default,
|
|
beforeTransportPolicies: default);
|
|
|
|
// Act
|
|
var message = pipeline.CreateMessage();
|
|
message.Request.Method = "POST";
|
|
message.Request.Uri = new System.Uri("https://example.test/anything");
|
|
await pipeline.SendAsync(message);
|
|
|
|
// Assert
|
|
Assert.Equal(1, handler.Count);
|
|
Assert.NotNull(handler.LastUserAgent);
|
|
Assert.Contains("MEAI/", handler.LastUserAgent);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task MeaiUserAgentPolicy_DoesNotAddFoundryHostingSegmentAsync()
|
|
{
|
|
// Arrange
|
|
using var handler = new RecordingHandler();
|
|
#pragma warning disable CA5399
|
|
using var httpClient = new HttpClient(handler);
|
|
#pragma warning restore CA5399
|
|
var pipeline = ClientPipeline.Create(
|
|
new ClientPipelineOptions { Transport = new HttpClientPipelineTransport(httpClient) },
|
|
perCallPolicies: [RequestOptionsExtensions.UserAgentPolicy],
|
|
perTryPolicies: default,
|
|
beforeTransportPolicies: default);
|
|
|
|
// Act
|
|
var message = pipeline.CreateMessage();
|
|
message.Request.Method = "POST";
|
|
message.Request.Uri = new System.Uri("https://example.test/anything");
|
|
await pipeline.SendAsync(message);
|
|
|
|
// Assert: the policy is MEAI-only; the foundry-hosting supplement is added elsewhere
|
|
// (by the polyfill UserAgentResponsesClient → HostedAgentUserAgentPolicy).
|
|
Assert.NotNull(handler.LastUserAgent);
|
|
Assert.DoesNotContain("foundry-hosting/agent-framework-dotnet", handler.LastUserAgent);
|
|
}
|
|
|
|
[Fact]
|
|
public void UserAgentPolicy_ExposesSingletonInstance()
|
|
{
|
|
// Two reads of the static property must return the same instance — the policy is stateless and shared.
|
|
var first = RequestOptionsExtensions.UserAgentPolicy;
|
|
var second = RequestOptionsExtensions.UserAgentPolicy;
|
|
Assert.Same(first, second);
|
|
}
|
|
|
|
[Fact]
|
|
public void MeaiUserAgentPolicy_ValueIncludesAFFoundryAssemblyVersion_ReflectionGuard()
|
|
{
|
|
// The policy emits "MEAI/{Microsoft.Agents.AI.Foundry assembly InformationalVersion}".
|
|
// If the assembly metadata stops being readable, the policy falls back to "MEAI" without a version,
|
|
// which is a measurable telemetry regression.
|
|
var attr = typeof(RequestOptionsExtensions).Assembly
|
|
.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
|
|
Assert.NotNull(attr);
|
|
Assert.False(string.IsNullOrEmpty(attr!.InformationalVersion));
|
|
}
|
|
|
|
private sealed class RecordingHandler : HttpClientHandler
|
|
{
|
|
public int Count { get; private set; }
|
|
public string? LastUserAgent { get; private set; }
|
|
|
|
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
|
{
|
|
this.Count++;
|
|
this.LastUserAgent = request.Headers.TryGetValues("User-Agent", out var values)
|
|
? string.Join(",", values)
|
|
: null;
|
|
|
|
var resp = new HttpResponseMessage(HttpStatusCode.OK)
|
|
{
|
|
Content = new StringContent("{}", Encoding.UTF8, "application/json"),
|
|
RequestMessage = request,
|
|
};
|
|
return Task.FromResult(resp);
|
|
}
|
|
}
|
|
}
|