3535import io .opentelemetry .sdk .common .CompletableResultCode ;
3636import io .opentelemetry .sdk .trace .data .SpanData ;
3737import io .opentelemetry .sdk .trace .export .SpanExporter ;
38+ import java .io .IOException ;
3839import java .time .Duration ;
3940import java .time .Instant ;
4041import java .util .AbstractMap .SimpleEntry ;
@@ -214,26 +215,33 @@ public void testCustomizerFailWithMissingResourceProject() {
214215 @ ParameterizedTest
215216 @ MethodSource ("provideQuotaBehaviorTestCases" )
216217 @ SuppressWarnings ("CannotMockMethod" )
217- public void testQuotaProjectBehavior (QuotaProjectIdTestBehavior testCase ) {
218+ public void testQuotaProjectBehavior (QuotaProjectIdTestBehavior testCase ) throws IOException {
218219 // Set resource project system property
219220 System .setProperty (
220221 ConfigurableOption .GOOGLE_CLOUD_PROJECT .getSystemProperty (), DUMMY_GCP_RESOURCE_PROJECT_ID );
221- // Configure mock credentials to return fake access token
222- Mockito .when (mockedGoogleCredentials .getAccessToken ())
223- .thenReturn (new AccessToken ("fake" , Date .from (Instant .now ())));
224-
225- // To prevent unncecessary stubbings, mock getQuotaProjectId only when necessary
226- if (testCase .getUserSpecifiedQuotaProjectId () == null
227- || testCase .getUserSpecifiedQuotaProjectId ().isEmpty ()) {
228- String quotaProjectFromCredential =
229- testCase .getIsQuotaProjectPresentInCredentials () ? DUMMY_GCP_QUOTA_PROJECT_ID : null ;
230- Mockito .when (mockedGoogleCredentials .getQuotaProjectId ())
231- .thenReturn (quotaProjectFromCredential );
222+
223+ // Prepare request metadata
224+ AccessToken fakeAccessToken = new AccessToken ("fake" , Date .from (Instant .now ()));
225+ ImmutableMap <String , List <String >> mockedRequestMetadata ;
226+ if (testCase .getIsQuotaProjectPresentInMetadata ()) {
227+ mockedRequestMetadata =
228+ ImmutableMap .of (
229+ "Authorization" ,
230+ Collections .singletonList ("Bearer " + fakeAccessToken .getTokenValue ()),
231+ QUOTA_USER_PROJECT_HEADER ,
232+ Collections .singletonList (DUMMY_GCP_QUOTA_PROJECT_ID ));
233+ } else {
234+ mockedRequestMetadata =
235+ ImmutableMap .of (
236+ "Authorization" ,
237+ Collections .singletonList ("Bearer " + fakeAccessToken .getTokenValue ()));
232238 }
239+ // mock credentials to return the prepared request metadata
240+ Mockito .when (mockedGoogleCredentials .getRequestMetadata ()).thenReturn (mockedRequestMetadata );
233241
234242 // configure environment according to test case
235243 String quotaProjectId = testCase .getUserSpecifiedQuotaProjectId (); // maybe empty string
236- if (testCase . getUserSpecifiedQuotaProjectId () != null ) {
244+ if (quotaProjectId != null ) {
237245 // user specified a quota project id
238246 System .setProperty (
239247 ConfigurableOption .GOOGLE_CLOUD_QUOTA_PROJECT .getSystemProperty (), quotaProjectId );
@@ -288,58 +296,62 @@ public void testQuotaProjectBehavior(QuotaProjectIdTestBehavior testCase) {
288296 * indicates the expectation that the QUOTA_USER_PROJECT_HEADER should not be present in the
289297 * export headers.
290298 *
291- * <p>{@code true} for {@link QuotaProjectIdTestBehavior#getIsQuotaProjectPresentInCredentials ()}
299+ * <p>{@code true} for {@link QuotaProjectIdTestBehavior#getIsQuotaProjectPresentInMetadata ()}
292300 * indicates that the mocked credentials are configured to provide DUMMY_GCP_QUOTA_PROJECT_ID as
293301 * the quota project ID.
294302 */
295303 private static Stream <Arguments > provideQuotaBehaviorTestCases () {
296304 return Stream .of (
305+ // If quota project present in metadata, it will be used
297306 Arguments .of (
298307 QuotaProjectIdTestBehavior .builder ()
299308 .setUserSpecifiedQuotaProjectId (DUMMY_GCP_QUOTA_PROJECT_ID )
300- .setIsQuotaProjectPresentInCredentials (true )
309+ .setIsQuotaProjectPresentInMetadata (true )
301310 .setExpectedQuotaProjectInHeader (DUMMY_GCP_QUOTA_PROJECT_ID )
302311 .build ()),
303312 Arguments .of (
304313 QuotaProjectIdTestBehavior .builder ()
305- .setUserSpecifiedQuotaProjectId (DUMMY_GCP_QUOTA_PROJECT_ID )
306- .setIsQuotaProjectPresentInCredentials ( false )
314+ .setUserSpecifiedQuotaProjectId ("my-custom-quota-project-id" )
315+ .setIsQuotaProjectPresentInMetadata ( true )
307316 .setExpectedQuotaProjectInHeader (DUMMY_GCP_QUOTA_PROJECT_ID )
308317 .build ()),
318+ // If quota project not present in request metadata, then user specified project is used
309319 Arguments .of (
310320 QuotaProjectIdTestBehavior .builder ()
311- .setUserSpecifiedQuotaProjectId ("my-custom-quota-project-id" )
312- .setIsQuotaProjectPresentInCredentials ( true )
313- .setExpectedQuotaProjectInHeader ("my-custom-quota-project-id" )
321+ .setUserSpecifiedQuotaProjectId (DUMMY_GCP_QUOTA_PROJECT_ID )
322+ .setIsQuotaProjectPresentInMetadata ( false )
323+ .setExpectedQuotaProjectInHeader (DUMMY_GCP_QUOTA_PROJECT_ID )
314324 .build ()),
315325 Arguments .of (
316326 QuotaProjectIdTestBehavior .builder ()
317327 .setUserSpecifiedQuotaProjectId ("my-custom-quota-project-id" )
318- .setIsQuotaProjectPresentInCredentials (false )
328+ .setIsQuotaProjectPresentInMetadata (false )
319329 .setExpectedQuotaProjectInHeader ("my-custom-quota-project-id" )
320330 .build ()),
331+ // Testing for special edge case inputs
332+ // user-specified quota project is empty
321333 Arguments .of (
322334 QuotaProjectIdTestBehavior .builder ()
323335 .setUserSpecifiedQuotaProjectId ("" ) // user explicitly specifies empty
324- .setIsQuotaProjectPresentInCredentials (true )
336+ .setIsQuotaProjectPresentInMetadata (true )
325337 .setExpectedQuotaProjectInHeader (DUMMY_GCP_QUOTA_PROJECT_ID )
326338 .build ()),
327339 Arguments .of (
328340 QuotaProjectIdTestBehavior .builder ()
329- .setUserSpecifiedQuotaProjectId (null ) // user omits specifying quota project
330- .setIsQuotaProjectPresentInCredentials ( true )
331- .setExpectedQuotaProjectInHeader (DUMMY_GCP_QUOTA_PROJECT_ID )
341+ .setUserSpecifiedQuotaProjectId ("" )
342+ .setIsQuotaProjectPresentInMetadata ( false )
343+ .setExpectedQuotaProjectInHeader (null )
332344 .build ()),
333345 Arguments .of (
334346 QuotaProjectIdTestBehavior .builder ()
335- .setUserSpecifiedQuotaProjectId ("" )
336- .setIsQuotaProjectPresentInCredentials ( false )
337- .setExpectedQuotaProjectInHeader (null )
347+ .setUserSpecifiedQuotaProjectId (null ) // user omits specifying quota project
348+ .setIsQuotaProjectPresentInMetadata ( true )
349+ .setExpectedQuotaProjectInHeader (DUMMY_GCP_QUOTA_PROJECT_ID )
338350 .build ()),
339351 Arguments .of (
340352 QuotaProjectIdTestBehavior .builder ()
341353 .setUserSpecifiedQuotaProjectId (null )
342- .setIsQuotaProjectPresentInCredentials (false )
354+ .setIsQuotaProjectPresentInMetadata (false )
343355 .setExpectedQuotaProjectInHeader (null )
344356 .build ()));
345357 }
@@ -367,7 +379,7 @@ abstract static class QuotaProjectIdTestBehavior {
367379 @ Nullable
368380 abstract String getUserSpecifiedQuotaProjectId ();
369381
370- abstract boolean getIsQuotaProjectPresentInCredentials ();
382+ abstract boolean getIsQuotaProjectPresentInMetadata ();
371383
372384 // If expected quota project in header is null, the header entry should not be present in export
373385 @ Nullable
@@ -382,9 +394,15 @@ static Builder builder() {
382394 abstract static class Builder {
383395 abstract Builder setUserSpecifiedQuotaProjectId (String quotaProjectId );
384396
385- abstract Builder setIsQuotaProjectPresentInCredentials (
386- boolean quotaProjectPresentInCredentials );
397+ abstract Builder setIsQuotaProjectPresentInMetadata (boolean quotaProjectPresentInMetadata );
387398
399+ /**
400+ * Sets the expected quota project header value for the test case. A null value is allowed,
401+ * and it indicates that the header should not be present in the export request.
402+ *
403+ * @param expectedQuotaProjectInHeader the expected header value to match in the export
404+ * headers.
405+ */
388406 abstract Builder setExpectedQuotaProjectInHeader (String expectedQuotaProjectInHeader );
389407
390408 abstract QuotaProjectIdTestBehavior build ();
@@ -393,10 +411,18 @@ abstract Builder setIsQuotaProjectPresentInCredentials(
393411
394412 @ SuppressWarnings ("CannotMockMethod" )
395413 private void prepareMockBehaviorForGoogleCredentials () {
396- Mockito .when (mockedGoogleCredentials .getQuotaProjectId ())
397- .thenReturn (DUMMY_GCP_QUOTA_PROJECT_ID );
398- Mockito .when (mockedGoogleCredentials .getAccessToken ())
399- .thenReturn (new AccessToken ("fake" , Date .from (Instant .now ())));
414+ AccessToken fakeAccessToken = new AccessToken ("fake" , Date .from (Instant .now ()));
415+ try {
416+ Mockito .when (mockedGoogleCredentials .getRequestMetadata ())
417+ .thenReturn (
418+ ImmutableMap .of (
419+ "Authorization" ,
420+ Collections .singletonList ("Bearer " + fakeAccessToken .getTokenValue ()),
421+ QUOTA_USER_PROJECT_HEADER ,
422+ Collections .singletonList (DUMMY_GCP_QUOTA_PROJECT_ID )));
423+ } catch (IOException e ) {
424+ throw new RuntimeException (e );
425+ }
400426 }
401427
402428 private OpenTelemetrySdk buildOpenTelemetrySdkWithExporter (SpanExporter spanExporter ) {
0 commit comments