Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
address review: document statelessness + tighten opt-out assertion
- Document that both handler types inserted by TUnitHttpClientFilter must
  remain stateless/thread-safe because IHttpClientFactory caches pipelines
  and shares handler instances across concurrent parallel-test requests.
- Explain the outermost-insert intent so a future refactor doesn't reverse
  the order.
- Opt-out test now also asserts the downstream hop does not carry baggage,
  mirroring the positive test.
  • Loading branch information
thomhurst committed Apr 17, 2026
commit e4898e9dad6d256e3e56e10ad58501769e466cdb
10 changes: 10 additions & 0 deletions TUnit.AspNetCore.Core/Http/TUnitHttpClientFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,23 @@ namespace TUnit.AspNetCore.Http;
/// to every <see cref="System.Net.Http.IHttpClientFactory"/> handler pipeline built in the SUT.
/// Ensures outbound HTTP calls made via <c>AddHttpClient&lt;T&gt;()</c>, named, or typed clients
/// carry the current test's <c>traceparent</c>, <c>baggage</c>, and <c>X-TUnit-TestId</c> headers.
/// <para>
/// Both handler types must remain stateless and thread-safe: <see cref="System.Net.Http.IHttpClientFactory"/>
/// caches the built pipeline and shares the same handler instances across every request on a given
/// named client, including concurrent requests from parallel tests. Per-test correlation comes from
/// <see cref="TUnit.Core.TestContext.Current"/> and <see cref="System.Diagnostics.Activity.Current"/>,
/// which are async-local — do not add instance fields capturing per-request state to either handler.
/// </para>
/// </summary>
internal sealed class TUnitHttpClientFilter : IHttpMessageHandlerBuilderFilter
{
public Action<HttpMessageHandlerBuilder> Configure(Action<HttpMessageHandlerBuilder> next) =>
builder =>
{
next(builder);
// Insert at outermost positions so TUnit headers are emitted before any
// SUT-registered handler can run. Order must stay ActivityPropagationHandler
// first (writes traceparent/baggage) then TUnitTestIdHandler (writes X-TUnit-TestId).
builder.AdditionalHandlers.Insert(0, new ActivityPropagationHandler());
builder.AdditionalHandlers.Insert(1, new TUnitTestIdHandler());
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,6 @@ public async Task SutHttpClientFactory_DoesNotPropagate_WhenAutoPropagationDisab

await Assert.That(echoed).DoesNotContain(TUnitTestIdHandler.HeaderName);
await Assert.That(echoed).DoesNotContain("traceparent:");
await Assert.That(echoed).DoesNotContain("baggage:");
}
}
Loading