Refactor test: remove micrometer mocking, micrometer is testable #32
7 changed files with 61 additions and 70 deletions
2
pom.xml
2
pom.xml
|
@ -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>
|
||||||
|
|
|
@ -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()),
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue