Skip to content

Commit a8a8350

Browse files
test(csharp): simplify retry telemetry tests
Collapses the two Retry-After tests (503 and 429) into a single Theory parameterized by status code, since they exercise the same code path. Trims the verbose Assert.True(tag.Key == "...", "long message") pattern in favor of direct dictionary lookups and Assert.Equal. Net: ~150 lines -> ~50 lines; 3 telemetry tests still pass. Co-authored-by: Isaac
1 parent dc6cd7a commit a8a8350

1 file changed

Lines changed: 28 additions & 127 deletions

File tree

csharp/test/Unit/RetryHttpHandlerTest.cs

Lines changed: 28 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -723,155 +723,56 @@ public IReadOnlyList<Activity> StoppedActivities
723723

724724
/// <summary>
725725
/// Issue #479: when the first attempt succeeds, no `retry.attempt`
726-
/// events should be emitted (there were no retries).
726+
/// events fire.
727727
/// </summary>
728728
[Fact]
729-
public async Task RetryTelemetry_NoRetryNeeded_EmitsNoAttemptEvents_Issue479()
729+
public async Task RetryTelemetry_NoRetry_EmitsNoEvents_Issue479()
730730
{
731731
using var capture = new ActivityCapture("TestSource");
732+
var mockHandler = new MockHttpMessageHandler(new HttpResponseMessage(HttpStatusCode.OK));
733+
var retryHandler = new RetryHttpHandler(mockHandler, new MockActivityTracer(), 5, 5, true, true);
732734

733-
var mockHandler = new MockHttpMessageHandler(
734-
new HttpResponseMessage(HttpStatusCode.OK)
735-
{
736-
Content = new StringContent("Success")
737-
});
738-
739-
var mockTracer = new MockActivityTracer();
740-
var retryHandler = new RetryHttpHandler(mockHandler, mockTracer, 5, 5, true, true);
741-
742-
var httpClient = new HttpClient(retryHandler);
743-
var response = await httpClient.GetAsync("http://test.com");
744-
745-
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
746-
Assert.Equal(1, mockHandler.RequestCount);
747-
748-
Activity? sendAsyncActivity = capture.StoppedActivities
749-
.FirstOrDefault(a => a.OperationName == "SendAsync");
750-
Assert.NotNull(sendAsyncActivity);
735+
await new HttpClient(retryHandler).GetAsync("http://test.com");
751736

752-
var retryEvents = sendAsyncActivity!.Events.Where(e => e.Name == "retry.attempt").ToList();
753-
Assert.Empty(retryEvents);
737+
Activity sendAsync = capture.StoppedActivities.Single(a => a.OperationName == "SendAsync");
738+
Assert.DoesNotContain(sendAsync.Events, e => e.Name == "retry.attempt");
754739
}
755740

756741
/// <summary>
757-
/// Issue #479: each retry triggered by a Retry-After 503 response must
758-
/// emit a `retry.attempt` event carrying `attempt_number`, `delay_ms`,
759-
/// and a `reason` describing why the retry was scheduled.
742+
/// Issue #479: each retry triggered by a Retry-After response emits a
743+
/// `retry.attempt` event with attempt_number / delay_ms / reason. The
744+
/// reason string must reference the status code so 429 throttles are
745+
/// distinguishable from 503s.
760746
/// </summary>
761-
[Fact]
762-
public async Task RetryTelemetry_RetryAfter503_EmitsAttemptEvents_Issue479()
747+
[Theory]
748+
[InlineData(HttpStatusCode.ServiceUnavailable, "503")]
749+
[InlineData((HttpStatusCode)429, "429")]
750+
public async Task RetryTelemetry_RetryAfter_EmitsAttemptEvents_Issue479(HttpStatusCode status, string expectedReasonSubstring)
763751
{
764752
using var capture = new ActivityCapture("TestSource");
765753

766-
var mockHandler = new MockHttpMessageHandler(
767-
new HttpResponseMessage(HttpStatusCode.ServiceUnavailable)
768-
{
769-
Headers = { { "Retry-After", "1" } },
770-
Content = new StringContent("Service Unavailable")
771-
});
772-
773-
// Succeed after 2 503 responses (i.e. 2 retries).
774-
mockHandler.SetResponseAfterRetryCount(2, new HttpResponseMessage(HttpStatusCode.OK)
754+
var mockHandler = new MockHttpMessageHandler(new HttpResponseMessage(status)
775755
{
776-
Content = new StringContent("Success")
756+
Headers = { { "Retry-After", "1" } }
777757
});
758+
mockHandler.SetResponseAfterRetryCount(2, new HttpResponseMessage(HttpStatusCode.OK));
778759

779-
var mockTracer = new MockActivityTracer();
780-
var retryHandler = new RetryHttpHandler(mockHandler, mockTracer, 10, 10, true, true);
760+
var retryHandler = new RetryHttpHandler(mockHandler, new MockActivityTracer(), 10, 10, true, true);
761+
await new HttpClient(retryHandler).GetAsync("http://test.com");
781762

782-
var httpClient = new HttpClient(retryHandler);
783-
var response = await httpClient.GetAsync("http://test.com");
763+
Activity sendAsync = capture.StoppedActivities.Single(a => a.OperationName == "SendAsync");
764+
var events = sendAsync.Events.Where(e => e.Name == "retry.attempt").ToList();
765+
Assert.Equal(2, events.Count);
784766

785-
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
786-
Assert.Equal(3, mockHandler.RequestCount); // initial + 2 retries
787-
788-
Activity? sendAsyncActivity = capture.StoppedActivities
789-
.FirstOrDefault(a => a.OperationName == "SendAsync");
790-
Assert.NotNull(sendAsyncActivity);
791-
792-
// We retried twice → two retry.attempt events.
793-
var retryEvents = sendAsyncActivity!.Events
794-
.Where(e => e.Name == "retry.attempt")
795-
.ToList();
796-
Assert.Equal(2, retryEvents.Count);
797-
798-
// Every retry.attempt event must carry attempt_number, delay_ms,
799-
// and reason. attempt_number must be monotonically increasing
800-
// starting at 1 (the first retry is the 1st *retry*, not the 0th).
801-
for (int i = 0; i < retryEvents.Count; i++)
767+
for (int i = 0; i < events.Count; i++)
802768
{
803-
var evt = retryEvents[i];
804-
var tags = evt.Tags.ToList();
805-
806-
var attemptTag = tags.FirstOrDefault(t => t.Key == "attempt_number");
807-
Assert.True(attemptTag.Key == "attempt_number",
808-
$"retry.attempt event #{i} missing attempt_number. Tags: [" +
809-
string.Join(", ", tags.Select(t => $"{t.Key}={t.Value}")) + "]");
810-
Assert.Equal(i + 1, Convert.ToInt32(attemptTag.Value));
811-
812-
var delayTag = tags.FirstOrDefault(t => t.Key == "delay_ms");
813-
Assert.True(delayTag.Key == "delay_ms",
814-
$"retry.attempt event #{i} missing delay_ms. Tags: [" +
815-
string.Join(", ", tags.Select(t => $"{t.Key}={t.Value}")) + "]");
816-
// Retry-After of "1" → 1000ms.
817-
Assert.True(Convert.ToInt64(delayTag.Value) >= 1000,
818-
$"retry.attempt event #{i} delay_ms should be >= 1000 (Retry-After: 1). " +
819-
$"Got: {delayTag.Value}");
820-
821-
var reasonTag = tags.FirstOrDefault(t => t.Key == "reason");
822-
Assert.True(reasonTag.Key == "reason",
823-
$"retry.attempt event #{i} missing reason. Tags: [" +
824-
string.Join(", ", tags.Select(t => $"{t.Key}={t.Value}")) + "]");
825-
var reason = reasonTag.Value as string;
826-
Assert.NotNull(reason);
827-
// We don't pin the exact spelling, but it must reference 503.
828-
Assert.Contains("503", reason!);
769+
var tags = events[i].Tags.ToDictionary(t => t.Key, t => t.Value);
770+
Assert.Equal(i + 1, Convert.ToInt32(tags["attempt_number"]));
771+
Assert.True(Convert.ToInt64(tags["delay_ms"]) >= 1000); // Retry-After: 1 → 1000ms
772+
Assert.Contains(expectedReasonSubstring, (string)tags["reason"]!);
829773
}
830774
}
831775

832-
/// <summary>
833-
/// Issue #479: rate-limit 429 retries must also emit `retry.attempt`
834-
/// events with a 429-shaped `reason` so the trace makes the throttle
835-
/// case distinguishable from generic 503.
836-
/// </summary>
837-
[Fact]
838-
public async Task RetryTelemetry_RetryAfter429_EmitsAttemptEventWithRateLimitReason_Issue479()
839-
{
840-
using var capture = new ActivityCapture("TestSource");
841-
842-
var mockHandler = new MockHttpMessageHandler(
843-
new HttpResponseMessage((HttpStatusCode)429)
844-
{
845-
Headers = { { "Retry-After", "1" } },
846-
Content = new StringContent("Too Many Requests")
847-
});
848-
mockHandler.SetResponseAfterRetryCount(1, new HttpResponseMessage(HttpStatusCode.OK)
849-
{
850-
Content = new StringContent("Success")
851-
});
852-
853-
var mockTracer = new MockActivityTracer();
854-
var retryHandler = new RetryHttpHandler(mockHandler, mockTracer, 10, 10, true, true);
855-
856-
var httpClient = new HttpClient(retryHandler);
857-
var response = await httpClient.GetAsync("http://test.com");
858-
859-
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
860-
861-
Activity? sendAsyncActivity = capture.StoppedActivities
862-
.FirstOrDefault(a => a.OperationName == "SendAsync");
863-
Assert.NotNull(sendAsyncActivity);
864-
865-
var retryEvents = sendAsyncActivity!.Events.Where(e => e.Name == "retry.attempt").ToList();
866-
Assert.Single(retryEvents);
867-
var reasonTag = retryEvents[0].Tags.FirstOrDefault(t => t.Key == "reason");
868-
Assert.True(reasonTag.Key == "reason");
869-
var reason = reasonTag.Value as string;
870-
Assert.NotNull(reason);
871-
// Must reference 429 so 429 retries are distinguishable from 503.
872-
Assert.Contains("429", reason!);
873-
}
874-
875776
/// <summary>
876777
/// Mock HttpMessageHandler for testing the RetryHttpHandler.
877778
/// </summary>

0 commit comments

Comments
 (0)