Provide an Actuator endpoint for non-indexed session repositories

At present, Actuator sessions endpoint is supported only on a Servlet stack and also requires an indexed session repository. With Spring Session moving to non-indexed session repositories as a default for some session stores, this means that sessions endpoint won't be available unless users opt into a (non-default) indexed session repository.

This commit updates SessionEndpoint so that it is able to work with a non-indexed session repository. In such setup, it exposes operations for fetching session by id and deleting the session.

Additionally, this also adds support for reactive stack by introducing ReactiveSessionEndpoint and its auto-configuration support.

See gh-32046
This commit is contained in:
Vedran Pavic 2022-08-09 23:36:38 +02:00 committed by Moritz Halbritter
parent a09cc22841
commit 6a9eb7754f
10 changed files with 477 additions and 49 deletions

View File

@ -16,17 +16,24 @@
package org.springframework.boot.actuate.autoconfigure.session;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint;
import org.springframework.boot.actuate.session.ReactiveSessionsEndpoint;
import org.springframework.boot.actuate.session.SessionsEndpoint;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
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.ReactiveSessionRepository;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
/**
* {@link EnableAutoConfiguration Auto-configuration} for {@link SessionsEndpoint}.
@ -35,15 +42,35 @@ import org.springframework.session.Session;
* @since 2.0.0
*/
@AutoConfiguration(after = SessionAutoConfiguration.class)
@ConditionalOnClass(FindByIndexNameSessionRepository.class)
@ConditionalOnClass(Session.class)
@ConditionalOnAvailableEndpoint(endpoint = SessionsEndpoint.class)
public class SessionsEndpointAutoConfiguration {
@Bean
@ConditionalOnBean(FindByIndexNameSessionRepository.class)
@ConditionalOnMissingBean
public SessionsEndpoint sessionEndpoint(FindByIndexNameSessionRepository<? extends Session> sessionRepository) {
return new SessionsEndpoint(sessionRepository);
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnBean(SessionRepository.class)
static class ServletSessionEndpointConfiguration {
@Bean
@ConditionalOnMissingBean
SessionsEndpoint sessionEndpoint(SessionRepository<? extends Session> sessionRepository,
ObjectProvider<FindByIndexNameSessionRepository<? extends Session>> indexedSessionRepository) {
return new SessionsEndpoint(sessionRepository, indexedSessionRepository.getIfAvailable());
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.REACTIVE)
@ConditionalOnBean(ReactiveSessionRepository.class)
static class ReactiveSessionEndpointConfiguration {
@Bean
@ConditionalOnMissingBean
ReactiveSessionsEndpoint sessionsEndpoint(ReactiveSessionRepository<? extends Session> sessionRepository) {
return new ReactiveSessionsEndpoint(sessionRepository);
}
}
}

View File

@ -125,7 +125,7 @@ class SessionsEndpointDocumentationTests extends MockMvcEndpointDocumentationTes
@Bean
SessionsEndpoint endpoint(FindByIndexNameSessionRepository<?> sessionRepository) {
return new SessionsEndpoint(sessionRepository);
return new SessionsEndpoint(sessionRepository, sessionRepository);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 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,14 +16,19 @@
package org.springframework.boot.actuate.autoconfigure.session;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.session.ReactiveSessionsEndpoint;
import org.springframework.boot.actuate.session.SessionsEndpoint;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
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.ReactiveSessionRepository;
import org.springframework.session.SessionRepository;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
@ -35,33 +40,93 @@ import static org.mockito.Mockito.mock;
*/
class SessionsEndpointAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(SessionsEndpointAutoConfiguration.class))
.withUserConfiguration(SessionConfiguration.class);
@Nested
class ServletSessionEndpointConfigurationTests {
private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(SessionsEndpointAutoConfiguration.class))
.withUserConfiguration(IndexedSessionRepositoryConfiguration.class);
@Test
void runShouldHaveEndpointBean() {
this.contextRunner.withPropertyValues("management.endpoints.web.exposure.include=sessions")
.run((context) -> assertThat(context).hasSingleBean(SessionsEndpoint.class));
}
@Test
void runWhenNoIndexedSessionRepositoryShouldHaveEndpointBean() {
new WebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(SessionsEndpointAutoConfiguration.class))
.withUserConfiguration(SessionRepositoryConfiguration.class)
.withPropertyValues("management.endpoints.web.exposure.include=sessions")
.run((context) -> assertThat(context).hasSingleBean(SessionsEndpoint.class));
}
@Test
void runWhenNotExposedShouldNotHaveEndpointBean() {
this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(SessionsEndpoint.class));
}
@Test
void runWhenEnabledPropertyIsFalseShouldNotHaveEndpointBean() {
this.contextRunner.withPropertyValues("management.endpoint.sessions.enabled:false")
.run((context) -> assertThat(context).doesNotHaveBean(SessionsEndpoint.class));
}
@Configuration(proxyBeanMethods = false)
static class IndexedSessionRepositoryConfiguration {
@Bean
FindByIndexNameSessionRepository<?> sessionRepository() {
return mock(FindByIndexNameSessionRepository.class);
}
}
@Configuration(proxyBeanMethods = false)
static class SessionRepositoryConfiguration {
@Bean
SessionRepository<?> sessionRepository() {
return mock(SessionRepository.class);
}
}
@Test
void runShouldHaveEndpointBean() {
this.contextRunner.withPropertyValues("management.endpoints.web.exposure.include=sessions")
.run((context) -> assertThat(context).hasSingleBean(SessionsEndpoint.class));
}
@Test
void runWhenNotExposedShouldNotHaveEndpointBean() {
this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(SessionsEndpoint.class));
}
@Nested
class ReactiveSessionEndpointConfigurationTests {
@Test
void runWhenEnabledPropertyIsFalseShouldNotHaveEndpointBean() {
this.contextRunner.withPropertyValues("management.endpoint.sessions.enabled:false")
.run((context) -> assertThat(context).doesNotHaveBean(SessionsEndpoint.class));
}
private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(SessionsEndpointAutoConfiguration.class))
.withUserConfiguration(ReactiveSessionRepositoryConfiguration.class);
@Configuration(proxyBeanMethods = false)
static class SessionConfiguration {
@Test
void runShouldHaveEndpointBean() {
this.contextRunner.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));
}
@Test
void runWhenEnabledPropertyIsFalseShouldNotHaveEndpointBean() {
this.contextRunner.withPropertyValues("management.endpoint.sessions.enabled:false")
.run((context) -> assertThat(context).doesNotHaveBean(ReactiveSessionsEndpoint.class));
}
@Configuration(proxyBeanMethods = false)
static class ReactiveSessionRepositoryConfiguration {
@Bean
ReactiveSessionRepository<?> sessionRepository() {
return mock(ReactiveSessionRepository.class);
}
@Bean
FindByIndexNameSessionRepository<?> sessionRepository() {
return mock(FindByIndexNameSessionRepository.class);
}
}

View File

@ -0,0 +1,60 @@
/*
* 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 reactor.core.publisher.Mono;
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.session.ReactiveSessionRepository;
import org.springframework.session.Session;
import org.springframework.util.Assert;
/**
* {@link Endpoint @Endpoint} to expose information about HTTP {@link Session}s on a
* reactive stack.
*
* @author Vedran Pavic
* @since 3.0.0
*/
@Endpoint(id = "sessions")
public class ReactiveSessionsEndpoint {
private final ReactiveSessionRepository<? extends Session> sessionRepository;
/**
* Create a new {@link ReactiveSessionsEndpoint} instance.
* @param sessionRepository the session repository
*/
public ReactiveSessionsEndpoint(ReactiveSessionRepository<? extends Session> sessionRepository) {
Assert.notNull(sessionRepository, "ReactiveSessionRepository must not be null");
this.sessionRepository = sessionRepository;
}
@ReadOperation
public Mono<SessionDescriptor> getSession(@Selector String sessionId) {
return this.sessionRepository.findById(sessionId).map(SessionDescriptor::new);
}
@DeleteOperation
public Mono<Void> deleteSession(@Selector String sessionId) {
return this.sessionRepository.deleteById(sessionId);
}
}

View File

@ -0,0 +1,78 @@
/*
* 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<String> 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<String> 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;
}
}

View File

@ -28,9 +28,12 @@ import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.Selector;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
import org.springframework.util.Assert;
/**
* {@link Endpoint @Endpoint} to expose a user's {@link Session}s.
* {@link Endpoint @Endpoint} to expose information about HTTP {@link Session}s on a
* Servlet stack.
*
* @author Vedran Pavic
* @since 2.0.0
@ -38,19 +41,28 @@ import org.springframework.session.Session;
@Endpoint(id = "sessions")
public class SessionsEndpoint {
private final FindByIndexNameSessionRepository<? extends Session> sessionRepository;
private final SessionRepository<? extends Session> sessionRepository;
private final FindByIndexNameSessionRepository<? extends Session> indexedSessionRepository;
/**
* Create a new {@link SessionsEndpoint} instance.
* @param sessionRepository the session repository
* @param indexedSessionRepository the indexed session repository
*/
public SessionsEndpoint(FindByIndexNameSessionRepository<? extends Session> sessionRepository) {
public SessionsEndpoint(SessionRepository<? extends Session> sessionRepository,
FindByIndexNameSessionRepository<? extends Session> indexedSessionRepository) {
Assert.notNull(sessionRepository, "SessionRepository must not be null");
this.sessionRepository = sessionRepository;
this.indexedSessionRepository = indexedSessionRepository;
}
@ReadOperation
public SessionsDescriptor sessionsForUsername(String username) {
Map<String, ? extends Session> sessions = this.sessionRepository.findByPrincipalName(username);
if (this.indexedSessionRepository == null) {
return null;
}
Map<String, ? extends Session> sessions = this.indexedSessionRepository.findByPrincipalName(username);
return new SessionsDescriptor(sessions);
}

View File

@ -0,0 +1,74 @@
/*
* 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 org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.session.MapSession;
import org.springframework.session.ReactiveSessionRepository;
import org.springframework.session.Session;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link ReactiveSessionsEndpoint}.
*
* @author Vedran Pavic
*/
class ReactiveSessionsEndpointTests {
private static final Session session = new MapSession();
@SuppressWarnings("unchecked")
private final ReactiveSessionRepository<Session> sessionRepository = mock(ReactiveSessionRepository.class);
private final ReactiveSessionsEndpoint endpoint = new ReactiveSessionsEndpoint(this.sessionRepository);
@Test
void getSession() {
given(this.sessionRepository.findById(session.getId())).willReturn(Mono.just(session));
StepVerifier.create(this.endpoint.getSession(session.getId())).consumeNextWith((result) -> {
assertThat(result.getId()).isEqualTo(session.getId());
assertThat(result.getAttributeNames()).isEqualTo(session.getAttributeNames());
assertThat(result.getCreationTime()).isEqualTo(session.getCreationTime());
assertThat(result.getLastAccessedTime()).isEqualTo(session.getLastAccessedTime());
assertThat(result.getMaxInactiveInterval()).isEqualTo(session.getMaxInactiveInterval().getSeconds());
assertThat(result.isExpired()).isEqualTo(session.isExpired());
}).verifyComplete();
then(this.sessionRepository).should().findById(session.getId());
}
@Test
void getSessionWithIdNotFound() {
given(this.sessionRepository.findById("not-found")).willReturn(Mono.empty());
StepVerifier.create(this.endpoint.getSession("not-found")).verifyComplete();
then(this.sessionRepository).should().findById("not-found");
}
@Test
void deleteSession() {
given(this.sessionRepository.deleteById(session.getId())).willReturn(Mono.empty());
StepVerifier.create(this.endpoint.deleteSession(session.getId())).verifyComplete();
then(this.sessionRepository).should().deleteById(session.getId());
}
}

View File

@ -0,0 +1,88 @@
/*
* 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 reactor.core.publisher.Mono;
import org.springframework.boot.actuate.endpoint.web.test.WebEndpointTest;
import org.springframework.boot.actuate.endpoint.web.test.WebEndpointTest.Infrastructure;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.MapSession;
import org.springframework.session.ReactiveSessionRepository;
import org.springframework.session.Session;
import org.springframework.test.web.reactive.server.WebTestClient;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Integration tests for {@link ReactiveSessionsEndpoint} exposed by WebFlux.
*
* @author Vedran Pavic
*/
class ReactiveSessionsEndpointWebIntegrationTests {
private static final Session session = new MapSession();
@SuppressWarnings("unchecked")
private static final ReactiveSessionRepository<Session> sessionRepository = mock(ReactiveSessionRepository.class);
@WebEndpointTest(infrastructure = Infrastructure.WEBFLUX)
void sessionForIdFound(WebTestClient client) {
given(sessionRepository.findById(session.getId())).willReturn(Mono.just(session));
client.get()
.uri((builder) -> builder.path("/actuator/sessions/{id}").build(session.getId()))
.exchange()
.expectStatus()
.isOk()
.expectBody()
.jsonPath("id")
.isEqualTo(session.getId());
}
@WebEndpointTest(infrastructure = Infrastructure.WEBFLUX)
void sessionForIdNotFound(WebTestClient client) {
given(sessionRepository.findById("not-found")).willReturn(Mono.empty());
client.get()
.uri((builder) -> builder.path("/actuator/sessions/not-found").build())
.exchange()
.expectStatus()
.isNotFound();
}
@WebEndpointTest(infrastructure = Infrastructure.WEBFLUX)
void deleteSession(WebTestClient client) {
given(sessionRepository.deleteById(session.getId())).willReturn(Mono.empty());
client.delete()
.uri((builder) -> builder.path("/actuator/sessions/{id}").build(session.getId()))
.exchange()
.expectStatus()
.isNoContent();
}
@Configuration(proxyBeanMethods = false)
static class TestConfiguration {
@Bean
ReactiveSessionsEndpoint sessionsEndpoint() {
return new ReactiveSessionsEndpoint(sessionRepository);
}
}
}

View File

@ -21,10 +21,10 @@ import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.session.SessionsEndpoint.SessionDescriptor;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.MapSession;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
@ -41,15 +41,20 @@ class SessionsEndpointTests {
private static final Session session = new MapSession();
@SuppressWarnings("unchecked")
private final FindByIndexNameSessionRepository<Session> repository = mock(FindByIndexNameSessionRepository.class);
private final SessionRepository<Session> sessionRepository = mock(SessionRepository.class);
private final SessionsEndpoint endpoint = new SessionsEndpoint(this.repository);
@SuppressWarnings("unchecked")
private final FindByIndexNameSessionRepository<Session> indexedSessionRepository = mock(
FindByIndexNameSessionRepository.class);
private final SessionsEndpoint endpoint = new SessionsEndpoint(this.sessionRepository,
this.indexedSessionRepository);
@Test
void sessionsForUsername() {
given(this.repository.findByPrincipalName("user"))
given(this.indexedSessionRepository.findByPrincipalName("user"))
.willReturn(Collections.singletonMap(session.getId(), session));
List<SessionDescriptor> result = this.endpoint.sessionsForUsername("user").getSessions();
List<SessionsEndpoint.SessionDescriptor> result = this.endpoint.sessionsForUsername("user").getSessions();
assertThat(result).hasSize(1);
assertThat(result.get(0).getId()).isEqualTo(session.getId());
assertThat(result.get(0).getAttributeNames()).isEqualTo(session.getAttributeNames());
@ -57,30 +62,39 @@ class SessionsEndpointTests {
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());
then(this.indexedSessionRepository).should().findByPrincipalName("user");
}
@Test
void sessionsForUsernameWhenNoIndexedRepository() {
SessionsEndpoint endpoint = new SessionsEndpoint(this.sessionRepository, null);
assertThat(endpoint.sessionsForUsername("user")).isNull();
}
@Test
void getSession() {
given(this.repository.findById(session.getId())).willReturn(session);
SessionDescriptor result = this.endpoint.getSession(session.getId());
given(this.sessionRepository.findById(session.getId())).willReturn(session);
SessionsEndpoint.SessionDescriptor result = this.endpoint.getSession(session.getId());
assertThat(result.getId()).isEqualTo(session.getId());
assertThat(result.getAttributeNames()).isEqualTo(session.getAttributeNames());
assertThat(result.getCreationTime()).isEqualTo(session.getCreationTime());
assertThat(result.getLastAccessedTime()).isEqualTo(session.getLastAccessedTime());
assertThat(result.getMaxInactiveInterval()).isEqualTo(session.getMaxInactiveInterval().getSeconds());
assertThat(result.isExpired()).isEqualTo(session.isExpired());
then(this.sessionRepository).should().findById(session.getId());
}
@Test
void getSessionWithIdNotFound() {
given(this.repository.findById("not-found")).willReturn(null);
given(this.sessionRepository.findById("not-found")).willReturn(null);
assertThat(this.endpoint.getSession("not-found")).isNull();
then(this.sessionRepository).should().findById("not-found");
}
@Test
void deleteSession() {
this.endpoint.deleteSession(session.getId());
then(this.repository).should().deleteById(session.getId());
then(this.sessionRepository).should().deleteById(session.getId());
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 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.
@ -21,6 +21,7 @@ import java.util.Collections;
import net.minidev.json.JSONArray;
import org.springframework.boot.actuate.endpoint.web.test.WebEndpointTest;
import org.springframework.boot.actuate.endpoint.web.test.WebEndpointTest.Infrastructure;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.FindByIndexNameSessionRepository;
@ -45,7 +46,7 @@ class SessionsEndpointWebIntegrationTests {
private static final FindByIndexNameSessionRepository<Session> repository = mock(
FindByIndexNameSessionRepository.class);
@WebEndpointTest
@WebEndpointTest(infrastructure = { Infrastructure.JERSEY, Infrastructure.MVC })
void sessionsForUsernameWithoutUsernameParam(WebTestClient client) {
client.get()
.uri((builder) -> builder.path("/actuator/sessions").build())
@ -54,7 +55,7 @@ class SessionsEndpointWebIntegrationTests {
.isBadRequest();
}
@WebEndpointTest
@WebEndpointTest(infrastructure = { Infrastructure.JERSEY, Infrastructure.MVC })
void sessionsForUsernameNoResults(WebTestClient client) {
given(repository.findByPrincipalName("user")).willReturn(Collections.emptyMap());
client.get()
@ -67,7 +68,7 @@ class SessionsEndpointWebIntegrationTests {
.isEmpty();
}
@WebEndpointTest
@WebEndpointTest(infrastructure = { Infrastructure.JERSEY, Infrastructure.MVC })
void sessionsForUsernameFound(WebTestClient client) {
given(repository.findByPrincipalName("user")).willReturn(Collections.singletonMap(session.getId(), session));
client.get()
@ -80,7 +81,7 @@ class SessionsEndpointWebIntegrationTests {
.isEqualTo(new JSONArray().appendElement(session.getId()));
}
@WebEndpointTest
@WebEndpointTest(infrastructure = { Infrastructure.JERSEY, Infrastructure.MVC })
void sessionForIdNotFound(WebTestClient client) {
client.get()
.uri((builder) -> builder.path("/actuator/sessions/session-id-not-found").build())
@ -89,12 +90,21 @@ class SessionsEndpointWebIntegrationTests {
.isNotFound();
}
@WebEndpointTest(infrastructure = { Infrastructure.JERSEY, Infrastructure.MVC })
void deleteSession(WebTestClient client) {
client.delete()
.uri((builder) -> builder.path("/actuator/sessions/{id}").build(session.getId()))
.exchange()
.expectStatus()
.isNoContent();
}
@Configuration(proxyBeanMethods = false)
static class TestConfiguration {
@Bean
SessionsEndpoint sessionsEndpoint() {
return new SessionsEndpoint(repository);
return new SessionsEndpoint(repository, repository);
}
}