diff --git a/src/test/java/com/stripe/net/RequestTelemetryTest.java b/src/test/java/com/stripe/net/RequestTelemetryTest.java
new file mode 100644
index 00000000000..5bdc14f9140
--- /dev/null
+++ b/src/test/java/com/stripe/net/RequestTelemetryTest.java
@@ -0,0 +1,220 @@
+package com.stripe.net;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.stripe.Stripe;
+import java.time.Duration;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests for {@link RequestTelemetry}.
+ *
+ *
These test the class directly, verifying enqueue/poll semantics, telemetry toggle behavior,
+ * queue bounds, and JSON payload structure — without going through the HTTP layer.
+ */
+public class RequestTelemetryTest {
+ private boolean originalTelemetry;
+ private RequestTelemetry telemetry;
+
+ @BeforeEach
+ public void setUp() {
+ originalTelemetry = Stripe.enableTelemetry;
+ Stripe.enableTelemetry = true;
+ telemetry = new RequestTelemetry();
+ // Drain any leftover metrics from prior tests (shared static queue)
+ while (telemetry.pollPayload().isPresent()) {
+ // discard
+ }
+ }
+
+ @AfterEach
+ public void tearDown() {
+ Stripe.enableTelemetry = originalTelemetry;
+ }
+
+ // ---- basic enqueue / poll ----
+
+ @Test
+ public void testPollReturnsEmptyWhenNothingEnqueued() {
+ Optional payload = telemetry.pollPayload();
+ assertFalse(payload.isPresent(), "Expected empty payload when no metrics enqueued");
+ }
+
+ @Test
+ public void testEnqueueAndPollReturnsPayload() {
+ StripeResponse response = buildResponse("req_test_1");
+ telemetry.maybeEnqueueMetrics(response, Duration.ofMillis(42), null);
+
+ Optional payload = telemetry.pollPayload();
+ assertTrue(payload.isPresent(), "Expected a telemetry payload after enqueue");
+
+ JsonObject root = JsonParser.parseString(payload.get()).getAsJsonObject();
+ JsonObject metrics = root.getAsJsonObject("last_request_metrics");
+ assertEquals("req_test_1", metrics.get("request_id").getAsString());
+ assertEquals(42L, metrics.get("request_duration_ms").getAsLong());
+ assertFalse(metrics.has("usage"), "usage should be absent when null");
+ }
+
+ @Test
+ public void testPollDrainsQueue() {
+ StripeResponse response = buildResponse("req_drain");
+ telemetry.maybeEnqueueMetrics(response, Duration.ofMillis(10), null);
+
+ assertTrue(telemetry.pollPayload().isPresent());
+ assertFalse(telemetry.pollPayload().isPresent(), "Second poll should return empty");
+ }
+
+ // ---- usage field ----
+
+ @Test
+ public void testUsageIncludedInPayload() {
+ StripeResponse response = buildResponse("req_usage");
+ List usage = Arrays.asList("llm", "streaming");
+ telemetry.maybeEnqueueMetrics(response, Duration.ofMillis(100), usage);
+
+ Optional payload = telemetry.pollPayload();
+ assertTrue(payload.isPresent());
+
+ JsonObject metrics =
+ JsonParser.parseString(payload.get())
+ .getAsJsonObject()
+ .getAsJsonObject("last_request_metrics");
+ assertEquals("req_usage", metrics.get("request_id").getAsString());
+ assertTrue(metrics.has("usage"), "usage field should be present");
+ assertEquals(2, metrics.getAsJsonArray("usage").size());
+ }
+
+ @Test
+ public void testEmptyUsageListTreatedAsNull() {
+ StripeResponse response = buildResponse("req_empty_usage");
+ telemetry.maybeEnqueueMetrics(response, Duration.ofMillis(50), Collections.emptyList());
+
+ Optional payload = telemetry.pollPayload();
+ assertTrue(payload.isPresent());
+
+ JsonObject metrics =
+ JsonParser.parseString(payload.get())
+ .getAsJsonObject()
+ .getAsJsonObject("last_request_metrics");
+ // The class normalizes empty list to null
+ assertFalse(metrics.has("usage"), "Empty usage list should be normalized to absent");
+ }
+
+ // ---- telemetry toggle ----
+
+ @Test
+ public void testEnqueueIgnoredWhenTelemetryDisabled() {
+ Stripe.enableTelemetry = false;
+ StripeResponse response = buildResponse("req_disabled");
+ telemetry.maybeEnqueueMetrics(response, Duration.ofMillis(10), null);
+
+ // Re-enable so pollPayload doesn't short-circuit
+ Stripe.enableTelemetry = true;
+ assertFalse(
+ telemetry.pollPayload().isPresent(),
+ "Nothing should be enqueued when telemetry is disabled");
+ }
+
+ @Test
+ public void testPollReturnsEmptyWhenTelemetryDisabledAtPollTime() {
+ // Enqueue while enabled
+ StripeResponse response = buildResponse("req_toggle");
+ telemetry.maybeEnqueueMetrics(response, Duration.ofMillis(10), null);
+
+ // Disable before polling
+ Stripe.enableTelemetry = false;
+ assertFalse(
+ telemetry.pollPayload().isPresent(),
+ "pollPayload should return empty when telemetry is disabled at poll time");
+
+ // Re-enable — the metric was consumed (polled off queue) even though it wasn't returned
+ Stripe.enableTelemetry = true;
+ assertFalse(
+ telemetry.pollPayload().isPresent(),
+ "Metric should have been consumed even when telemetry was disabled");
+ }
+
+ // ---- null request ID ----
+
+ @Test
+ public void testEnqueueIgnoredWhenRequestIdIsNull() {
+ StripeResponse response = buildResponse(null);
+ telemetry.maybeEnqueueMetrics(response, Duration.ofMillis(10), null);
+
+ assertFalse(
+ telemetry.pollPayload().isPresent(),
+ "Nothing should be enqueued when requestId is null");
+ }
+
+ // ---- queue capacity ----
+
+ @Test
+ public void testQueueBoundedAtMaxSize() {
+ // MAX_REQUEST_METRICS_QUEUE_SIZE is 100
+ for (int i = 0; i < 110; i++) {
+ StripeResponse response = buildResponse("req_" + i);
+ telemetry.maybeEnqueueMetrics(response, Duration.ofMillis(1), null);
+ }
+
+ int count = 0;
+ while (telemetry.pollPayload().isPresent()) {
+ count++;
+ }
+ assertEquals(100, count, "Queue should be bounded at 100 entries");
+ }
+
+ // ---- deprecated getHeaderValue ----
+
+ @Test
+ public void testGetHeaderValueReturnsEmptyWhenHeaderAlreadyPresent() {
+ StripeResponse response = buildResponse("req_dup_header");
+ telemetry.maybeEnqueueMetrics(response, Duration.ofMillis(10), null);
+
+ HttpHeaders headers =
+ HttpHeaders.of(
+ Collections.singletonMap(
+ RequestTelemetry.HEADER_NAME, Collections.singletonList("existing")));
+
+ @SuppressWarnings("deprecation")
+ Optional result = telemetry.getHeaderValue(headers);
+ assertFalse(
+ result.isPresent(),
+ "getHeaderValue should return empty when header is already present");
+ }
+
+ @Test
+ public void testGetHeaderValueReturnsTelemetryWhenHeaderAbsent() {
+ StripeResponse response = buildResponse("req_no_header");
+ telemetry.maybeEnqueueMetrics(response, Duration.ofMillis(55), null);
+
+ HttpHeaders headers = HttpHeaders.of(Collections.emptyMap());
+
+ @SuppressWarnings("deprecation")
+ Optional result = telemetry.getHeaderValue(headers);
+ assertTrue(result.isPresent(), "getHeaderValue should return telemetry when header is absent");
+
+ JsonObject metrics =
+ JsonParser.parseString(result.get())
+ .getAsJsonObject()
+ .getAsJsonObject("last_request_metrics");
+ assertEquals("req_no_header", metrics.get("request_id").getAsString());
+ }
+
+ // ---- helpers ----
+
+ private static StripeResponse buildResponse(String requestId) {
+ java.util.Map> headerMap = new java.util.HashMap<>();
+ if (requestId != null) {
+ headerMap.put("Request-Id", Collections.singletonList(requestId));
+ }
+ return new StripeResponse(200, HttpHeaders.of(headerMap), "{}");
+ }
+}