From 6e3d4ed8786d9c9060b87031261b8e1e3a61e16a Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Thu, 18 Jan 2024 11:12:26 +0100 Subject: [PATCH] Use ReactiveFindByIndexNameSessionRepository See gh-32046 --- .../SessionsEndpointAutoConfiguration.java | 8 +- ...essionsEndpointAutoConfigurationTests.java | 24 ++++- .../session/ReactiveSessionsEndpoint.java | 22 ++++- .../actuate/session/SessionDescriptor.java | 78 --------------- .../actuate/session/SessionsDescriptor.java | 98 +++++++++++++++++++ .../actuate/session/SessionsEndpoint.java | 32 +++--- .../ReactiveSessionsEndpointTests.java | 36 ++++++- ...veSessionsEndpointWebIntegrationTests.java | 49 +++++++++- .../session/SessionsEndpointTests.java | 1 + 9 files changed, 241 insertions(+), 107 deletions(-) delete mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/session/SessionDescriptor.java create mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/session/SessionsDescriptor.java diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/session/SessionsEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/session/SessionsEndpointAutoConfiguration.java index 10f5e92f05d..30c6a155c1d 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/session/SessionsEndpointAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/session/SessionsEndpointAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,6 +31,7 @@ import org.springframework.boot.autoconfigure.session.SessionAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.session.FindByIndexNameSessionRepository; +import org.springframework.session.ReactiveFindByIndexNameSessionRepository; import org.springframework.session.ReactiveSessionRepository; import org.springframework.session.Session; import org.springframework.session.SessionRepository; @@ -67,8 +68,9 @@ public class SessionsEndpointAutoConfiguration { @Bean @ConditionalOnMissingBean - ReactiveSessionsEndpoint sessionsEndpoint(ReactiveSessionRepository sessionRepository) { - return new ReactiveSessionsEndpoint(sessionRepository); + ReactiveSessionsEndpoint sessionsEndpoint(ReactiveSessionRepository sessionRepository, + ObjectProvider> indexedSessionRepository) { + return new ReactiveSessionsEndpoint(sessionRepository, indexedSessionRepository.getIfAvailable()); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/session/SessionsEndpointAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/session/SessionsEndpointAutoConfigurationTests.java index 6477fdeb0b1..491e4e3d618 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/session/SessionsEndpointAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/session/SessionsEndpointAutoConfigurationTests.java @@ -27,6 +27,7 @@ import org.springframework.boot.test.context.runner.WebApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.session.FindByIndexNameSessionRepository; +import org.springframework.session.ReactiveFindByIndexNameSessionRepository; import org.springframework.session.ReactiveSessionRepository; import org.springframework.session.SessionRepository; @@ -37,6 +38,7 @@ import static org.mockito.Mockito.mock; * Tests for {@link SessionsEndpointAutoConfiguration}. * * @author Vedran Pavic + * @author Moritz Halbritter */ class SessionsEndpointAutoConfigurationTests { @@ -100,7 +102,8 @@ class SessionsEndpointAutoConfigurationTests { private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner() .withConfiguration(AutoConfigurations.of(SessionsEndpointAutoConfiguration.class)) - .withUserConfiguration(ReactiveSessionRepositoryConfiguration.class); + .withUserConfiguration(ReactiveSessionRepositoryConfiguration.class, + ReactiveIndexedSessionRepositoryConfiguration.class); @Test void runShouldHaveEndpointBean() { @@ -108,6 +111,15 @@ class SessionsEndpointAutoConfigurationTests { .run((context) -> assertThat(context).hasSingleBean(ReactiveSessionsEndpoint.class)); } + @Test + void runWhenNoIndexedSessionRepositoryShouldHaveEndpointBean() { + new ReactiveWebApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(SessionsEndpointAutoConfiguration.class)) + .withUserConfiguration(ReactiveSessionRepositoryConfiguration.class) + .withPropertyValues("management.endpoints.web.exposure.include=sessions") + .run((context) -> assertThat(context).hasSingleBean(ReactiveSessionsEndpoint.class)); + } + @Test void runWhenNotExposedShouldNotHaveEndpointBean() { this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(ReactiveSessionsEndpoint.class)); @@ -119,6 +131,16 @@ class SessionsEndpointAutoConfigurationTests { .run((context) -> assertThat(context).doesNotHaveBean(ReactiveSessionsEndpoint.class)); } + @Configuration(proxyBeanMethods = false) + static class ReactiveIndexedSessionRepositoryConfiguration { + + @Bean + ReactiveFindByIndexNameSessionRepository indexedSessionRepository() { + return mock(ReactiveFindByIndexNameSessionRepository.class); + } + + } + @Configuration(proxyBeanMethods = false) static class ReactiveSessionRepositoryConfiguration { diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/session/ReactiveSessionsEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/session/ReactiveSessionsEndpoint.java index 298764d5b32..9ee15d69fc0 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/session/ReactiveSessionsEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/session/ReactiveSessionsEndpoint.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,8 @@ import org.springframework.boot.actuate.endpoint.annotation.DeleteOperation; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.annotation.Selector; +import org.springframework.boot.actuate.session.SessionsDescriptor.SessionDescriptor; +import org.springframework.session.ReactiveFindByIndexNameSessionRepository; import org.springframework.session.ReactiveSessionRepository; import org.springframework.session.Session; import org.springframework.util.Assert; @@ -31,20 +33,34 @@ import org.springframework.util.Assert; * reactive stack. * * @author Vedran Pavic - * @since 3.0.0 + * @author Moritz Halbritter + * @since 3.3.0 */ @Endpoint(id = "sessions") public class ReactiveSessionsEndpoint { private final ReactiveSessionRepository sessionRepository; + private final ReactiveFindByIndexNameSessionRepository indexedSessionRepository; + /** * Create a new {@link ReactiveSessionsEndpoint} instance. * @param sessionRepository the session repository + * @param indexedSessionRepository the indexed session repository */ - public ReactiveSessionsEndpoint(ReactiveSessionRepository sessionRepository) { + public ReactiveSessionsEndpoint(ReactiveSessionRepository sessionRepository, + ReactiveFindByIndexNameSessionRepository indexedSessionRepository) { Assert.notNull(sessionRepository, "ReactiveSessionRepository must not be null"); this.sessionRepository = sessionRepository; + this.indexedSessionRepository = indexedSessionRepository; + } + + @ReadOperation + public Mono sessionsForUsername(String username) { + if (this.indexedSessionRepository == null) { + return Mono.empty(); + } + return this.indexedSessionRepository.findByPrincipalName(username).map(SessionsDescriptor::new); } @ReadOperation diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/session/SessionDescriptor.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/session/SessionDescriptor.java deleted file mode 100644 index 71e5cc8aebf..00000000000 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/session/SessionDescriptor.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2012-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.actuate.session; - -import java.time.Instant; -import java.util.Set; - -import org.springframework.session.Session; - -/** - * A description of user's {@link Session session} exposed by {@code sessions} endpoint. - * Primarily intended for serialization to JSON. - * - * @author Vedran Pavic - * @since 3.0.0 - */ -public final class SessionDescriptor { - - private final String id; - - private final Set attributeNames; - - private final Instant creationTime; - - private final Instant lastAccessedTime; - - private final long maxInactiveInterval; - - private final boolean expired; - - SessionDescriptor(Session session) { - this.id = session.getId(); - this.attributeNames = session.getAttributeNames(); - this.creationTime = session.getCreationTime(); - this.lastAccessedTime = session.getLastAccessedTime(); - this.maxInactiveInterval = session.getMaxInactiveInterval().getSeconds(); - this.expired = session.isExpired(); - } - - public String getId() { - return this.id; - } - - public Set getAttributeNames() { - return this.attributeNames; - } - - public Instant getCreationTime() { - return this.creationTime; - } - - public Instant getLastAccessedTime() { - return this.lastAccessedTime; - } - - public long getMaxInactiveInterval() { - return this.maxInactiveInterval; - } - - public boolean isExpired() { - return this.expired; - } - -} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/session/SessionsDescriptor.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/session/SessionsDescriptor.java new file mode 100644 index 00000000000..24e12de097b --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/session/SessionsDescriptor.java @@ -0,0 +1,98 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.session; + +import java.time.Instant; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.springframework.boot.actuate.endpoint.OperationResponseBody; +import org.springframework.session.Session; + +/** + * Description of user's {@link Session sessions}. + * + * @author Moritz Halbritter + * @since 3.3.0 + */ +public final class SessionsDescriptor implements OperationResponseBody { + + private final List sessions; + + public SessionsDescriptor(Map sessions) { + this.sessions = sessions.values().stream().map(SessionDescriptor::new).toList(); + } + + public List getSessions() { + return this.sessions; + } + + /** + * A description of user's {@link Session session} exposed by {@code sessions} + * endpoint. Primarily intended for serialization to JSON. + */ + public static final class SessionDescriptor { + + private final String id; + + private final Set attributeNames; + + private final Instant creationTime; + + private final Instant lastAccessedTime; + + private final long maxInactiveInterval; + + private final boolean expired; + + SessionDescriptor(Session session) { + this.id = session.getId(); + this.attributeNames = session.getAttributeNames(); + this.creationTime = session.getCreationTime(); + this.lastAccessedTime = session.getLastAccessedTime(); + this.maxInactiveInterval = session.getMaxInactiveInterval().getSeconds(); + this.expired = session.isExpired(); + } + + public String getId() { + return this.id; + } + + public Set getAttributeNames() { + return this.attributeNames; + } + + public Instant getCreationTime() { + return this.creationTime; + } + + public Instant getLastAccessedTime() { + return this.lastAccessedTime; + } + + public long getMaxInactiveInterval() { + return this.maxInactiveInterval; + } + + public boolean isExpired() { + return this.expired; + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/session/SessionsEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/session/SessionsEndpoint.java index a08e7964799..1b89c83a113 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/session/SessionsEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/session/SessionsEndpoint.java @@ -16,14 +16,13 @@ package org.springframework.boot.actuate.session; -import java.util.List; import java.util.Map; -import org.springframework.boot.actuate.endpoint.OperationResponseBody; import org.springframework.boot.actuate.endpoint.annotation.DeleteOperation; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.annotation.Selector; +import org.springframework.boot.actuate.session.SessionsDescriptor.SessionDescriptor; import org.springframework.session.FindByIndexNameSessionRepository; import org.springframework.session.Session; import org.springframework.session.SessionRepository; @@ -43,10 +42,22 @@ public class SessionsEndpoint { private final FindByIndexNameSessionRepository indexedSessionRepository; + /** + * Create a new {@link SessionsEndpoint} instance. + * @param sessionRepository the session repository + * @deprecated since 3.3.0 for removal in 3.5.0 in favor of + * {@link #SessionsEndpoint(SessionRepository, FindByIndexNameSessionRepository)} + */ + @Deprecated(since = "3.3.0", forRemoval = true) + public SessionsEndpoint(FindByIndexNameSessionRepository sessionRepository) { + this(sessionRepository, sessionRepository); + } + /** * Create a new {@link SessionsEndpoint} instance. * @param sessionRepository the session repository * @param indexedSessionRepository the indexed session repository + * @since 3.3.0 */ public SessionsEndpoint(SessionRepository sessionRepository, FindByIndexNameSessionRepository indexedSessionRepository) { @@ -78,21 +89,4 @@ public class SessionsEndpoint { this.sessionRepository.deleteById(sessionId); } - /** - * Description of user's {@link Session sessions}. - */ - public static final class SessionsDescriptor implements OperationResponseBody { - - private final List sessions; - - public SessionsDescriptor(Map sessions) { - this.sessions = sessions.values().stream().map(SessionDescriptor::new).toList(); - } - - public List getSessions() { - return this.sessions; - } - - } - } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/session/ReactiveSessionsEndpointTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/session/ReactiveSessionsEndpointTests.java index 48ca95536fa..d338e69f2c6 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/session/ReactiveSessionsEndpointTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/session/ReactiveSessionsEndpointTests.java @@ -17,12 +17,16 @@ package org.springframework.boot.actuate.session; import java.time.Duration; +import java.util.Collections; +import java.util.List; import org.junit.jupiter.api.Test; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +import org.springframework.boot.actuate.session.SessionsDescriptor.SessionDescriptor; import org.springframework.session.MapSession; +import org.springframework.session.ReactiveFindByIndexNameSessionRepository; import org.springframework.session.ReactiveSessionRepository; import org.springframework.session.Session; @@ -35,6 +39,7 @@ import static org.mockito.Mockito.mock; * Tests for {@link ReactiveSessionsEndpoint}. * * @author Vedran Pavic + * @author Moritz Halbritter */ class ReactiveSessionsEndpointTests { @@ -43,7 +48,36 @@ class ReactiveSessionsEndpointTests { @SuppressWarnings("unchecked") private final ReactiveSessionRepository sessionRepository = mock(ReactiveSessionRepository.class); - private final ReactiveSessionsEndpoint endpoint = new ReactiveSessionsEndpoint(this.sessionRepository); + @SuppressWarnings("unchecked") + private final ReactiveFindByIndexNameSessionRepository indexedSessionRepository = mock( + ReactiveFindByIndexNameSessionRepository.class); + + private final ReactiveSessionsEndpoint endpoint = new ReactiveSessionsEndpoint(this.sessionRepository, + this.indexedSessionRepository); + + @Test + void sessionsForUsername() { + given(this.indexedSessionRepository.findByPrincipalName("user")) + .willReturn(Mono.just(Collections.singletonMap(session.getId(), session))); + StepVerifier.create(this.endpoint.sessionsForUsername("user")).consumeNextWith((sessions) -> { + List result = sessions.getSessions(); + assertThat(result).hasSize(1); + assertThat(result.get(0).getId()).isEqualTo(session.getId()); + assertThat(result.get(0).getAttributeNames()).isEqualTo(session.getAttributeNames()); + assertThat(result.get(0).getCreationTime()).isEqualTo(session.getCreationTime()); + assertThat(result.get(0).getLastAccessedTime()).isEqualTo(session.getLastAccessedTime()); + assertThat(result.get(0).getMaxInactiveInterval()).isEqualTo(session.getMaxInactiveInterval().getSeconds()); + assertThat(result.get(0).isExpired()).isEqualTo(session.isExpired()); + + }).expectComplete().verify(Duration.ofSeconds(1)); + then(this.indexedSessionRepository).should().findByPrincipalName("user"); + } + + @Test + void sessionsForUsernameWhenNoIndexedRepository() { + ReactiveSessionsEndpoint endpoint = new ReactiveSessionsEndpoint(this.sessionRepository, null); + StepVerifier.create(endpoint.sessionsForUsername("user")).expectComplete().verify(Duration.ofSeconds(1)); + } @Test void getSession() { diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/session/ReactiveSessionsEndpointWebIntegrationTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/session/ReactiveSessionsEndpointWebIntegrationTests.java index 7f401484d72..c9b13949916 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/session/ReactiveSessionsEndpointWebIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/session/ReactiveSessionsEndpointWebIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,9 @@ package org.springframework.boot.actuate.session; +import java.util.Collections; + +import net.minidev.json.JSONArray; import reactor.core.publisher.Mono; import org.springframework.boot.actuate.endpoint.web.test.WebEndpointTest; @@ -23,6 +26,7 @@ import org.springframework.boot.actuate.endpoint.web.test.WebEndpointTest.Infras import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.session.MapSession; +import org.springframework.session.ReactiveFindByIndexNameSessionRepository; import org.springframework.session.ReactiveSessionRepository; import org.springframework.session.Session; import org.springframework.test.web.reactive.server.WebTestClient; @@ -34,6 +38,7 @@ import static org.mockito.Mockito.mock; * Integration tests for {@link ReactiveSessionsEndpoint} exposed by WebFlux. * * @author Vedran Pavic + * @author Moritz Halbritter */ class ReactiveSessionsEndpointWebIntegrationTests { @@ -42,6 +47,46 @@ class ReactiveSessionsEndpointWebIntegrationTests { @SuppressWarnings("unchecked") private static final ReactiveSessionRepository sessionRepository = mock(ReactiveSessionRepository.class); + @SuppressWarnings("unchecked") + private static final ReactiveFindByIndexNameSessionRepository indexedSessionRepository = mock( + ReactiveFindByIndexNameSessionRepository.class); + + @WebEndpointTest(infrastructure = Infrastructure.WEBFLUX) + void sessionsForUsernameWithoutUsernameParam(WebTestClient client) { + client.get() + .uri((builder) -> builder.path("/actuator/sessions").build()) + .exchange() + .expectStatus() + .is5xxServerError(); // https://github.com/spring-projects/spring-boot/issues/39236 + } + + @WebEndpointTest(infrastructure = Infrastructure.WEBFLUX) + void sessionsForUsernameNoResults(WebTestClient client) { + given(indexedSessionRepository.findByPrincipalName("user")).willReturn(Mono.just(Collections.emptyMap())); + client.get() + .uri((builder) -> builder.path("/actuator/sessions").queryParam("username", "user").build()) + .exchange() + .expectStatus() + .isOk() + .expectBody() + .jsonPath("sessions") + .isEmpty(); + } + + @WebEndpointTest(infrastructure = Infrastructure.WEBFLUX) + void sessionsForUsernameFound(WebTestClient client) { + given(indexedSessionRepository.findByPrincipalName("user")) + .willReturn(Mono.just(Collections.singletonMap(session.getId(), session))); + client.get() + .uri((builder) -> builder.path("/actuator/sessions").queryParam("username", "user").build()) + .exchange() + .expectStatus() + .isOk() + .expectBody() + .jsonPath("sessions.[*].id") + .isEqualTo(new JSONArray().appendElement(session.getId())); + } + @WebEndpointTest(infrastructure = Infrastructure.WEBFLUX) void sessionForIdFound(WebTestClient client) { given(sessionRepository.findById(session.getId())).willReturn(Mono.just(session)); @@ -80,7 +125,7 @@ class ReactiveSessionsEndpointWebIntegrationTests { @Bean ReactiveSessionsEndpoint sessionsEndpoint() { - return new ReactiveSessionsEndpoint(sessionRepository); + return new ReactiveSessionsEndpoint(sessionRepository, indexedSessionRepository); } } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/session/SessionsEndpointTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/session/SessionsEndpointTests.java index e37279c51f3..8047c825aba 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/session/SessionsEndpointTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/session/SessionsEndpointTests.java @@ -21,6 +21,7 @@ import java.util.List; import org.junit.jupiter.api.Test; +import org.springframework.boot.actuate.session.SessionsDescriptor.SessionDescriptor; import org.springframework.session.FindByIndexNameSessionRepository; import org.springframework.session.MapSession; import org.springframework.session.Session;