Skip to content

Test: failing regression guard for custom IMsalHttpClientFactory bypass (#6124) - #6160

Closed
Gladwin Johnson (gladjohn) wants to merge 1 commit into
mainfrom
gladjohn/mi-transport-regression-test
Closed

Test: failing regression guard for custom IMsalHttpClientFactory bypass (#6124)#6160
Gladwin Johnson (gladjohn) wants to merge 1 commit into
mainfrom
gladjohn/mi-transport-regression-test

Conversation

@gladjohn

Copy link
Copy Markdown
Contributor

Summary

Adds an end-to-end transport-selection regression test for the custom-IMsalHttpClientFactory bypass reported in #6124 (a recurrence of #5286: "HttpClientTransport setting is not applied after MSAL upgraded").

⚠️ Draft: this test intentionally FAILS on main. It is the reproduction; the product fix will follow in this PR.

What the test does

HttpClientTransportRegressionTests.CustomHttpClientFactory_MustNotBeBypassed_WhenServerCertificateValidationCallbackPresentAsync:

  • Drives a real HttpManager (not MockHttpManager) with a plain custom IMsalHttpClientFactory — implementing only the normal interface, exactly the shape Azure Identity's HttpClientTransport surfaces as (not IMsalSFHttpClientFactory / IMsalMtlsHttpClientFactory).
  • Sends a request carrying a server-certificate validation callback (the Service Fabric managed-identity path).
  • Asserts the configured factory is actually used (GetHttpClient invocation count + request count), per the repo guidance that transport/pooling regression tests must assert factory-invocation counts.

Why the existing coverage missed it

  • The #5292 regression test only asserts an implementation detail (SF returns a callback; other sources return null); it never executes a request to verify the caller's factory is used.
  • IMDSv2 tests drive the flow through MockHttpManager, whose mock factory implements every factory interface — so they can't catch a path that discards a caller supplying only the normal factory interface.

Root cause the test pins

HttpManager.GetHttpClient fabricates a fresh HttpClient when a validation callback is present and the factory is not IMsalSFHttpClientFactory:

return new HttpClient(new HttpClientHandler
{
    ServerCertificateCustomValidationCallback = validateServerCert
});

This discards the caller's configured transport (proxy, custom RootCAs, dialer, tracing).

Next step

Product fix so a caller-supplied IMsalHttpClientFactory is never discarded (attach the validation callback to a handler derived from the caller's factory / route SF through IMsalSFHttpClientFactory only), which flips this test green.

Related: #6124, #5286, #5292.

…being bypassed (#6124)

Adds an end-to-end transport-selection regression test that drives a real HttpManager
with a plain custom IMsalHttpClientFactory (the shape Azure Identity's HttpClientTransport
surfaces as) and asserts the caller's factory is actually used.

This reproduces the transport bypass behind issue #6124 (recurrence of #5286): when a
request carries a server-certificate validation callback (the Service Fabric managed
identity path) and the factory is not an IMsalSFHttpClientFactory, HttpManager fabricates
its own HttpClient and discards the caller's configured transport.

The test intentionally FAILS on current code; the fix follows.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: e65d13d3-9f55-4d28-8224-6b08ff1bd85b
Copilot AI lite review requested due to automatic review settings August 11, 2026 20:22

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new end-to-end regression test to reproduce and guard against the Managed Identity transport-selection bug where MSAL bypasses a caller-supplied IMsalHttpClientFactory when a server-certificate validation callback is present (issues #6124 / #5286). This test targets the HttpManager.GetHttpClient behavior to ensure custom transports (e.g., Azure Identity HttpClientTransport) are not silently discarded.

Changes:

  • Introduces HttpClientTransportRegressionTests.CustomHttpClientFactory_MustNotBeBypassed_WhenServerCertificateValidationCallbackPresentAsync.
  • Implements a minimal IMsalHttpClientFactory plus a tracking HttpMessageHandler to assert factory invocation and request flow.
  • Drives a real HttpManager (not MockHttpManager) to reproduce the bypass path.
Suppressed comments (1)

tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/HttpClientTransportRegressionTests.cs:130

  • RequestCount is used as an assertion signal; incrementing a non-atomic property can be racy if requests ever occur concurrently (e.g., future retries/parallelization). Use Interlocked/Volatile for a thread-safe counter.
            public int RequestCount { get; private set; }

            protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
            {
                RequestCount++;
                return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +56 to +73
Exception thrown = null;

// Act
try
{
await httpManager.SendRequestAsync(
// Unroutable endpoint: only ever contacted if MSAL bypasses the tracking factory and
// creates its own HttpClient. Connection is refused immediately (no external network).
endpoint: new Uri("https://127.0.0.1:1/token"),
headers: new Dictionary<string, string>(),
body: null,
method: HttpMethod.Get,
logger: Substitute.For<ILoggerAdapter>(),
doNotThrow: true,
bindingCertificate: null,
validateServerCert: validateServerCert,
cancellationToken: CancellationToken.None,
retryPolicy: new TestDefaultRetryPolicy(RequestType.STS))
Comment on lines +104 to +117
private readonly HttpClient _httpClient;

public int GetHttpClientCallCount { get; private set; }

public PlainTrackingHttpClientFactory(HttpMessageHandler handler)
{
_httpClient = new HttpClient(handler);
}

public HttpClient GetHttpClient()
{
GetHttpClientCallCount++;
return _httpClient;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants