Skip to content

Commit 44af5fd

Browse files
committed
fix: ClientContext(sdkKey) test ctor generates a single instance id (SDK-2356)
Previously the public single-arg `ClientContext(String sdkKey)` test-convenience constructor produced an internally inconsistent state: `defaultHttp(sdkKey)` built an HttpConfiguration whose default headers embedded one auto-generated UUID (via the inner minimalContext), and the outer delegated 8-arg constructor auto-generated a separate UUID for `this.instanceId`. As a result, `context.getInstanceId()` and `context.getHttp().getDefaultHeaders()` returned different values for `X-LaunchDarkly-Instance-Id` -- so consumer code that read the instance id via the getter would see a different identifier than the SDK actually placed on outbound requests. Generate the UUID once in a new private delegating constructor and thread it both into `defaultHttp(sdkKey, instanceId)` (so the headers carry it) and into the explicit instanceId argument of the 9-arg constructor (so the accessor returns the same value). Added a regression test in ClientContextImplTest that asserts these two accessors agree.
1 parent 30cf64b commit 44af5fd

2 files changed

Lines changed: 52 additions & 9 deletions

File tree

lib/sdk/server/src/main/java/com/launchdarkly/sdk/server/subsystems/ClientContext.java

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -129,20 +129,32 @@ protected ClientContext(ClientContext copyFrom) {
129129
* @param sdkKey the SDK key
130130
*/
131131
public ClientContext(String sdkKey) {
132+
this(sdkKey, UUID.randomUUID().toString());
133+
}
134+
135+
// Private delegating constructor: generates a single instance id up front and threads it
136+
// both into the default HttpConfiguration (so the X-LaunchDarkly-Instance-Id default
137+
// header carries it) and into this.instanceId (so getInstanceId() returns the same value).
138+
// The earlier shape, where the public single-arg ctor called defaultHttp(sdkKey) and then
139+
// let the eight-arg ctor auto-generate a fresh UUID, produced two different ids -- one in
140+
// the headers and one returned by getInstanceId().
141+
private ClientContext(String sdkKey, String instanceId) {
132142
this(
133143
sdkKey,
134144
new ApplicationInfo(null, null),
135-
defaultHttp(sdkKey),
145+
defaultHttp(sdkKey, instanceId),
136146
defaultLogging(),
137147
false,
138148
Components.serviceEndpoints().createServiceEndpoints(),
139149
Thread.MIN_PRIORITY,
140-
null
150+
null,
151+
instanceId
141152
);
142153
}
143-
144-
private static HttpConfiguration defaultHttp(String sdkKey) {
145-
ClientContext minimalContext = new ClientContext(sdkKey, null, null, null, false, null, 0, null);
154+
155+
private static HttpConfiguration defaultHttp(String sdkKey, String instanceId) {
156+
ClientContext minimalContext = new ClientContext(sdkKey, null, null, null, false, null, 0, null,
157+
instanceId);
146158
return Components.httpConfiguration().build(minimalContext);
147159
}
148160

lib/sdk/server/src/test/java/com/launchdarkly/sdk/server/ClientContextImplTest.java

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,15 +96,46 @@ public void packagePrivatePropertiesHaveDefaultsIfContextIsNotOurImplementation(
9696
// This covers a scenario where a user has created their own ClientContext and it has been
9797
// passed to one of our SDK components.
9898
ClientContext c = new ClientContext(SDK_KEY);
99-
99+
100100
ClientContextImpl impl = ClientContextImpl.get(c);
101-
101+
102102
assertNotNull(impl.sharedExecutor);
103103
assertNull(impl.diagnosticStore);
104-
104+
105105
ClientContextImpl impl2 = ClientContextImpl.get(c);
106-
106+
107107
assertNotNull(impl2.sharedExecutor);
108108
assertSame(impl.sharedExecutor, impl2.sharedExecutor);
109109
}
110+
111+
// Exercises the test-convenience constructor `new ClientContext(String)` that consumers of
112+
// the SDK use when wiring up custom data sources, stores, etc. Bugbot flagged that this
113+
// constructor produces an internally-inconsistent instance id: the headers carried by the
114+
// HttpConfiguration (built inside defaultHttp(sdkKey)) embed one UUID, while
115+
// getInstanceId() returns a different UUID generated by the delegated 8-arg constructor.
116+
@Test
117+
public void instanceIdIsConsistentBetweenAccessorAndDefaultHeaders() {
118+
ClientContext c = new ClientContext(SDK_KEY);
119+
120+
String accessorValue = c.getInstanceId();
121+
assertNotNull("ClientContext should expose a non-null instance id", accessorValue);
122+
123+
String headerValue = null;
124+
for (java.util.Map.Entry<String, String> entry : c.getHttp().getDefaultHeaders()) {
125+
if ("X-LaunchDarkly-Instance-Id".equalsIgnoreCase(entry.getKey())) {
126+
headerValue = entry.getValue();
127+
break;
128+
}
129+
}
130+
assertNotNull(
131+
"HttpConfiguration default headers should include X-LaunchDarkly-Instance-Id",
132+
headerValue);
133+
134+
assertEquals(
135+
"getInstanceId() should match the value attached to X-LaunchDarkly-Instance-Id on "
136+
+ "the default headers — otherwise outbound requests will not carry the same id "
137+
+ "that consumer code reads via getInstanceId().",
138+
accessorValue,
139+
headerValue);
140+
}
110141
}

0 commit comments

Comments
 (0)