mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
.NET: Fix flaky declarative test (#5669)
* Fix flaky declarative test * Addressed host gating and guid parsing concerns in test file.
This commit is contained in:
committed by
GitHub
Unverified
parent
b12109b7e4
commit
65455751a4
+70
-11
@@ -4,14 +4,19 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Azure.Core;
|
||||
using Microsoft.Agents.AI.Workflows.Declarative.Events;
|
||||
using Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests.Agents;
|
||||
using Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests.Framework;
|
||||
using Microsoft.Agents.AI.Workflows.Declarative.Kit;
|
||||
using Microsoft.Agents.AI.Workflows.Declarative.Mcp;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Shared.IntegrationTests;
|
||||
|
||||
namespace Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests;
|
||||
|
||||
@@ -48,9 +53,9 @@ public sealed class InvokeToolWorkflowTest(ITestOutputHelper output) : Integrati
|
||||
#region InvokeHttpRequest Tests
|
||||
|
||||
[RetryTheory(3, 5000)]
|
||||
[InlineData("HttpRequest.yaml", "visibility: public")]
|
||||
public Task ValidateHttpRequestAsync(string workflowFileName, string? expectedResultContains) =>
|
||||
this.RunHttpRequestTestAsync(workflowFileName, expectedResultContains);
|
||||
[InlineData("HttpRequest.yaml")]
|
||||
public Task ValidateHttpRequestAsync(string workflowFileName) =>
|
||||
this.RunHttpRequestTestAsync(workflowFileName);
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -261,16 +266,65 @@ public sealed class InvokeToolWorkflowTest(ITestOutputHelper output) : Integrati
|
||||
|
||||
#region InvokeHttpRequest Test Helpers
|
||||
|
||||
/// <summary>
|
||||
/// The Azure ARM scope used to acquire bearer tokens for the HttpRequestAction
|
||||
/// integration test. Matches the URL configured in <c>HttpRequest.yaml</c>.
|
||||
/// </summary>
|
||||
private const string ArmScope = "https://management.azure.com/.default";
|
||||
|
||||
/// <summary>
|
||||
/// The expected ARM endpoint. Only requests whose absolute URL exactly matches
|
||||
/// this scheme and host receive the authenticated <see cref="HttpClient"/>; all
|
||||
/// other URLs (including subdomain look-alikes such as
|
||||
/// <c>https://management.azure.com.evil.com</c>) fall through to the handler
|
||||
/// default and never see the bearer token.
|
||||
/// </summary>
|
||||
private static readonly Uri s_armEndpoint = new("https://management.azure.com/");
|
||||
|
||||
/// <summary>
|
||||
/// Runs an HttpRequestAction workflow test with the specified configuration.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The workflow under test calls an authenticated Azure ARM endpoint. We acquire a
|
||||
/// single bearer token via the same Azure CLI credential used elsewhere in the
|
||||
/// integration test suite, attach it to a cached <see cref="HttpClient"/>, and route
|
||||
/// matching requests through that client via <see cref="DefaultHttpRequestHandler"/>'s
|
||||
/// <c>httpClientProvider</c> callback. The test owns the <see cref="HttpClient"/>'s
|
||||
/// lifetime and disposes it explicitly — <see cref="DefaultHttpRequestHandler"/> does
|
||||
/// not dispose provider-returned clients.
|
||||
/// </remarks>
|
||||
private async Task RunHttpRequestTestAsync(
|
||||
string workflowFileName,
|
||||
string? expectedResultContains = null)
|
||||
string workflowFileName)
|
||||
{
|
||||
// Arrange
|
||||
string workflowPath = GetWorkflowPath(workflowFileName);
|
||||
await using DefaultHttpRequestHandler httpRequestHandler = new();
|
||||
|
||||
AccessToken accessToken =
|
||||
await TestAzureCliCredentials
|
||||
.CreateAzureCliCredential()
|
||||
.GetTokenAsync(new TokenRequestContext([ArmScope]), CancellationToken.None)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
using HttpClient authenticatedClient = new();
|
||||
authenticatedClient.DefaultRequestHeaders.Authorization =
|
||||
new AuthenticationHeaderValue("Bearer", accessToken.Token);
|
||||
|
||||
await using DefaultHttpRequestHandler httpRequestHandler =
|
||||
new(httpClientProvider: (request, _) =>
|
||||
{
|
||||
if (Uri.TryCreate(request.Url, UriKind.Absolute, out Uri? requestUri) &&
|
||||
string.Equals(requestUri.Scheme, s_armEndpoint.Scheme, StringComparison.OrdinalIgnoreCase) &&
|
||||
string.Equals(requestUri.Host, s_armEndpoint.Host, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
#pragma warning disable CA2025 // authenticatedClient outlives the handler (LIFO using disposal) and the workflow awaits all dispatches.
|
||||
return Task.FromResult<HttpClient?>(authenticatedClient);
|
||||
#pragma warning restore CA2025
|
||||
}
|
||||
|
||||
// Fall back to the handler's internal client for any non-ARM URLs.
|
||||
return Task.FromResult<HttpClient?>(null);
|
||||
});
|
||||
|
||||
DeclarativeWorkflowOptions workflowOptions = await this.CreateOptionsAsync(
|
||||
externalConversation: false,
|
||||
httpRequestHandler: httpRequestHandler);
|
||||
@@ -284,11 +338,16 @@ public sealed class InvokeToolWorkflowTest(ITestOutputHelper output) : Integrati
|
||||
// Assert - Verify executor and action events
|
||||
AssertWorkflowEventsEmitted(workflowEvents);
|
||||
|
||||
// Assert - Verify expected result if specified
|
||||
if (expectedResultContains is not null)
|
||||
{
|
||||
AssertResultContains(workflowEvents, expectedResultContains);
|
||||
}
|
||||
MessageActivityEvent? messageEvent = workflowEvents.Events
|
||||
.OfType<MessageActivityEvent>()
|
||||
.LastOrDefault();
|
||||
|
||||
Assert.NotNull(messageEvent);
|
||||
Assert.NotNull(messageEvent.Message);
|
||||
Assert.True(
|
||||
Guid.TryParse(messageEvent.Message, out Guid retrievedTenantId),
|
||||
$"Expected the SendMessage payload to be a tenant GUID, but got: '{messageEvent.Message}'");
|
||||
Assert.NotEqual(Guid.Empty, retrievedTenantId);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
+15
-16
@@ -1,6 +1,10 @@
|
||||
#
|
||||
# This workflow tests invoking HttpRequestAction end-to-end.
|
||||
# Uses the public GitHub API (unauthenticated) to fetch repo metadata.
|
||||
# Uses the Azure ARM tenants endpoint, which is authenticated, fully static, and
|
||||
# reachable with the credentials the integration test pipeline already provides
|
||||
# (via az login). The bearer token is supplied by the test through a custom
|
||||
# HttpClient passed to DefaultHttpRequestHandler; the YAML deliberately does not
|
||||
# carry an Authorization header.
|
||||
#
|
||||
kind: Workflow
|
||||
trigger:
|
||||
@@ -9,24 +13,19 @@ trigger:
|
||||
id: workflow_http_request_test
|
||||
actions:
|
||||
|
||||
# Set the repo owner used to form the request URL.
|
||||
- kind: SetVariable
|
||||
id: set_repo_owner
|
||||
variable: Local.RepoOwner
|
||||
value: dotnet
|
||||
|
||||
# Invoke the GitHub repo API.
|
||||
# Invoke the Azure ARM tenants list API.
|
||||
- kind: HttpRequestAction
|
||||
id: fetch_repo_info
|
||||
id: fetch_tenants
|
||||
conversationId: =System.ConversationId
|
||||
method: GET
|
||||
url: =Concatenate("https://api.github.com/repos/", Local.RepoOwner, "/runtime")
|
||||
url: https://management.azure.com/tenants?api-version=2022-09-01
|
||||
headers:
|
||||
Accept: application/vnd.github+json
|
||||
User-Agent: agent-framework-integration-test
|
||||
response: Local.RepoInfo
|
||||
Accept: application/json
|
||||
response: Local.TenantsResponse
|
||||
|
||||
# Surface the Repo visibility field from the parsed JSON response.
|
||||
# Surface the first tenant id from the parsed JSON response. Every
|
||||
# authenticated principal belongs to at least one tenant, so this path
|
||||
# always resolves on a successful call.
|
||||
- kind: SendMessage
|
||||
id: show_visibility
|
||||
message: "visibility: {Local.RepoInfo.visibility}"
|
||||
id: show_first_tenant
|
||||
message: "{First(Local.TenantsResponse.value).tenantId}"
|
||||
|
||||
Reference in New Issue
Block a user