Refactor test: remove micrometer mocking, micrometer is testable #32

Merged
sschnabe merged 1 commit from micrometer into main 2023-05-02 09:02:28 +02:00
7 changed files with 61 additions and 70 deletions
Showing only changes of commit ac7872b226 - Show all commits

View file

@ -159,7 +159,7 @@
<groupId>com.openshift</groupId> <groupId>com.openshift</groupId>
<artifactId>openshift-restclient-java</artifactId> <artifactId>openshift-restclient-java</artifactId>
</exclusion> </exclusion>
<exclusion> <exclusion><!-- https://github.com/keycloak/keycloak/issues/19850 -->
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-admin-ui</artifactId> <artifactId>keycloak-admin-ui</artifactId>
</exclusion> </exclusion>

View file

@ -10,7 +10,7 @@ import org.keycloak.models.KeycloakContext;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Metrics;
/** /**
* Listener for {@link Event} and {@link AdminEvent}. * Listener for {@link Event} and {@link AdminEvent}.
@ -20,19 +20,17 @@ import io.micrometer.core.instrument.MeterRegistry;
public class MetricsEventListener implements EventListenerProvider, AutoCloseable { public class MetricsEventListener implements EventListenerProvider, AutoCloseable {
private static final Logger log = Logger.getLogger(MetricsEventListener.class); private static final Logger log = Logger.getLogger(MetricsEventListener.class);
private final MeterRegistry registry;
private final boolean replaceIds; private final boolean replaceIds;
private final KeycloakSession session; private final KeycloakSession session;
MetricsEventListener(MeterRegistry registry, boolean replaceIds, KeycloakSession session) { MetricsEventListener(boolean replaceIds, KeycloakSession session) {
this.registry = registry;
this.replaceIds = replaceIds; this.replaceIds = replaceIds;
this.session = session; this.session = session;
} }
@Override @Override
public void onEvent(Event event) { public void onEvent(Event event) {
registry.counter("keycloak_event_user", Metrics.counter("keycloak_event_user",
"realm", toBlank(replaceIds ? getRealmName(event.getRealmId()) : event.getRealmId()), "realm", toBlank(replaceIds ? getRealmName(event.getRealmId()) : event.getRealmId()),
"type", toBlank(event.getType()), "type", toBlank(event.getType()),
"client", toBlank(event.getClientId()), "client", toBlank(event.getClientId()),
@ -42,7 +40,7 @@ public class MetricsEventListener implements EventListenerProvider, AutoCloseabl
@Override @Override
public void onEvent(AdminEvent event, boolean includeRepresentation) { public void onEvent(AdminEvent event, boolean includeRepresentation) {
registry.counter("keycloak_event_admin", Metrics.counter("keycloak_event_admin",
"realm", toBlank(replaceIds ? getRealmName(event.getRealmId()) : event.getRealmId()), "realm", toBlank(replaceIds ? getRealmName(event.getRealmId()) : event.getRealmId()),
"resource", toBlank(event.getResourceType()), "resource", toBlank(event.getResourceType()),
"operation", toBlank(event.getOperationType()), "operation", toBlank(event.getOperationType()),

View file

@ -7,11 +7,8 @@ import org.keycloak.events.EventListenerProviderFactory;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Metrics;
/** /**
* Factory for {@link MetricsEventListener}, uses {@link MeterRegistry} from CDI. * Factory for {@link MetricsEventListener}.
* *
* @author Stephan Schnabel * @author Stephan Schnabel
*/ */
@ -36,7 +33,7 @@ public class MetricsEventListenerFactory implements EventListenerProviderFactory
@Override @Override
public EventListenerProvider create(KeycloakSession session) { public EventListenerProvider create(KeycloakSession session) {
return new MetricsEventListener(Metrics.globalRegistry, replaceIds, session); return new MetricsEventListener(replaceIds, session);
} }
@Override @Override

View file

@ -10,8 +10,6 @@ import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.timer.TimerProvider; import org.keycloak.timer.TimerProvider;
import io.micrometer.core.instrument.Metrics;
/** /**
* Implementation of {@link MetricsStatsFactory}. * Implementation of {@link MetricsStatsFactory}.
* *
@ -53,10 +51,10 @@ public class MetricsStatsFactoryImpl implements MetricsStatsFactory {
intervalDuration, infoThreshold, warnThreshold); intervalDuration, infoThreshold, warnThreshold);
var interval = intervalDuration.toMillis(); var interval = intervalDuration.toMillis();
var task = new MetricsStatsTask(Metrics.globalRegistry, intervalDuration, infoThreshold, warnThreshold); var task = new MetricsStatsTask(intervalDuration, infoThreshold, warnThreshold);
KeycloakModelUtils.runJobInTransaction(factory, session -> session KeycloakModelUtils.runJobInTransaction(factory, session -> session
.getProvider(TimerProvider.class) .getProvider(TimerProvider.class)
.schedule(() -> KeycloakModelUtils.runJobInTransaction(factory, task), interval, "metrics")); .scheduleTask(task, interval, "metrics"));
} }
@Override @Override

View file

@ -9,10 +9,10 @@ import java.util.concurrent.atomic.AtomicLong;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionTask;
import org.keycloak.provider.Provider; import org.keycloak.provider.Provider;
import org.keycloak.timer.ScheduledTask;
import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.Tag; import io.micrometer.core.instrument.Tag;
/** /**
@ -20,17 +20,15 @@ import io.micrometer.core.instrument.Tag;
* *
* @author Stephan Schnabel * @author Stephan Schnabel
*/ */
public class MetricsStatsTask implements Provider, KeycloakSessionTask { public class MetricsStatsTask implements Provider, ScheduledTask {
private static final Logger log = Logger.getLogger(MetricsStatsTask.class); private static final Logger log = Logger.getLogger(MetricsStatsTask.class);
private final Map<String, AtomicLong> values = new HashMap<>(); private static final Map<String, AtomicLong> values = new HashMap<>();
private final MeterRegistry registry;
private final Duration interval; private final Duration interval;
private final Duration infoThreshold; private final Duration infoThreshold;
private final Duration warnThreshold; private final Duration warnThreshold;
MetricsStatsTask(MeterRegistry registry, Duration interval, Duration infoThreshold, Duration warnThreshold) { MetricsStatsTask(Duration interval, Duration infoThreshold, Duration warnThreshold) {
this.registry = registry;
this.interval = interval; this.interval = interval;
this.infoThreshold = infoThreshold; this.infoThreshold = infoThreshold;
this.warnThreshold = warnThreshold; this.warnThreshold = warnThreshold;
@ -84,6 +82,6 @@ public class MetricsStatsTask implements Provider, KeycloakSessionTask {
} }
private void gauge(String name, Set<Tag> tags, long value) { private void gauge(String name, Set<Tag> tags, long value) {
values.computeIfAbsent(name + tags, s -> registry.gauge(name, tags, new AtomicLong())).set(value); values.computeIfAbsent(name + tags, s -> Metrics.gauge(name, tags, new AtomicLong())).set(value);
} }
} }

View file

@ -1,22 +1,13 @@
package io.kokuwa.keycloak.metrics.event; package io.kokuwa.keycloak.metrics.event;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import java.util.Map;
import java.util.TreeMap;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.keycloak.events.Event; import org.keycloak.events.Event;
import org.keycloak.events.EventType; import org.keycloak.events.EventType;
import org.keycloak.events.admin.AdminEvent; import org.keycloak.events.admin.AdminEvent;
@ -26,21 +17,18 @@ import org.keycloak.models.KeycloakContext;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.RealmProvider; import org.keycloak.models.RealmProvider;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import io.micrometer.core.instrument.Counter; import io.kokuwa.keycloak.metrics.junit.AbstractMockitoTest;
import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Metrics;
/** /**
* Test for {@link MetricsEventListener} with Mockito. * Test for {@link MetricsEventListener} with Mockito.
* *
* @author Stephan Schnabel * @author Stephan Schnabel
*/ */
@ExtendWith(MockitoExtension.class) @DisplayName("events: listener")
public class MetricsEventListenerTest { public class MetricsEventListenerTest extends AbstractMockitoTest {
@Mock @Mock
KeycloakSession session; KeycloakSession session;
@ -50,19 +38,6 @@ public class MetricsEventListenerTest {
RealmProvider realmProvider; RealmProvider realmProvider;
@Mock @Mock
KeycloakContext context; KeycloakContext context;
@Mock
MeterRegistry registry;
@Mock
Counter counter;
@Captor
ArgumentCaptor<String> metricCaptor;
@Captor
ArgumentCaptor<String[]> tagsCaptor;
@BeforeEach
void setup() {
when(registry.counter(metricCaptor.capture(), tagsCaptor.capture())).thenReturn(counter);
}
@DisplayName("onEvent(true)") @DisplayName("onEvent(true)")
@Nested @Nested
@ -213,11 +188,11 @@ public class MetricsEventListenerTest {
} }
private void assertEvent(String realm, String client, String type, String error) { private void assertEvent(String realm, String client, String type, String error) {
assertCounter("keycloak_event_user", Map.of( assertCounter("keycloak_event_user",
"realm", realm, "realm", realm,
"client", client, "client", client,
"type", type, "type", type,
"error", error)); "error", error);
} }
} }
@ -370,29 +345,25 @@ public class MetricsEventListenerTest {
} }
private void assertAdminEvent(String realm, String resource, String operation, String error) { private void assertAdminEvent(String realm, String resource, String operation, String error) {
assertCounter("keycloak_event_admin", Map.of( assertCounter("keycloak_event_admin",
"realm", realm, "realm", realm,
"resource", resource, "resource", resource,
"operation", operation, "operation", operation,
"error", error)); "error", error);
} }
} }
private MetricsEventListener listener(boolean replace) { private MetricsEventListener listener(boolean replace) {
return new MetricsEventListener(registry, replace, session); return new MetricsEventListener(replace, session);
} }
private void assertCounter(String metric, Map<String, String> tags) { private static void assertCounter(String metric, String... tags) {
verify(registry).counter(anyString(), any(String[].class)); var counter = Metrics.globalRegistry.counter(metric, tags);
verify(counter).increment(); assertEquals(1D, counter.count(), "micrometer.counter.count");
assertEquals(metric, metricCaptor.getValue(), "metric"); assertEquals(0, Metrics.globalRegistry
var expectedTags = new TreeMap<>(tags); .getMeters().stream()
var actualTags = IntStream .filter(meter -> meter != counter)
.range(0, tagsCaptor.getValue().length / 2).mapToObj(i -> i * 2) .count(),
.collect(Collectors.toMap( "other meter found");
i -> tagsCaptor.getValue()[i],
i -> tagsCaptor.getValue()[i + 1],
(i, j) -> i, TreeMap::new));
assertEquals(expectedTags, actualTags, "tags");
} }
} }

View file

@ -0,0 +1,29 @@
package io.kokuwa.keycloak.metrics.junit;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.ClassOrderer;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.TestClassOrder;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
/**
* Mockito base class with configured logging.
*
* @author Stephan Schnabel
*/
@ExtendWith(MockitoExtension.class)
@TestClassOrder(ClassOrderer.DisplayName.class)
@TestMethodOrder(MethodOrderer.DisplayName.class)
public abstract class AbstractMockitoTest {
@BeforeEach
void reset() {
Metrics.globalRegistry.clear();
Metrics.addRegistry(new SimpleMeterRegistry());
}
}