mirror of
https://github.com/spring-projects/spring-boot.git
synced 2024-07-15 01:07:30 +08:00
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:
parent
a09cc22841
commit
6a9eb7754f
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -125,7 +125,7 @@ class SessionsEndpointDocumentationTests extends MockMvcEndpointDocumentationTes
|
||||
|
||||
@Bean
|
||||
SessionsEndpoint endpoint(FindByIndexNameSessionRepository<?> sessionRepository) {
|
||||
return new SessionsEndpoint(sessionRepository);
|
||||
return new SessionsEndpoint(sessionRepository, sessionRepository);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user