diff --git a/Makefile b/Makefile index 710e3b7228c..164e9777442 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,8 @@ update-version: @echo "$(VERSION)" > VERSION @perl -pi -e 's|badge/maven--central-v[.\d\-\w]+-blue|badge/maven--central-v$(VERSION)-blue|' README.md - @perl -pi -e 's|https:\/\/search\.maven\.org\/remotecontent\?filepath=com\/stripe\/stripe-java\/[.\d\-\w]+\/stripe-java-[.\d\-\w]+.jar|https://search.maven.org/remotecontent?filepath=com/stripe/stripe-java/$(VERSION)/stripe-java-$(VERSION).jar|' README.md + @perl -pi -e 's|https:\/\/repo1\.maven\.org\/maven2\/com\/stripe\/stripe-java\/[.\d\-\w]+\/stripe-java-[.\d\-\w]+.jar|https://repo1.maven.org/maven2/com/stripe/stripe-java/$(VERSION)/stripe-java-$(VERSION).jar|' README.md + @perl -pi -e 's|Current release version: [.\d\-\w]+|Current release version: $(VERSION)|' README.md @perl -pi -e 's|implementation "com\.stripe:stripe-java:[.\d\-\w]+"|implementation "com.stripe:stripe-java:$(VERSION)"|' README.md @perl -pi -e 's|[.\d\-\w]+<\/version>|$(VERSION)|' README.md @perl -pi -e 's|VERSION_NAME=[.\d\-\w]+|VERSION_NAME=$(VERSION)|' gradle.properties diff --git a/justfile b/justfile index 8db677dee7b..d48f299877e 100644 --- a/justfile +++ b/justfile @@ -28,7 +28,8 @@ format-check: update-version version: echo "{{ version }}" > VERSION perl -pi -e 's|badge/maven--central-v[.\d\-\w]+-blue|badge/maven--central-v{{ version }}-blue|' README.md - perl -pi -e 's|https:\/\/search\.maven\.org\/remotecontent\?filepath=com\/stripe\/stripe-java\/[.\d\-\w]+\/stripe-java-[.\d\-\w]+.jar|https://search.maven.org/remotecontent?filepath=com/stripe/stripe-java/{{ version }}/stripe-java-{{ version }}.jar|' README.md + perl -pi -e 's|https:\/\/repo1\.maven\.org\/maven2\/com\/stripe\/stripe-java\/[.\d\-\w]+\/stripe-java-[.\d\-\w]+.jar|https://repo1.maven.org/maven2/com/stripe/stripe-java/{{ version }}/stripe-java-{{ version }}.jar|' README.md + perl -pi -e 's|Current release version: [.\d\-\w]+|Current release version: {{ version }}|' README.md perl -pi -e 's|implementation "com\.stripe:stripe-java:[.\d\-\w]+"|implementation "com.stripe:stripe-java:{{ version }}"|' README.md perl -pi -e 's|[.\d\-\w]+<\/version>|{{ version }}|' README.md perl -pi -e 's|VERSION_NAME=[.\d\-\w]+|VERSION_NAME={{ version }}|' gradle.properties diff --git a/src/main/java/com/stripe/StripeClient.java b/src/main/java/com/stripe/StripeClient.java index 356f474d768..a58ab08a122 100644 --- a/src/main/java/com/stripe/StripeClient.java +++ b/src/main/java/com/stripe/StripeClient.java @@ -1326,6 +1326,11 @@ public StripeClientBuilder setStripeContext(String context) { return this; } + public StripeClientBuilder setStripeContext(StripeContext context) { + this.stripeContext = context == null ? null : context.toString(); + return this; + } + public String getStripeContext() { return this.stripeContext; } diff --git a/src/main/java/com/stripe/StripeContext.java b/src/main/java/com/stripe/StripeContext.java new file mode 100644 index 00000000000..cd7b15b19e8 --- /dev/null +++ b/src/main/java/com/stripe/StripeContext.java @@ -0,0 +1,98 @@ +package com.stripe; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import lombok.EqualsAndHashCode; + +/** + * The StripeContext class provides an immutable container for interacting with the `Stripe-Context` + * header. + * + *

You can use it whenever you're initializing a `StripeClient` or sending `stripe_context` with + * a request. It's also found in the `EventNotification.context` property. + */ +@EqualsAndHashCode +public final class StripeContext { + private final List segments; + + /** Creates a new StripeContext with no segments. */ + public StripeContext() { + this(null); + } + + /** + * Creates a new StripeContext with the specified segments. + * + * @param segments the list of context segments + */ + public StripeContext(List segments) { + this.segments = + segments == null + ? Collections.emptyList() + : Collections.unmodifiableList(new ArrayList<>(segments)); + } + + /** + * Returns a new StripeContext with the given segment added to the end. + * + * @param segment the segment to add + * @return a new StripeContext instance with the segment appended + */ + public StripeContext push(String segment) { + List newSegments = new ArrayList<>(this.segments); + newSegments.add(segment); + return new StripeContext(newSegments); + } + + /** + * Returns a new StripeContext with the last segment removed. + * + * @return a new StripeContext instance with the last segment removed + */ + public StripeContext pop() { + if (segments.isEmpty()) { + throw new IllegalStateException("Cannot pop from an empty StripeContext"); + } + + List newSegments = new ArrayList<>(this.segments); + newSegments.remove(newSegments.size() - 1); + return new StripeContext(newSegments); + } + + /** + * Converts the context to a string by joining segments with '/'. + * + * @return string representation of the context segments joined by '/', `null` if there are no + * segments (useful for clearing context) + */ + @Override + public String toString() { + return String.join("/", segments); + } + + /** + * Parse a context string into a StripeContext instance. + * + * @param contextStr string to parse (segments separated by '/') + * @return StripeContext instance with segments from the string + */ + public static StripeContext parse(String contextStr) { + if (contextStr == null || contextStr.isEmpty()) { + return new StripeContext(); + } + + List segments = Arrays.asList(contextStr.split("/")); + return new StripeContext(segments); + } + + /** + * Returns an unmodifiable list of the current segments. + * + * @return the list of segments + */ + public List getSegments() { + return segments; + } +} diff --git a/src/main/java/com/stripe/model/StripeContextDeserializer.java b/src/main/java/com/stripe/model/StripeContextDeserializer.java new file mode 100644 index 00000000000..3f9c21d5943 --- /dev/null +++ b/src/main/java/com/stripe/model/StripeContextDeserializer.java @@ -0,0 +1,26 @@ +package com.stripe.model; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.stripe.StripeContext; +import java.lang.reflect.Type; + +public class StripeContextDeserializer implements JsonDeserializer { + + @Override + public StripeContext deserialize( + JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + if (json == null || json.isJsonNull()) { + return null; + } + + String contextString = json.getAsString().trim(); + if (contextString.isEmpty()) { + return null; + } + return StripeContext.parse(contextString); + } +} diff --git a/src/main/java/com/stripe/model/v2/EventNotification.java b/src/main/java/com/stripe/model/v2/EventNotification.java index 33866cf95e7..796a1ad0eb5 100644 --- a/src/main/java/com/stripe/model/v2/EventNotification.java +++ b/src/main/java/com/stripe/model/v2/EventNotification.java @@ -3,6 +3,7 @@ import com.google.gson.JsonObject; import com.google.gson.annotations.SerializedName; import com.stripe.StripeClient; +import com.stripe.StripeContext; import com.stripe.exception.StripeException; import com.stripe.model.StripeObject; import com.stripe.model.v2.Event.RelatedObject; @@ -71,7 +72,7 @@ public static class Reason { /** [Optional] Authentication context needed to fetch the event or related object. */ @SerializedName("context") - public String context; + public StripeContext context; /** [Optional] Reason for the event. */ @SerializedName("reason") @@ -98,6 +99,7 @@ public static EventNotification fromJson(String payload, StripeClient client) { EventNotification e = ApiResource.GSON.fromJson(payload, cls); e.client = client; + return e; } @@ -105,7 +107,9 @@ private RawRequestOptions getRequestOptions() { if (context == null) { return null; } - return new RawRequestOptions.RawRequestOptionsBuilder().setStripeContext(context).build(); + return new RawRequestOptions.RawRequestOptionsBuilder() + .setStripeContext(context.toString()) + .build(); } /* retrieves the full payload for an event. Protected because individual push classes use it, but type it correctly */ diff --git a/src/main/java/com/stripe/net/ApiResource.java b/src/main/java/com/stripe/net/ApiResource.java index 214803bcca7..d23ce045463 100644 --- a/src/main/java/com/stripe/net/ApiResource.java +++ b/src/main/java/com/stripe/net/ApiResource.java @@ -1,6 +1,7 @@ package com.stripe.net; import com.google.gson.*; +import com.stripe.StripeContext; import com.stripe.exception.InvalidRequestException; import com.stripe.model.*; import com.stripe.model.v2.EventTypeAdapterFactory; @@ -54,6 +55,7 @@ private static Gson createGson(boolean shouldSetResponseGetter) { .registerTypeAdapter(EphemeralKey.class, new EphemeralKeyDeserializer()) .registerTypeAdapter(Event.Data.class, new EventDataDeserializer()) .registerTypeAdapter(Event.Request.class, new EventRequestDeserializer()) + .registerTypeAdapter(StripeContext.class, new StripeContextDeserializer()) .registerTypeAdapter(ExpandableField.class, new ExpandableFieldDeserializer()) .registerTypeAdapter(Instant.class, new InstantDeserializer()) .registerTypeAdapterFactory(new EventTypeAdapterFactory()) diff --git a/src/main/java/com/stripe/net/RawRequestOptions.java b/src/main/java/com/stripe/net/RawRequestOptions.java index abad1823552..ba0384c338e 100644 --- a/src/main/java/com/stripe/net/RawRequestOptions.java +++ b/src/main/java/com/stripe/net/RawRequestOptions.java @@ -1,5 +1,6 @@ package com.stripe.net; +import com.stripe.StripeContext; import java.net.PasswordAuthentication; import java.net.Proxy; import java.util.Map; @@ -81,6 +82,12 @@ public RawRequestOptionsBuilder setStripeContext(String stripeContext) { return this; } + @Override + public RawRequestOptionsBuilder setStripeContext(StripeContext stripeContext) { + super.setStripeContext(stripeContext); + return this; + } + @Override public RawRequestOptionsBuilder setStripeAccount(String stripeAccount) { super.setStripeAccount(stripeAccount); diff --git a/src/main/java/com/stripe/net/RequestOptions.java b/src/main/java/com/stripe/net/RequestOptions.java index 311db50d840..c1ce9e8a85d 100644 --- a/src/main/java/com/stripe/net/RequestOptions.java +++ b/src/main/java/com/stripe/net/RequestOptions.java @@ -237,6 +237,17 @@ public RequestOptionsBuilder setStripeContext(String context) { return this; } + public RequestOptionsBuilder setStripeContext(com.stripe.StripeContext context) { + this.stripeContext = context != null ? context.toString() : null; + return this; + } + + /** + * Empties the current builder value for StripeContext, which will defer to the client options. + * + *

To send no context at all, call `setContext(new StripeContext())`or set the context to an + * empty string. + */ public RequestOptionsBuilder clearStripeContext() { this.stripeContext = null; return this; @@ -474,15 +485,28 @@ static RequestOptions merge(StripeResponseGetterOptions clientOptions, RequestOp clientOptions.getProxyCredential() // proxyCredential ); } + + // callers need to be able to explicitly unset context per-request + // an empty StripeContext serializes to a "", so check for that and empty context out if it's + // there. + String stripeContext; + if (options.getStripeContext() != null) { + String requestContext = options.getStripeContext().trim(); + if (requestContext.isEmpty()) { + stripeContext = null; + } else { + stripeContext = requestContext; + } + } else { + stripeContext = clientOptions.getStripeContext(); + } return new RequestOptions( options.getAuthenticator() != null ? options.getAuthenticator() : clientOptions.getAuthenticator(), options.getClientId() != null ? options.getClientId() : clientOptions.getClientId(), options.getIdempotencyKey(), - options.getStripeContext() != null - ? options.getStripeContext() - : clientOptions.getStripeContext(), + stripeContext, options.getStripeAccount() != null ? options.getStripeAccount() : clientOptions.getStripeAccount(), diff --git a/src/test/java/com/stripe/StripeClientTest.java b/src/test/java/com/stripe/StripeClientTest.java index f8c18ce31a7..4a95f3eaec2 100644 --- a/src/test/java/com/stripe/StripeClientTest.java +++ b/src/test/java/com/stripe/StripeClientTest.java @@ -248,7 +248,7 @@ public void parsesEventNotificationWithRelatedObject() assertEquals("evt_234", eventNotification.getId()); assertEquals("v1.billing.meter.error_report_triggered", eventNotification.getType()); assertEquals(Instant.parse("2022-02-15T00:27:45.330Z"), eventNotification.created); - assertEquals("org_123", eventNotification.context); + assertEquals("org_123", eventNotification.getContext().toString()); assertInstanceOf(V1BillingMeterErrorReportTriggeredEventNotification.class, eventNotification); assertEquals("request", eventNotification.getReason().getType()); assertEquals("abc123", eventNotification.getReason().getRequest().getId()); diff --git a/src/test/java/com/stripe/StripeContextIntegrationTest.java b/src/test/java/com/stripe/StripeContextIntegrationTest.java new file mode 100644 index 00000000000..a6ed855e9a3 --- /dev/null +++ b/src/test/java/com/stripe/StripeContextIntegrationTest.java @@ -0,0 +1,156 @@ +package com.stripe; + +import static org.junit.jupiter.api.Assertions.*; + +import com.stripe.model.v2.EventNotification; +import com.stripe.net.RawRequestOptions; +import com.stripe.net.RequestOptions; +import java.util.Arrays; +import org.junit.jupiter.api.Test; + +public class StripeContextIntegrationTest { + + @Test + public void testRequestOptionsWithStringContext() { + RequestOptions options = RequestOptions.builder().setStripeContext("a/b/c").build(); + + assertEquals("a/b/c", options.getStripeContext()); + } + + @Test + public void testRawRequestOptionsWithStringContext() { + RawRequestOptions options = RawRequestOptions.builder().setStripeContext("a/b/c").build(); + + assertEquals("a/b/c", options.getStripeContext()); + } + + @Test + public void testRequestOptionsWithContextObject() { + StripeContext context = new StripeContext(Arrays.asList("a", "b", "c")); + RequestOptions options = RequestOptions.builder().setStripeContext(context).build(); + + assertEquals("a/b/c", options.getStripeContext()); + } + + @Test + public void testRequestOptionsWithNullContext() { + RequestOptions options = RequestOptions.builder().setStripeContext(new StripeContext()).build(); + + assertEquals("", options.getStripeContext()); + } + + @Test + public void testStripeClientBuilderWithStringContext() { + StripeClient.StripeClientBuilder builder = + StripeClient.builder().setApiKey("sk_test_123").setStripeContext("a/b/c"); + + assertEquals("a/b/c", builder.getStripeContext()); + } + + @Test + public void testStripeClientBuilderWithContextObject() { + StripeContext context = new StripeContext(Arrays.asList("a", "b", "c")); + StripeClient.StripeClientBuilder builder = + StripeClient.builder().setApiKey("sk_test_123").setStripeContext(context); + + assertEquals("a/b/c", builder.getStripeContext()); + } + + @Test + public void testStripeClientBuilderWithNullContext() { + StripeClient.StripeClientBuilder builder = + StripeClient.builder().setApiKey("sk_test_123").setStripeContext((StripeContext) null); + + assertNull(builder.getStripeContext()); + } + + @Test + public void testStripeClientBuilderEmptyContextOverwrites() { + StripeClient.StripeClientBuilder builder = + StripeClient.builder() + .setApiKey("sk_test_123") + .setStripeContext("ctx_123") + .setStripeContext(new StripeContext()); + + assertEquals("", builder.getStripeContext()); + } + + @Test + public void testStripeClientBuilderWithEmptyContext() { + StripeClient.StripeClientBuilder builder = + StripeClient.builder().setApiKey("sk_test_123").setStripeContext(new StripeContext()); + + assertEquals("", builder.getStripeContext()); + } + + @Test + public void testEventNotificationParsing() { + String payload = + "{\"id\":\"evt_123\",\"type\":\"test.event\",\"created\":\"2023-01-01T00:00:00Z\",\"livemode\":false,\"context\":\"a/b/c\"}"; + StripeClient client = new StripeClient("sk_test_123"); + + EventNotification notification = EventNotification.fromJson(payload, client); + + assertNotNull(notification.context); + assertEquals(Arrays.asList("a", "b", "c"), notification.context.getSegments()); + assertEquals("a/b/c", notification.context.toString()); + } + + @Test + public void testEventNotificationNoContext() { + String payload = + "{\"id\":\"evt_123\",\"type\":\"test.event\",\"created\":\"2023-01-01T00:00:00Z\",\"livemode\":false}"; + StripeClient client = new StripeClient("sk_test_123"); + + EventNotification notification = EventNotification.fromJson(payload, client); + + assertNull(notification.getContext()); + } + + @Test + public void testEventNotificationEmptyContext() { + String payload = + "{\"id\":\"evt_123\",\"type\":\"test.event\",\"created\":\"2023-01-01T00:00:00Z\",\"livemode\":false,\"context\":\"\"}"; + StripeClient client = new StripeClient("sk_test_123"); + + EventNotification notification = EventNotification.fromJson(payload, client); + + assertNull(notification.getContext()); + } + + @Test + public void testEventNotificationNullContext() { + String payload = + "{\"id\":\"evt_123\",\"type\":\"test.event\",\"created\":\"2023-01-01T00:00:00Z\",\"livemode\":false,\"context\":null}"; + StripeClient client = new StripeClient("sk_test_123"); + + EventNotification notification = EventNotification.fromJson(payload, client); + + assertNull(notification.getContext()); + } + + @Test + public void testContextBuilderPattern() { + // Test the builder pattern works well with StripeContext + StripeContext baseContext = StripeContext.parse("workspace_123"); + + RequestOptions options = + RequestOptions.builder() + .setApiKey("sk_test_123") + .setStripeContext(baseContext.push("account_456")) + .build(); + + assertEquals("workspace_123/account_456", options.getStripeContext()); + } + + @Test + public void testContextCompatibility() { + // Test that both string and StripeContext work equivalently + RequestOptions stringOptions = RequestOptions.builder().setStripeContext("a/b/c").build(); + + StripeContext context = new StripeContext(Arrays.asList("a", "b", "c")); + RequestOptions contextOptions = RequestOptions.builder().setStripeContext(context).build(); + + assertEquals(stringOptions.getStripeContext(), contextOptions.getStripeContext()); + } +} diff --git a/src/test/java/com/stripe/StripeContextTest.java b/src/test/java/com/stripe/StripeContextTest.java new file mode 100644 index 00000000000..e4b4a2dec1c --- /dev/null +++ b/src/test/java/com/stripe/StripeContextTest.java @@ -0,0 +1,173 @@ +package com.stripe; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import org.junit.jupiter.api.Test; + +public class StripeContextTest { + + @Test + public void testDefaultConstructor() { + StripeContext context = new StripeContext(); + assertEquals(Collections.emptyList(), context.getSegments()); + } + + @Test + public void testConstructorWithSegments() { + List segments = Arrays.asList("a", "b", "c"); + StripeContext context = new StripeContext(segments); + assertEquals(segments, context.getSegments()); + } + + @Test + public void testConstructorWithNullSegments() { + StripeContext context = new StripeContext(null); + assertEquals(Collections.emptyList(), context.getSegments()); + } + + @Test + public void testPush() { + StripeContext context = new StripeContext(Arrays.asList("a", "b")); + StripeContext newContext = context.push("c"); + + // Original context unchanged + assertEquals(Arrays.asList("a", "b"), context.getSegments()); + // New context has added segment + assertEquals(Arrays.asList("a", "b", "c"), newContext.getSegments()); + // Different objects + assertNotSame(context, newContext); + } + + @Test + public void testPopWithSegments() { + StripeContext context = new StripeContext(Arrays.asList("a", "b", "c")); + StripeContext newContext = context.pop(); + + // Original context unchanged + assertEquals(Arrays.asList("a", "b", "c"), context.getSegments()); + // New context has removed last segment + assertEquals(Arrays.asList("a", "b"), newContext.getSegments()); + // Different objects + assertNotSame(context, newContext); + } + + @Test + public void testPopEmpty() { + StripeContext context = new StripeContext(); + assertThrows(IllegalStateException.class, () -> context.pop()); + } + + @Test + public void testToStringEmpty() { + StripeContext context = new StripeContext(); + // useful for clearing context + assertEquals("", context.toString()); + } + + @Test + public void testToStringSingleSegment() { + StripeContext context = new StripeContext(Arrays.asList("a")); + assertEquals("a", context.toString()); + } + + @Test + public void testToStringMultipleSegments() { + StripeContext context = new StripeContext(Arrays.asList("a", "b", "c")); + assertEquals("a/b/c", context.toString()); + } + + @Test + public void testParseEmptyString() { + StripeContext context = StripeContext.parse(""); + assertEquals(Collections.emptyList(), context.getSegments()); + } + + @Test + public void testParseNull() { + StripeContext context = StripeContext.parse(null); + assertEquals(Collections.emptyList(), context.getSegments()); + } + + @Test + public void testParseSingleSegment() { + StripeContext context = StripeContext.parse("a"); + assertEquals(Arrays.asList("a"), context.getSegments()); + } + + @Test + public void testParseMultipleSegments() { + StripeContext context = StripeContext.parse("a/b/c"); + assertEquals(Arrays.asList("a", "b", "c"), context.getSegments()); + } + + @Test + public void testEquals() { + StripeContext context1 = new StripeContext(Arrays.asList("a", "b")); + StripeContext context2 = new StripeContext(Arrays.asList("a", "b")); + StripeContext context3 = new StripeContext(Arrays.asList("a", "c")); + + assertEquals(context1, context2); + assertNotEquals(context1, context3); + assertNotEquals(context1, "a/b"); + assertNotEquals(context1, null); + } + + @Test + public void testHashCode() { + StripeContext context1 = new StripeContext(Arrays.asList("a", "b")); + StripeContext context2 = new StripeContext(Arrays.asList("a", "b")); + + assertEquals(context1.hashCode(), context2.hashCode()); + } + + @Test + public void testImmutableSegments() { + List originalSegments = Arrays.asList("a", "b"); + StripeContext context = new StripeContext(originalSegments); + + // Getting segments should return an unmodifiable list + List segments = context.getSegments(); + assertThrows(UnsupportedOperationException.class, () -> segments.add("c")); + } + + @Test + public void testContextManipulationPattern() { + // Common usage: start with base context, add child contexts + StripeContext base = StripeContext.parse("workspace_123"); + StripeContext child = base.push("account_456"); + StripeContext grandchild = child.push("customer_789"); + + assertEquals("workspace_123", base.toString()); + assertEquals("workspace_123/account_456", child.toString()); + assertEquals("workspace_123/account_456/customer_789", grandchild.toString()); + + // Go back up the hierarchy + StripeContext backToChild = grandchild.pop(); + StripeContext backToBase = backToChild.pop(); + + assertEquals("workspace_123/account_456", backToChild.toString()); + assertEquals("workspace_123", backToBase.toString()); + } + + @Test + public void testContextImmutability() { + StripeContext original = new StripeContext(Arrays.asList("a", "b")); + + // Multiple operations on the same context + StripeContext pushed = original.push("c"); + StripeContext popped = original.pop(); + + // Original remains unchanged + assertEquals(Arrays.asList("a", "b"), original.getSegments()); + assertEquals(Arrays.asList("a", "b", "c"), pushed.getSegments()); + assertEquals(Arrays.asList("a"), popped.getSegments()); + + // All are different objects + assertNotSame(original, pushed); + assertNotSame(original, popped); + assertNotSame(pushed, popped); + } +} diff --git a/src/test/java/com/stripe/net/RequestOptionsTest.java b/src/test/java/com/stripe/net/RequestOptionsTest.java index 15970137fff..a2de33d44dc 100644 --- a/src/test/java/com/stripe/net/RequestOptionsTest.java +++ b/src/test/java/com/stripe/net/RequestOptionsTest.java @@ -4,6 +4,7 @@ import com.stripe.BaseStripeTest; import com.stripe.Stripe; +import com.stripe.StripeContext; import com.stripe.net.RequestOptions.RequestOptionsBuilder; import java.net.InetSocketAddress; import java.net.PasswordAuthentication; @@ -173,6 +174,57 @@ public void mergeOverwritesClientOptions() { assertEquals("5", merged.getStripeContext()); } + @Test + public void clientContextIsUsedWhenRequestNull() { + StripeResponseGetterOptions clientOptions = + TestStripeResponseGetterOptions.builder().setStripeContext("a/b/c").build(); + + RequestOptions requestOptions = + RequestOptions.builder().setStripeContext((StripeContext) null).build(); + + RequestOptions merged = RequestOptions.merge(clientOptions, requestOptions); + + assertEquals("a/b/c", merged.getStripeContext()); + } + + @Test + public void requestContextPrioritizedIfRequestSetNullString() { + StripeResponseGetterOptions clientOptions = + TestStripeResponseGetterOptions.builder().setStripeContext("a/b/c").build(); + + RequestOptions requestOptions = + RequestOptions.builder().setStripeContext((String) null).build(); + + RequestOptions merged = RequestOptions.merge(clientOptions, requestOptions); + + assertEquals("a/b/c", merged.getStripeContext()); + } + + @Test + public void mergeRequestOptionsWithEmptyContextOverwritesClientContext() { + StripeResponseGetterOptions clientOptions = + TestStripeResponseGetterOptions.builder().setStripeContext("a/b/c").build(); + + RequestOptions requestOptions = + RequestOptions.builder().setStripeContext(new StripeContext()).build(); + + RequestOptions merged = RequestOptions.merge(clientOptions, requestOptions); + + assertNull(merged.getStripeContext()); + } + + @Test + public void requestContextPrioritized() { + StripeResponseGetterOptions clientOptions = + TestStripeResponseGetterOptions.builder().setStripeContext("a/b/c").build(); + + RequestOptions requestOptions = RequestOptions.builder().setStripeContext("d/e/f").build(); + + RequestOptions merged = RequestOptions.merge(clientOptions, requestOptions); + + assertEquals("d/e/f", merged.getStripeContext()); + } + @Test public void mergeFallsBackToClientOptions() { Proxy clientProxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("localhost", 8080));