From 7a2ff81559287e442f417933dcd3070817675036 Mon Sep 17 00:00:00 2001 From: Keith Lustria Date: Wed, 13 Mar 2024 17:08:33 -0700 Subject: [PATCH] Use a mocked OciExtension using Proxy that will inject a mocked Monitoring interface. Additional Note: This fix also includes a resolution to https://github.com/helidon-io/helidon/issues/7739 that is caused by the Monitoring.postMetricData() to be called before the test metrics are registered. --- integrations/oci/metrics/cdi/pom.xml | 5 - .../cdi/OciMetricsCdiExtensionTest.java | 125 ++++++++++++------ .../oci/metrics/cdi/TestOverridingBean.java | 15 ++- 3 files changed, 102 insertions(+), 43 deletions(-) diff --git a/integrations/oci/metrics/cdi/pom.xml b/integrations/oci/metrics/cdi/pom.xml index 3f8252f136b..5dbd305cbcd 100644 --- a/integrations/oci/metrics/cdi/pom.xml +++ b/integrations/oci/metrics/cdi/pom.xml @@ -62,11 +62,6 @@ hamcrest-all test - - org.mockito - mockito-core - test - diff --git a/integrations/oci/metrics/cdi/src/test/java/io/helidon/integrations/oci/metrics/cdi/OciMetricsCdiExtensionTest.java b/integrations/oci/metrics/cdi/src/test/java/io/helidon/integrations/oci/metrics/cdi/OciMetricsCdiExtensionTest.java index 6a8953ec3f5..634e6fc3aec 100644 --- a/integrations/oci/metrics/cdi/src/test/java/io/helidon/integrations/oci/metrics/cdi/OciMetricsCdiExtensionTest.java +++ b/integrations/oci/metrics/cdi/src/test/java/io/helidon/integrations/oci/metrics/cdi/OciMetricsCdiExtensionTest.java @@ -15,12 +15,14 @@ */ package io.helidon.integrations.oci.metrics.cdi; +import java.lang.annotation.Annotation; +import java.lang.reflect.Proxy; +import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import io.helidon.config.Config; import io.helidon.integrations.oci.metrics.OciMetricsSupport; -import io.helidon.integrations.oci.sdk.cdi.OciExtension; import io.helidon.metrics.api.Counter; import io.helidon.metrics.api.Meter; import io.helidon.metrics.api.MeterRegistry; @@ -40,33 +42,34 @@ import com.oracle.bmc.monitoring.requests.PostMetricDataRequest; import com.oracle.bmc.monitoring.responses.PostMetricDataResponse; -import jakarta.annotation.Priority; import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.context.Initialized; import jakarta.enterprise.event.Observes; +import jakarta.enterprise.inject.Default; +import jakarta.enterprise.inject.spi.AfterBeanDiscovery; +import jakarta.enterprise.inject.spi.Extension; +import jakarta.enterprise.inject.spi.InjectionPoint; +import jakarta.enterprise.inject.spi.ProcessInjectionPoint; +import jakarta.enterprise.inject.spi.configurator.BeanConfigurator; +import org.glassfish.jersey.ext.cdi1x.internal.CdiComponentProvider; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; -import static jakarta.interceptor.Interceptor.Priority.LIBRARY_BEFORE; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.fail; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; @HelidonTest(resetPerTest = true) @DisableDiscovery -// Add bean that will simulate oci metrics posting @AddBean(OciMetricsCdiExtensionTest.MockOciMetricsBean.class) // Helidon MP Extensions @AddExtension(ServerCdiExtension.class) @AddExtension(JaxRsCdiExtension.class) @AddExtension(ConfigCdiExtension.class) -@AddExtension(OciExtension.class) +@AddExtension(CdiComponentProvider.class) +// Add an extension that will simulate a mocked OciExtension that will inject a mocked Monitoring object +@AddExtension(OciMetricsCdiExtensionTest.MockOciMonitoringExtension.class) // ConfigSources @AddConfig(key = "ocimetrics.compartmentId", value = OciMetricsCdiExtensionTest.MetricDataDetailsOCIParams.compartmentId) @@ -74,9 +77,10 @@ value = OciMetricsCdiExtensionTest.MetricDataDetailsOCIParams.namespace) @AddConfig(key = "ocimetrics.resourceGroup", value = OciMetricsCdiExtensionTest.MetricDataDetailsOCIParams.resourceGroup) -@AddConfig(key = "ocimetrics.initialDelay", value = "1") -@AddConfig(key = "ocimetrics.delay", value = "2") +@AddConfig(key = "ocimetrics.initialDelay", value = "0") +@AddConfig(key = "ocimetrics.delay", value = "1") class OciMetricsCdiExtensionTest { + private static String METRIC_NAME_SUFFIX = "DummyCounter"; private static volatile int testMetricCount = 0; private static CountDownLatch countDownLatch = new CountDownLatch(1); private static PostMetricDataDetails postMetricDataDetails; @@ -108,13 +112,13 @@ void testDisableOciMetrics() throws InterruptedException { } private void validateOciMetricsSupport(boolean enabled) throws InterruptedException { - Counter c1 = registry.getOrCreate(Counter.builder("baseDummyCounter") + Counter c1 = registry.getOrCreate(Counter.builder(Meter.Scope.BASE + METRIC_NAME_SUFFIX) .scope(Meter.Scope.BASE)); c1.increment(); - Counter c2 = registry.getOrCreate(Counter.builder("vendorDummyCounter") + Counter c2 = registry.getOrCreate(Counter.builder(Meter.Scope.VENDOR + METRIC_NAME_SUFFIX) .scope(Meter.Scope.VENDOR)); c2.increment(); - Counter c3 = registry.getOrCreate(Counter.builder("appDummyCounter") + Counter c3 = registry.getOrCreate(Counter.builder(Meter.Scope.APPLICATION + METRIC_NAME_SUFFIX) .scope(Meter.Scope.APPLICATION)); c3.increment(); @@ -130,12 +134,9 @@ private void validateOciMetricsSupport(boolean enabled) throws InterruptedExcept assertThat(activateOciMetricsSupportIsInvoked, is(true)); // System meters in the registry might vary over time. Instead of looking for a specific number of meters, // make sure the three we added are in the OCI metric data. - long dummyCounterCount = postMetricDataDetails.getMetricData().stream() - .filter(details -> details.getName().contains("DummyCounter")) - .count(); - assertThat(dummyCounterCount, is(3L)); + assertThat(testMetricCount, is(3)); - MetricDataDetails metricDataDetails = postMetricDataDetails.getMetricData().get(0); + MetricDataDetails metricDataDetails = postMetricDataDetails.getMetricData().getFirst(); assertThat(metricDataDetails.getCompartmentId(), is(MetricDataDetailsOCIParams.compartmentId)); assertThat(metricDataDetails.getNamespace(), is(MetricDataDetailsOCIParams.namespace)); @@ -157,25 +158,75 @@ interface MetricDataDetailsOCIParams { String resourceGroup = "dummy_resourceGroup"; } - static class MockOciMetricsBean extends OciMetricsBean { - @Override - void registerOciMetrics(@Observes @Priority(LIBRARY_BEFORE + 20) @Initialized(ApplicationScoped.class) Object ignore, - Config rootConfig, Monitoring monitoringClient) { - Monitoring mockedMonitoringClient = mock(Monitoring.class); - when(mockedMonitoringClient.getEndpoint()).thenReturn("http://www.DummyEndpoint.com"); - doAnswer(invocationOnMock -> { - PostMetricDataRequest postMetricDataRequest = invocationOnMock.getArgument(0); - postMetricDataDetails = postMetricDataRequest.getPostMetricDataDetails(); - testMetricCount = postMetricDataDetails.getMetricData().size(); - // Give signal that metrics has been posted - countDownLatch.countDown(); - return PostMetricDataResponse.builder() - .__httpStatusCode__(200) - .build(); - }).when(mockedMonitoringClient).postMetricData(any()); - super.registerOciMetrics(ignore, rootConfig, mockedMonitoringClient); + // Use this to replace OciExtension, but will only process OCI Monitoring annotation. If the Monitoring + // annotation is found, a Mocked Monitoring object will be injected as a bean + static public class MockOciMonitoringExtension implements Extension { + boolean monitoringFound; + Set monitoringQualifiers; + + void processInjectionPoint(@Observes final ProcessInjectionPoint event) { + if (event != null) { + InjectionPoint ip = event.getInjectionPoint(); + Class c = (Class) ip.getAnnotated().getBaseType(); + final Set existingQualifiers = ip.getQualifiers(); + if (c == Monitoring.class && existingQualifiers != null && !existingQualifiers.isEmpty()) { + monitoringFound = true; + monitoringQualifiers = existingQualifiers; + } + } + } + + Monitoring getMockedMonitoring() { + // Use Proxy to mock only getEndPoint() and postMetricDataDetails() methods of the Monitoring interface, + // as those are the only ones needed by the test + return + (Monitoring) Proxy.newProxyInstance( + Monitoring.class.getClassLoader(), + new Class[] {Monitoring.class}, + (proxy, method, args) -> { + if (method.getName().equals("getEndpoint")) { + return "http://www.DummyEndpoint.com"; + } else if (method.getName().equals("postMetricData")) { + // startupMetricLatch.await(5, TimeUnit.SECONDS); + PostMetricDataRequest postMetricDataRequest = (PostMetricDataRequest) args[0]; + postMetricDataDetails = postMetricDataRequest.getPostMetricDataDetails(); + testMetricCount = (int) postMetricDataDetails.getMetricData().stream() + .filter(details -> details.getName().contains(METRIC_NAME_SUFFIX)) + .count(); + PostMetricDataResponse response = PostMetricDataResponse.builder() + .__httpStatusCode__(200) + .build(); + + // Give signal that metrics will be posted if the right no. of metrics has been retrieved. + // If not, that means that the metrics have not been registered yet, so try again on the + // next invocation. + if (testMetricCount > 0) { + countDownLatch.countDown(); + } + return response; + } + return null; + }); } + void afterBeanDiscovery(@Observes AfterBeanDiscovery event) { + if (monitoringFound) { + BeanConfigurator beanConfigurator = event.addBean() + .types(Monitoring.class) + .scope(ApplicationScoped.class) + .addQualifiers(monitoringQualifiers); + beanConfigurator = monitoringQualifiers != null ? beanConfigurator.addQualifiers(monitoringQualifiers) : + beanConfigurator.addQualifier(Default.Literal.INSTANCE); + // Add the mocked Monitoring as a bean + beanConfigurator.produceWith(obj -> getMockedMonitoring()); + } else { + throw new IllegalStateException("Monitoring was never injected. Check if OciMetricsBean.registerOciMetrics() " + + "has changed and does not inject Monitoring anymore."); + } + } + } + + static class MockOciMetricsBean extends OciMetricsBean { // Override so we can test if this is invoked when enabled or skipped when disabled @Override protected void activateOciMetricsSupport(Config rootConfig, Config ociMetricsConfig, OciMetricsSupport.Builder builder) { diff --git a/integrations/oci/metrics/cdi/src/test/java/io/helidon/integrations/oci/metrics/cdi/TestOverridingBean.java b/integrations/oci/metrics/cdi/src/test/java/io/helidon/integrations/oci/metrics/cdi/TestOverridingBean.java index 4a4f6b80493..39a39603abd 100644 --- a/integrations/oci/metrics/cdi/src/test/java/io/helidon/integrations/oci/metrics/cdi/TestOverridingBean.java +++ b/integrations/oci/metrics/cdi/src/test/java/io/helidon/integrations/oci/metrics/cdi/TestOverridingBean.java @@ -18,8 +18,13 @@ import java.lang.reflect.Field; import io.helidon.integrations.oci.metrics.OciMetricsSupport; +import io.helidon.microprofile.config.ConfigCdiExtension; +import io.helidon.microprofile.server.JaxRsCdiExtension; +import io.helidon.microprofile.server.ServerCdiExtension; import io.helidon.microprofile.testing.junit5.AddBean; import io.helidon.microprofile.testing.junit5.AddConfig; +import io.helidon.microprofile.testing.junit5.AddExtension; +import io.helidon.microprofile.testing.junit5.DisableDiscovery; import io.helidon.microprofile.testing.junit5.HelidonTest; import jakarta.inject.Inject; @@ -31,7 +36,15 @@ import static org.hamcrest.Matchers.is; @HelidonTest +@DisableDiscovery @AddBean(OverridingOciMetricsBean.class) +// Use OciMetricsCdiExtensionTest.MockOciMonitoringExtension to avoid Monitoring client from being instantiated +// with OCI authentication +@AddExtension(OciMetricsCdiExtensionTest.MockOciMonitoringExtension.class) +// Supporting Extensions for CDI +@AddExtension(ServerCdiExtension.class) +@AddExtension(JaxRsCdiExtension.class) +@AddExtension(ConfigCdiExtension.class) @AddConfig(key = "oci.metrics.product", value = TestOverridingBean.PRODUCT) @AddConfig(key = "oci.metrics.fleet", value = TestOverridingBean.FLEET) class TestOverridingBean { @@ -53,7 +66,7 @@ void checkOverriding() throws NoSuchFieldException, IllegalAccessException { String resourceGroup = getStringField("resourceGroup", ociMetricsSupport); assertThat("Effective namespace", namespace, is(equalTo(PRODUCT))); - assertThat("Effective namespace", resourceGroup, is(equalTo(FLEET))); + assertThat("Effective resourceGroup", resourceGroup, is(equalTo(FLEET))); } private String getStringField(String fieldName, OciMetricsSupport ociMetricsSupport)