diff --git a/gcp-auth-extension/README.md b/gcp-auth-extension/README.md index 36d6504e..34cdf4bb 100644 --- a/gcp-auth-extension/README.md +++ b/gcp-auth-extension/README.md @@ -36,7 +36,10 @@ Here is a list of configurable options for the extension: - `GOOGLE_CLOUD_PROJECT`: Environment variable that represents the Google Cloud Project ID to which the telemetry needs to be exported. - Can also be configured using `google.cloud.project` system property. - - If this option is not configured, the extension would infer GCP Project ID from the application default credentials. For more information on application default credentials, see [here](https://cloud.google.com/docs/authentication/application-default-credentials). + - This is a required option, the agent configuration will fail if this option is not set. +- `GOOGLE_CLOUD_QUOTA_PROJECT`: Environment variable that represents the Google Cloud Quota Project ID which will be charged for the GCP API usage. To learn more about a *quota project*, see [here](https://cloud.google.com/docs/quotas/quota-project). + - Can also be configured using `google.cloud.quota.project` system property. + - If this option is not configured, the extension will use the Quota Project ID found in the Application Default Credentials (ADC), if available. For more information on application default credentials, see [here](https://cloud.google.com/docs/authentication/application-default-credentials). ## Usage diff --git a/gcp-auth-extension/build.gradle.kts b/gcp-auth-extension/build.gradle.kts index f4c67021..5bbf55f6 100644 --- a/gcp-auth-extension/build.gradle.kts +++ b/gcp-auth-extension/build.gradle.kts @@ -29,6 +29,7 @@ dependencies { testCompileOnly("com.google.auto.service:auto-service-annotations") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine") testImplementation("org.junit.jupiter:junit-jupiter-api") + testCompileOnly("org.junit.jupiter:junit-jupiter-params") testImplementation("io.opentelemetry:opentelemetry-api") testImplementation("io.opentelemetry:opentelemetry-exporter-otlp") @@ -45,6 +46,9 @@ dependencies { testImplementation("org.springframework.boot:spring-boot-starter:2.7.18") testImplementation("org.springframework.boot:spring-boot-starter-test:2.7.18") + testAnnotationProcessor("com.google.auto.value:auto-value") + testCompileOnly("com.google.auto.value:auto-value-annotations") + agent("io.opentelemetry.javaagent:opentelemetry-javaagent") } diff --git a/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/ConfigurableOption.java b/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/ConfigurableOption.java index 520c59db..1bf90e48 100644 --- a/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/ConfigurableOption.java +++ b/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/ConfigurableOption.java @@ -18,7 +18,18 @@ public enum ConfigurableOption { * Represents the Google Cloud Project ID option. Can be configured using the environment variable * `GOOGLE_CLOUD_PROJECT` or the system property `google.cloud.project`. */ - GOOGLE_CLOUD_PROJECT("Google Cloud Project ID"); + GOOGLE_CLOUD_PROJECT("Google Cloud Project ID"), + + /** + * Represents the Google Cloud Quota Project ID option. Can be configured using the environment + * variable `GOOGLE_CLOUD_QUOTA_PROJECT` or the system property `google.cloud.quota.project`. The + * quota project is the project that is used for quota management and billing for the API usage. + * + *

The environment variable name is selected to be consistent with the official GCP client + * libraries. + */ + GOOGLE_CLOUD_QUOTA_PROJECT("Google Cloud Quota Project ID"); private final String userReadableName; private final String environmentVariableName; diff --git a/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java b/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java index 2fa1a666..70e9bdd3 100644 --- a/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java +++ b/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java @@ -59,15 +59,16 @@ public class GcpAuthAutoConfigurationCustomizerProvider */ @Override public void customize(AutoConfigurationCustomizer autoConfiguration) { + GoogleCredentials credentials; try { - GoogleCredentials credentials = GoogleCredentials.getApplicationDefault(); - autoConfiguration - .addSpanExporterCustomizer( - (exporter, configProperties) -> addAuthorizationHeaders(exporter, credentials)) - .addResourceCustomizer(GcpAuthAutoConfigurationCustomizerProvider::customizeResource); + credentials = GoogleCredentials.getApplicationDefault(); } catch (IOException e) { throw new GoogleAuthException(Reason.FAILED_ADC_RETRIEVAL, e); } + autoConfiguration + .addSpanExporterCustomizer( + (exporter, configProperties) -> addAuthorizationHeaders(exporter, credentials)) + .addResourceCustomizer(GcpAuthAutoConfigurationCustomizerProvider::customizeResource); } @Override @@ -100,24 +101,19 @@ public class GcpAuthAutoConfigurationCustomizerProvider } catch (IOException e) { throw new GoogleAuthException(Reason.FAILED_ADC_REFRESH, e); } - gcpHeaders.put(QUOTA_USER_PROJECT_HEADER, credentials.getQuotaProjectId()); gcpHeaders.put("Authorization", "Bearer " + credentials.getAccessToken().getTokenValue()); + String configuredQuotaProjectId = + ConfigurableOption.GOOGLE_CLOUD_QUOTA_PROJECT.getConfiguredValueWithFallback( + credentials::getQuotaProjectId); + if (configuredQuotaProjectId != null && !configuredQuotaProjectId.isEmpty()) { + gcpHeaders.put(QUOTA_USER_PROJECT_HEADER, configuredQuotaProjectId); + } return gcpHeaders; } // Updates the current resource with the attributes required for ingesting OTLP data on GCP. private static Resource customizeResource(Resource resource, ConfigProperties configProperties) { - String gcpProjectId = - ConfigurableOption.GOOGLE_CLOUD_PROJECT.getConfiguredValueWithFallback( - () -> { - try { - GoogleCredentials googleCredentials = GoogleCredentials.getApplicationDefault(); - return googleCredentials.getQuotaProjectId(); - } catch (IOException e) { - throw new GoogleAuthException(Reason.FAILED_ADC_RETRIEVAL, e); - } - }); - + String gcpProjectId = ConfigurableOption.GOOGLE_CLOUD_PROJECT.getConfiguredValue(); Resource res = Resource.create( Attributes.of(AttributeKey.stringKey(GCP_USER_PROJECT_ID_KEY), gcpProjectId)); diff --git a/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java b/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java index c03d14c9..39f626e6 100644 --- a/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java +++ b/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java @@ -9,10 +9,12 @@ import static io.opentelemetry.contrib.gcp.auth.GcpAuthAutoConfigurationCustomiz import static io.opentelemetry.contrib.gcp.auth.GcpAuthAutoConfigurationCustomizerProvider.QUOTA_USER_PROJECT_HEADER; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import com.google.auth.oauth2.AccessToken; import com.google.auth.oauth2.GoogleCredentials; +import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableMap; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.trace.Span; @@ -28,6 +30,7 @@ import io.opentelemetry.sdk.autoconfigure.internal.AutoConfigureUtil; import io.opentelemetry.sdk.autoconfigure.internal.ComponentLoader; import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; import io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider; import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.trace.data.SpanData; @@ -44,9 +47,14 @@ import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; +import java.util.stream.Stream; +import javax.annotation.Nullable; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; @@ -58,6 +66,9 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) class GcpAuthAutoConfigurationCustomizerProviderTest { + private static final String DUMMY_GCP_RESOURCE_PROJECT_ID = "my-gcp-resource-project-id"; + private static final String DUMMY_GCP_QUOTA_PROJECT_ID = "my-gcp-quota-project-id"; + @Mock private GoogleCredentials mockedGoogleCredentials; @Captor private ArgumentCaptor>> headerSupplierCaptor; @@ -74,16 +85,17 @@ class GcpAuthAutoConfigurationCustomizerProviderTest { "foo=bar"); @BeforeEach - @SuppressWarnings("CannotMockMethod") public void setup() { MockitoAnnotations.openMocks(this); - Mockito.when(mockedGoogleCredentials.getQuotaProjectId()).thenReturn("test-project"); - Mockito.when(mockedGoogleCredentials.getAccessToken()) - .thenReturn(new AccessToken("fake", Date.from(Instant.now()))); } @Test public void testCustomizerOtlpHttp() { + // Set resource project system property + System.setProperty( + ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty(), DUMMY_GCP_RESOURCE_PROJECT_ID); + // Prepare mocks + prepareMockBehaviorForGoogleCredentials(); OtlpHttpSpanExporter mockOtlpHttpSpanExporter = Mockito.mock(OtlpHttpSpanExporter.class); OtlpHttpSpanExporterBuilder otlpSpanExporterBuilder = OtlpHttpSpanExporter.builder(); OtlpHttpSpanExporterBuilder spyOtlpHttpSpanExporterBuilder = @@ -116,7 +128,7 @@ class GcpAuthAutoConfigurationCustomizerProviderTest { Mockito.verify(spyOtlpHttpSpanExporterBuilder, Mockito.times(1)) .setHeaders(headerSupplierCaptor.capture()); assertEquals(2, headerSupplierCaptor.getValue().get().size()); - assertThat(verifyAuthHeaders(headerSupplierCaptor.getValue().get())).isTrue(); + assertThat(authHeadersQuotaProjectIsPresent(headerSupplierCaptor.getValue().get())).isTrue(); Mockito.verify(mockOtlpHttpSpanExporter, Mockito.atLeast(1)).export(Mockito.anyCollection()); @@ -125,7 +137,9 @@ class GcpAuthAutoConfigurationCustomizerProviderTest { .allSatisfy( spanData -> { assertThat(spanData.getResource().getAttributes().asMap()) - .containsEntry(AttributeKey.stringKey(GCP_USER_PROJECT_ID_KEY), "test-project") + .containsEntry( + AttributeKey.stringKey(GCP_USER_PROJECT_ID_KEY), + DUMMY_GCP_RESOURCE_PROJECT_ID) .containsEntry(AttributeKey.stringKey("foo"), "bar"); assertThat(spanData.getAttributes().asMap()) .containsKey(AttributeKey.longKey("work_loop")); @@ -135,20 +149,17 @@ class GcpAuthAutoConfigurationCustomizerProviderTest { @Test public void testCustomizerOtlpGrpc() { + // Set resource project system property + System.setProperty( + ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty(), DUMMY_GCP_RESOURCE_PROJECT_ID); + // Prepare mocks + prepareMockBehaviorForGoogleCredentials(); OtlpGrpcSpanExporter mockOtlpGrpcSpanExporter = Mockito.mock(OtlpGrpcSpanExporter.class); - OtlpGrpcSpanExporterBuilder otlpSpanExporterBuilder = OtlpGrpcSpanExporter.builder(); OtlpGrpcSpanExporterBuilder spyOtlpGrpcSpanExporterBuilder = - Mockito.spy(otlpSpanExporterBuilder); - Mockito.when(spyOtlpGrpcSpanExporterBuilder.build()).thenReturn(mockOtlpGrpcSpanExporter); - Mockito.when(mockOtlpGrpcSpanExporter.shutdown()).thenReturn(CompletableResultCode.ofSuccess()); + Mockito.spy(OtlpGrpcSpanExporter.builder()); List exportedSpans = new ArrayList<>(); - Mockito.when(mockOtlpGrpcSpanExporter.export(Mockito.anyCollection())) - .thenAnswer( - invocationOnMock -> { - exportedSpans.addAll(invocationOnMock.getArgument(0)); - return CompletableResultCode.ofSuccess(); - }); - Mockito.when(mockOtlpGrpcSpanExporter.toBuilder()).thenReturn(spyOtlpGrpcSpanExporterBuilder); + configureGrpcMockExporters( + mockOtlpGrpcSpanExporter, spyOtlpGrpcSpanExporterBuilder, exportedSpans); try (MockedStatic googleCredentialsMockedStatic = Mockito.mockStatic(GoogleCredentials.class)) { @@ -166,7 +177,7 @@ class GcpAuthAutoConfigurationCustomizerProviderTest { Mockito.verify(spyOtlpGrpcSpanExporterBuilder, Mockito.times(1)) .setHeaders(headerSupplierCaptor.capture()); assertEquals(2, headerSupplierCaptor.getValue().get().size()); - verifyAuthHeaders(headerSupplierCaptor.getValue().get()); + assertThat(authHeadersQuotaProjectIsPresent(headerSupplierCaptor.getValue().get())).isTrue(); Mockito.verify(mockOtlpGrpcSpanExporter, Mockito.atLeast(1)).export(Mockito.anyCollection()); @@ -175,7 +186,9 @@ class GcpAuthAutoConfigurationCustomizerProviderTest { .allSatisfy( spanData -> { assertThat(spanData.getResource().getAttributes().asMap()) - .containsEntry(AttributeKey.stringKey(GCP_USER_PROJECT_ID_KEY), "test-project") + .containsEntry( + AttributeKey.stringKey(GCP_USER_PROJECT_ID_KEY), + DUMMY_GCP_RESOURCE_PROJECT_ID) .containsEntry(AttributeKey.stringKey("foo"), "bar"); assertThat(spanData.getAttributes().asMap()) .containsKey(AttributeKey.longKey("work_loop")); @@ -183,6 +196,209 @@ class GcpAuthAutoConfigurationCustomizerProviderTest { } } + @Test + public void testCustomizerFailWithMissingResourceProject() { + OtlpGrpcSpanExporter mockOtlpGrpcSpanExporter = Mockito.mock(OtlpGrpcSpanExporter.class); + try (MockedStatic googleCredentialsMockedStatic = + Mockito.mockStatic(GoogleCredentials.class)) { + googleCredentialsMockedStatic + .when(GoogleCredentials::getApplicationDefault) + .thenReturn(mockedGoogleCredentials); + + assertThrows( + ConfigurationException.class, + () -> buildOpenTelemetrySdkWithExporter(mockOtlpGrpcSpanExporter)); + } + } + + @ParameterizedTest + @MethodSource("provideQuotaBehaviorTestCases") + @SuppressWarnings("CannotMockMethod") + public void testQuotaProjectBehavior(QuotaProjectIdTestBehavior testCase) { + // Set resource project system property + System.setProperty( + ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty(), DUMMY_GCP_RESOURCE_PROJECT_ID); + // Configure mock credentials to return fake access token + Mockito.when(mockedGoogleCredentials.getAccessToken()) + .thenReturn(new AccessToken("fake", Date.from(Instant.now()))); + + // To prevent unncecessary stubbings, mock getQuotaProjectId only when necessary + if (testCase.getUserSpecifiedQuotaProjectId() == null + || testCase.getUserSpecifiedQuotaProjectId().isEmpty()) { + String quotaProjectFromCredential = + testCase.getIsQuotaProjectPresentInCredentials() ? DUMMY_GCP_QUOTA_PROJECT_ID : null; + Mockito.when(mockedGoogleCredentials.getQuotaProjectId()) + .thenReturn(quotaProjectFromCredential); + } + + // configure environment according to test case + String quotaProjectId = testCase.getUserSpecifiedQuotaProjectId(); // maybe empty string + if (testCase.getUserSpecifiedQuotaProjectId() != null) { + // user specified a quota project id + System.setProperty( + ConfigurableOption.GOOGLE_CLOUD_QUOTA_PROJECT.getSystemProperty(), quotaProjectId); + } + + // prepare mock exporter + OtlpGrpcSpanExporter mockOtlpGrpcSpanExporter = Mockito.mock(OtlpGrpcSpanExporter.class); + OtlpGrpcSpanExporterBuilder spyOtlpGrpcSpanExporterBuilder = + Mockito.spy(OtlpGrpcSpanExporter.builder()); + List exportedSpans = new ArrayList<>(); + configureGrpcMockExporters( + mockOtlpGrpcSpanExporter, spyOtlpGrpcSpanExporterBuilder, exportedSpans); + + try (MockedStatic googleCredentialsMockedStatic = + Mockito.mockStatic(GoogleCredentials.class)) { + googleCredentialsMockedStatic + .when(GoogleCredentials::getApplicationDefault) + .thenReturn(mockedGoogleCredentials); + + // Export telemetry to capture headers in the export calls + OpenTelemetrySdk sdk = buildOpenTelemetrySdkWithExporter(mockOtlpGrpcSpanExporter); + generateTestSpan(sdk); + CompletableResultCode code = sdk.shutdown(); + CompletableResultCode joinResult = code.join(10, TimeUnit.SECONDS); + assertTrue(joinResult.isSuccess()); + Mockito.verify(spyOtlpGrpcSpanExporterBuilder, Mockito.times(1)) + .setHeaders(headerSupplierCaptor.capture()); + + // assert that the Authorization bearer token header is present + Map exportHeaders = headerSupplierCaptor.getValue().get(); + assertThat(exportHeaders).containsEntry("Authorization", "Bearer fake"); + + if (testCase.getExpectedQuotaProjectInHeader() == null) { + // there should be no user quota project header + assertThat(exportHeaders).doesNotContainKey(QUOTA_USER_PROJECT_HEADER); + } else { + // there should be user quota project header with expected value + assertThat(exportHeaders) + .containsEntry(QUOTA_USER_PROJECT_HEADER, testCase.getExpectedQuotaProjectInHeader()); + } + } + } + + /** + * Test cases specifying expected value for the user quota project header given the user input and + * the current credentials state. + * + *

{@code null} for {@link QuotaProjectIdTestBehavior#getUserSpecifiedQuotaProjectId()} + * indicates the case of user not specifying the quota project ID. + * + *

{@code null} value for {@link QuotaProjectIdTestBehavior#getExpectedQuotaProjectInHeader()} + * indicates the expectation that the QUOTA_USER_PROJECT_HEADER should not be present in the + * export headers. + * + *

{@code true} for {@link QuotaProjectIdTestBehavior#getIsQuotaProjectPresentInCredentials()} + * indicates that the mocked credentials are configured to provide DUMMY_GCP_QUOTA_PROJECT_ID as + * the quota project ID. + */ + private static Stream provideQuotaBehaviorTestCases() { + return Stream.of( + Arguments.of( + QuotaProjectIdTestBehavior.builder() + .setUserSpecifiedQuotaProjectId(DUMMY_GCP_QUOTA_PROJECT_ID) + .setIsQuotaProjectPresentInCredentials(true) + .setExpectedQuotaProjectInHeader(DUMMY_GCP_QUOTA_PROJECT_ID) + .build()), + Arguments.of( + QuotaProjectIdTestBehavior.builder() + .setUserSpecifiedQuotaProjectId(DUMMY_GCP_QUOTA_PROJECT_ID) + .setIsQuotaProjectPresentInCredentials(false) + .setExpectedQuotaProjectInHeader(DUMMY_GCP_QUOTA_PROJECT_ID) + .build()), + Arguments.of( + QuotaProjectIdTestBehavior.builder() + .setUserSpecifiedQuotaProjectId("my-custom-quota-project-id") + .setIsQuotaProjectPresentInCredentials(true) + .setExpectedQuotaProjectInHeader("my-custom-quota-project-id") + .build()), + Arguments.of( + QuotaProjectIdTestBehavior.builder() + .setUserSpecifiedQuotaProjectId("my-custom-quota-project-id") + .setIsQuotaProjectPresentInCredentials(false) + .setExpectedQuotaProjectInHeader("my-custom-quota-project-id") + .build()), + Arguments.of( + QuotaProjectIdTestBehavior.builder() + .setUserSpecifiedQuotaProjectId("") // user explicitly specifies empty + .setIsQuotaProjectPresentInCredentials(true) + .setExpectedQuotaProjectInHeader(DUMMY_GCP_QUOTA_PROJECT_ID) + .build()), + Arguments.of( + QuotaProjectIdTestBehavior.builder() + .setUserSpecifiedQuotaProjectId(null) // user omits specifying quota project + .setIsQuotaProjectPresentInCredentials(true) + .setExpectedQuotaProjectInHeader(DUMMY_GCP_QUOTA_PROJECT_ID) + .build()), + Arguments.of( + QuotaProjectIdTestBehavior.builder() + .setUserSpecifiedQuotaProjectId("") + .setIsQuotaProjectPresentInCredentials(false) + .setExpectedQuotaProjectInHeader(null) + .build()), + Arguments.of( + QuotaProjectIdTestBehavior.builder() + .setUserSpecifiedQuotaProjectId(null) + .setIsQuotaProjectPresentInCredentials(false) + .setExpectedQuotaProjectInHeader(null) + .build())); + } + + // Configure necessary behavior on the Grpc mock exporters to work + // TODO: Potential improvement - make this work for Http exporter as well. + private static void configureGrpcMockExporters( + OtlpGrpcSpanExporter mockGrpcExporter, + OtlpGrpcSpanExporterBuilder spyGrpcExporterBuilder, + List exportedSpanContainer) { + Mockito.when(spyGrpcExporterBuilder.build()).thenReturn(mockGrpcExporter); + Mockito.when(mockGrpcExporter.shutdown()).thenReturn(CompletableResultCode.ofSuccess()); + Mockito.when(mockGrpcExporter.toBuilder()).thenReturn(spyGrpcExporterBuilder); + Mockito.when(mockGrpcExporter.export(Mockito.anyCollection())) + .thenAnswer( + invocationOnMock -> { + exportedSpanContainer.addAll(invocationOnMock.getArgument(0)); + return CompletableResultCode.ofSuccess(); + }); + } + + @AutoValue + abstract static class QuotaProjectIdTestBehavior { + // A null user specified quota represents the use case where user omits specifying quota + @Nullable + abstract String getUserSpecifiedQuotaProjectId(); + + abstract boolean getIsQuotaProjectPresentInCredentials(); + + // If expected quota project in header is null, the header entry should not be present in export + @Nullable + abstract String getExpectedQuotaProjectInHeader(); + + static Builder builder() { + return new AutoValue_GcpAuthAutoConfigurationCustomizerProviderTest_QuotaProjectIdTestBehavior + .Builder(); + } + + @AutoValue.Builder + abstract static class Builder { + abstract Builder setUserSpecifiedQuotaProjectId(String quotaProjectId); + + abstract Builder setIsQuotaProjectPresentInCredentials( + boolean quotaProjectPresentInCredentials); + + abstract Builder setExpectedQuotaProjectInHeader(String expectedQuotaProjectInHeader); + + abstract QuotaProjectIdTestBehavior build(); + } + } + + @SuppressWarnings("CannotMockMethod") + private void prepareMockBehaviorForGoogleCredentials() { + Mockito.when(mockedGoogleCredentials.getQuotaProjectId()) + .thenReturn(DUMMY_GCP_QUOTA_PROJECT_ID); + Mockito.when(mockedGoogleCredentials.getAccessToken()) + .thenReturn(new AccessToken("fake", Date.from(Instant.now()))); + } + private OpenTelemetrySdk buildOpenTelemetrySdkWithExporter(SpanExporter spanExporter) { SpiHelper spiHelper = SpiHelper.create(GcpAuthAutoConfigurationCustomizerProviderTest.class.getClassLoader()); @@ -215,9 +431,12 @@ class GcpAuthAutoConfigurationCustomizerProviderTest { return builder.build().getOpenTelemetrySdk(); } - private static boolean verifyAuthHeaders(Map headers) { + private static boolean authHeadersQuotaProjectIsPresent(Map headers) { Set> headerEntrySet = headers.entrySet(); - return headerEntrySet.contains(new SimpleEntry<>(QUOTA_USER_PROJECT_HEADER, "test-project")) + return headerEntrySet.contains( + new SimpleEntry<>( + QUOTA_USER_PROJECT_HEADER, + GcpAuthAutoConfigurationCustomizerProviderTest.DUMMY_GCP_QUOTA_PROJECT_ID)) && headerEntrySet.contains(new SimpleEntry<>("Authorization", "Bearer fake")); }