Add WebFlux Support
This commit is contained in:
@@ -7,14 +7,16 @@ dependencies {
|
||||
|
||||
optional "io.projectreactor:reactor-core"
|
||||
optional "javax.servlet:javax.servlet-api"
|
||||
optional "org.springframework.security:spring-security-core"
|
||||
optional "org.springframework.security:spring-security-web"
|
||||
optional "org.springframework:spring-context"
|
||||
optional "org.springframework:spring-jdbc"
|
||||
optional "org.springframework:spring-messaging"
|
||||
optional "org.springframework:spring-web"
|
||||
optional "org.springframework:spring-webflux"
|
||||
optional "org.springframework:spring-websocket"
|
||||
optional "org.springframework.security:spring-security-core"
|
||||
optional "org.springframework.security:spring-security-web"
|
||||
|
||||
testCompile "io.projectreactor:reactor-test"
|
||||
testCompile "junit:junit"
|
||||
testCompile "org.mockito:mockito-core"
|
||||
testCompile "edu.umd.cs.mtc:multithreadedtc"
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* Copyright 2014-2017 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
|
||||
*
|
||||
* http://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.session.web.server.session;
|
||||
|
||||
import org.springframework.session.ReactorSessionRepository;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebSession;
|
||||
import org.springframework.web.server.session.CookieWebSessionIdResolver;
|
||||
import org.springframework.web.server.session.WebSessionIdResolver;
|
||||
import org.springframework.web.server.session.WebSessionManager;
|
||||
import org.springframework.web.server.session.WebSessionStore;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
* @since 2.0
|
||||
*/
|
||||
public class SpringSessionWebSessionManager implements WebSessionManager {
|
||||
private final SpringSessionWebSessionStore<? extends Session> sessionStore;
|
||||
|
||||
private WebSessionIdResolver sessionIdResolver = new CookieWebSessionIdResolver();
|
||||
|
||||
private Clock clock = Clock.system(ZoneId.of("GMT"));
|
||||
|
||||
public SpringSessionWebSessionManager(ReactorSessionRepository<? extends Session> sessionRepository) {
|
||||
sessionStore
|
||||
= new SpringSessionWebSessionStore<>(sessionRepository);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the id resolution strategy.
|
||||
* <p>By default an instance of {@link CookieWebSessionIdResolver}.
|
||||
* @param sessionIdResolver the resolver to use
|
||||
*/
|
||||
public void setSessionIdResolver(WebSessionIdResolver sessionIdResolver) {
|
||||
Assert.notNull(sessionIdResolver, "WebSessionIdResolver is required.");
|
||||
this.sessionIdResolver = sessionIdResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the configured {@link WebSessionIdResolver}.
|
||||
*/
|
||||
private WebSessionIdResolver getSessionIdResolver() {
|
||||
return this.sessionIdResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the configured {@link WebSessionStore}.
|
||||
*/
|
||||
private WebSessionStore getSessionStore() {
|
||||
return this.sessionStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the {@link Clock} to use to set lastAccessTime on every created
|
||||
* session and to calculate if it is expired.
|
||||
* <p>This may be useful to align to different timezone or to set the clock
|
||||
* back in a test, e.g. {@code Clock.offset(clock, Duration.ofMinutes(-31))}
|
||||
* in order to simulate session expiration.
|
||||
* <p>By default this is {@code Clock.system(ZoneId.of("GMT"))}.
|
||||
* @param clock the clock to use
|
||||
*/
|
||||
public void setClock(Clock clock) {
|
||||
Assert.notNull(clock, "'clock' is required.");
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the configured clock for session lastAccessTime calculations.
|
||||
*/
|
||||
private Clock getClock() {
|
||||
return this.clock;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<WebSession> getSession(ServerWebExchange exchange) {
|
||||
return Mono.defer(() ->
|
||||
retrieveSession(exchange)
|
||||
.flatMap(session -> removeSessionIfExpired(exchange, session))
|
||||
.flatMap(session -> {
|
||||
Instant lastAccessTime = Instant.now(getClock());
|
||||
return this.sessionStore.setLastAccessedTime(session, lastAccessTime);
|
||||
})
|
||||
.switchIfEmpty(createSession(exchange))
|
||||
.doOnNext(session -> exchange.getResponse().beforeCommit(session::save)));
|
||||
}
|
||||
|
||||
private Mono<WebSession> retrieveSession(ServerWebExchange exchange) {
|
||||
return Flux.fromIterable(getSessionIdResolver().resolveSessionIds(exchange))
|
||||
.concatMap(this.sessionStore::retrieveSession)
|
||||
.cast(WebSession.class)
|
||||
.next();
|
||||
}
|
||||
|
||||
private Mono<WebSession> removeSessionIfExpired(ServerWebExchange exchange, WebSession session) {
|
||||
if (session.isExpired()) {
|
||||
this.sessionIdResolver.expireSession(exchange);
|
||||
return this.sessionStore.removeSession(session.getId()).then(Mono.empty());
|
||||
}
|
||||
return Mono.just(session);
|
||||
}
|
||||
|
||||
private Mono<Void> saveSession(ServerWebExchange exchange, WebSession session) {
|
||||
if (session.isExpired()) {
|
||||
return Mono.error(new IllegalStateException(
|
||||
"Sessions are checked for expiration and have their " +
|
||||
"lastAccessTime updated when first accessed during request processing. " +
|
||||
"However this session is expired meaning that maxIdleTime elapsed " +
|
||||
"before the call to session.save()."));
|
||||
}
|
||||
|
||||
if (!session.isStarted()) {
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
// Force explicit start
|
||||
session.start();
|
||||
|
||||
if (hasNewSessionId(exchange, session)) {
|
||||
this.sessionIdResolver.setSessionId(exchange, session.getId());
|
||||
}
|
||||
|
||||
return this.sessionStore.storeSession(session);
|
||||
}
|
||||
|
||||
private boolean hasNewSessionId(ServerWebExchange exchange, WebSession session) {
|
||||
List<String> ids = getSessionIdResolver().resolveSessionIds(exchange);
|
||||
return ids.isEmpty() || !session.getId().equals(ids.get(0));
|
||||
}
|
||||
|
||||
private Mono<WebSession> createSession(ServerWebExchange exchange) {
|
||||
return this.sessionStore.createSession();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
/*
|
||||
* Copyright 2014-2017 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
|
||||
*
|
||||
* http://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.session.web.server.session;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.session.ReactorSessionRepository;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.server.WebSession;
|
||||
import org.springframework.web.server.session.WebSessionStore;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
* @since 2.0
|
||||
*/
|
||||
class SpringSessionWebSessionStore<S extends Session> implements WebSessionStore {
|
||||
private final ReactorSessionRepository<S> sessions;
|
||||
|
||||
SpringSessionWebSessionStore(ReactorSessionRepository<S> sessions) {
|
||||
Assert.notNull(sessions, "sessions cannot be null");
|
||||
this.sessions = sessions;
|
||||
}
|
||||
|
||||
public Mono<WebSession> createSession() {
|
||||
return this.sessions.createSession().map(this::createSession);
|
||||
}
|
||||
|
||||
public Mono<WebSession> setLastAccessedTime(WebSession session,
|
||||
Instant lastAccessedTime) {
|
||||
SpringSessionWebSession springSessionWebSession = (SpringSessionWebSession) session;
|
||||
springSessionWebSession.session.setLastAccessedTime(lastAccessedTime);
|
||||
return Mono.just(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> storeSession(WebSession session) {
|
||||
@SuppressWarnings("unchecked")
|
||||
SpringSessionWebSession springWebSession = (SpringSessionWebSession) session;
|
||||
return this.sessions.save(springWebSession.session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<WebSession> retrieveSession(String sessionId) {
|
||||
return this.sessions.findById(sessionId).map(this::existingSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> changeSessionId(String s, WebSession webSession) {
|
||||
return storeSession(webSession);
|
||||
}
|
||||
|
||||
private SpringSessionWebSession createSession(S session) {
|
||||
return new SpringSessionWebSession(session, State.NEW);
|
||||
}
|
||||
|
||||
private SpringSessionWebSession existingSession(S session) {
|
||||
return new SpringSessionWebSession(session, State.STARTED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> removeSession(String sessionId) {
|
||||
return sessions.delete(sessionId);
|
||||
}
|
||||
|
||||
private class SpringSessionWebSession implements WebSession {
|
||||
private final S session;
|
||||
|
||||
private final Map<String, Object> attributes;
|
||||
|
||||
private AtomicReference<State> state = new AtomicReference<>();
|
||||
|
||||
private volatile transient Supplier<Mono<Void>> saveOperation = Mono::empty;
|
||||
|
||||
SpringSessionWebSession(S session, State state) {
|
||||
Assert.notNull(session, "session cannot be null");
|
||||
this.session = session;
|
||||
this.attributes = new SpringSessionMap(session);
|
||||
this.state.set(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return session.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> changeSessionId() {
|
||||
return Mono.defer(() -> {
|
||||
session.changeSessionId();
|
||||
return save();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getAttributes() {
|
||||
return this.attributes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
this.state.compareAndSet(State.NEW, State.STARTED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStarted() {
|
||||
State value = this.state.get();
|
||||
return (State.STARTED.equals(value)
|
||||
|| (State.NEW.equals(value) && !getAttributes().isEmpty()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> save() {
|
||||
return this.saveOperation.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExpired() {
|
||||
return this.session.isExpired();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instant getCreationTime() {
|
||||
return this.session.getCreationTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instant getLastAccessTime() {
|
||||
return this.session.getLastAccessedTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxIdleTime(Duration maxIdleTime) {
|
||||
this.session.setMaxInactiveInterval(maxIdleTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Duration getMaxIdleTime() {
|
||||
return this.session.getMaxInactiveInterval();
|
||||
}
|
||||
}
|
||||
|
||||
private enum State {
|
||||
NEW, STARTED
|
||||
}
|
||||
|
||||
static class SpringSessionMap implements Map<String, Object> {
|
||||
private final Session session;
|
||||
private final Collection<Object> values = new SessionValues();
|
||||
|
||||
SpringSessionMap(Session session) {
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return this.session.getAttributeNames().size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return this.session.getAttributeNames().isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(Object key) {
|
||||
return key instanceof String
|
||||
&& this.session.getAttributeNames().contains(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsValue(Object value) {
|
||||
return this.session.getAttributeNames().stream()
|
||||
.anyMatch(attrName -> this.session.getAttribute(attrName) != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Object get(Object key) {
|
||||
if (key instanceof String) {
|
||||
return this.session.getAttribute((String) key);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object put(String key, Object value) {
|
||||
Object original = this.session.getAttribute(key);
|
||||
this.session.setAttribute(key, value);
|
||||
return original;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Object remove(Object key) {
|
||||
if (key instanceof String) {
|
||||
String attrName = (String) key;
|
||||
Object original = this.session.getAttribute(attrName);
|
||||
this.session.removeAttribute(attrName);
|
||||
return original;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(Map<? extends String, ?> m) {
|
||||
for (Entry<? extends String, ?> entry : m.entrySet()) {
|
||||
put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
for (String attrName : this.session.getAttributeNames()) {
|
||||
remove(attrName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> keySet() {
|
||||
return this.session.getAttributeNames();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Object> values() {
|
||||
return values;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Entry<String, Object>> entrySet() {
|
||||
Set<String> attrNames = keySet();
|
||||
Set<Entry<String, Object>> entries = new HashSet<>(attrNames.size());
|
||||
for (String attrName : attrNames) {
|
||||
Object value = this.session.getAttribute(attrName);
|
||||
entries.add(new AbstractMap.SimpleEntry<>(attrName, value));
|
||||
}
|
||||
return Collections.unmodifiableSet(entries);
|
||||
}
|
||||
|
||||
private class SessionValues extends AbstractCollection<Object> {
|
||||
public Iterator<Object> iterator() {
|
||||
return new Iterator<Object>() {
|
||||
private Iterator<Entry<String, Object>> i = entrySet().iterator();
|
||||
|
||||
public boolean hasNext() {
|
||||
return i.hasNext();
|
||||
}
|
||||
|
||||
public Object next() {
|
||||
return i.next().getValue();
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
i.remove();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return SpringSessionMap.this.size();
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return SpringSessionMap.this.isEmpty();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
SpringSessionMap.this.clear();
|
||||
}
|
||||
|
||||
public boolean contains(Object v) {
|
||||
return SpringSessionMap.this.containsValue(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* @author Rob Winch
|
||||
* @since 5.0
|
||||
*/
|
||||
@NonNullApi
|
||||
package org.springframework.session.web.server.session;
|
||||
|
||||
import org.springframework.lang.NonNullApi;
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright 2014-2017 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
|
||||
*
|
||||
* http://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.session.web.server.session;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.springframework.http.HttpCookie;
|
||||
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
|
||||
import org.springframework.session.ReactorSessionRepository;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebSession;
|
||||
import org.springframework.web.server.session.WebSessionIdResolver;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
* @since 5.0
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class SpringSessionWebSessionManagerTests<S extends Session> {
|
||||
@Mock
|
||||
ReactorSessionRepository<S> sessions;
|
||||
|
||||
@Mock
|
||||
WebSessionIdResolver resolver;
|
||||
|
||||
@Mock
|
||||
S createSession;
|
||||
|
||||
@Mock
|
||||
S findByIdSession;
|
||||
|
||||
Mono<S> createSessionMono;
|
||||
|
||||
ServerWebExchange exchange = MockServerHttpRequest.get("/").toExchange();
|
||||
|
||||
|
||||
SpringSessionWebSessionManager manager;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
when(this.createSession.getId()).thenReturn("createSession-id");
|
||||
when(this.findByIdSession.getId()).thenReturn("findByIdSession-id");
|
||||
this.createSessionMono = Mono.just(this.createSession);
|
||||
when(this.sessions.createSession()).thenReturn(createSessionMono);
|
||||
this.manager = new SpringSessionWebSessionManager(this.sessions);
|
||||
this.manager.setSessionIdResolver(this.resolver);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSessionWhenDefaultSessionIdResolverFoundSessionUsed() {
|
||||
String findByIdSessionId = this.findByIdSession.getId();
|
||||
this.exchange = MockServerHttpRequest.get("/").cookie(new HttpCookie("SESSION", findByIdSessionId)).toExchange();
|
||||
this.manager = new SpringSessionWebSessionManager(this.sessions);
|
||||
when(this.sessions.findById(findByIdSessionId)).thenReturn(Mono.just(findByIdSession));
|
||||
|
||||
WebSession webSession = this.manager.getSession(exchange).block();
|
||||
|
||||
assertThat(webSession.getId()).isEqualTo(findByIdSessionId);
|
||||
verify(this.sessions).findById(findByIdSessionId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSessionWhenNewThenCreateSessionInvoked() {
|
||||
WebSession webSession = this.manager.getSession(exchange).block();
|
||||
|
||||
assertThat(webSession.getId()).isEqualTo(this.createSession.getId());
|
||||
verify(this.sessions).createSession();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSessionWhenNewAndPutThenSetAttributeInvoked() {
|
||||
String attrName = "attrName";
|
||||
String attrValue = "attrValue";
|
||||
|
||||
WebSession webSession = this.manager.getSession(exchange).block();
|
||||
webSession.getAttributes().put(attrName, attrValue);
|
||||
|
||||
verify(this.createSession).setAttribute(attrName, attrValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSessionWhenInvalidIdThenCreateSessionInvoked() {
|
||||
String invalidId = "invalid";
|
||||
String createSessionId = this.createSession.getId();
|
||||
when(this.sessions.findById(any())).thenReturn(Mono.empty());
|
||||
when(this.resolver.resolveSessionIds(exchange)).thenReturn(Arrays.asList(invalidId));
|
||||
|
||||
WebSession webSession = this.manager.getSession(exchange).block();
|
||||
|
||||
assertThat(webSession.getId()).isEqualTo(createSessionId);
|
||||
verify(this.sessions).findById(invalidId);
|
||||
|
||||
Mono<String> mono = Mono.just("toTest");
|
||||
StepVerifier
|
||||
.create(mono)
|
||||
.expectNoEvent(Duration.ZERO);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSessionWhenValidIdThenFoundSessionUsed() {
|
||||
String findByIdSessionId = this.findByIdSession.getId();
|
||||
when(this.sessions.findById(findByIdSessionId)).thenReturn(Mono.just(findByIdSession));
|
||||
when(this.resolver.resolveSessionIds(exchange)).thenReturn(Arrays.asList(findByIdSessionId));
|
||||
|
||||
WebSession webSession = this.manager.getSession(exchange).block();
|
||||
|
||||
assertThat(webSession.getId()).isEqualTo(findByIdSessionId);
|
||||
verify(this.sessions).findById(findByIdSessionId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
/*
|
||||
* Copyright 2014-2017 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
|
||||
*
|
||||
* http://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.session.web.server.session;
|
||||
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.springframework.session.ReactorSessionRepository;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.web.server.WebSession;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.AbstractMap;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
* @since 5.0
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class SpringSessionWebSessionStoreTests<S extends Session> {
|
||||
@Mock
|
||||
ReactorSessionRepository<S> sessionRepository;
|
||||
@Mock
|
||||
S createSession;
|
||||
@Mock
|
||||
S findByIdSession;
|
||||
SpringSessionWebSessionStore<S> webSessionStore;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.webSessionStore = new SpringSessionWebSessionStore<>(sessionRepository);
|
||||
when(this.sessionRepository.findById(any())).thenReturn(Mono.just(findByIdSession));
|
||||
when(this.sessionRepository.createSession()).thenReturn(Mono.just(createSession));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructorWhenNullRepositoryThenThrowsIllegalArgumentException() {
|
||||
new SpringSessionWebSessionStore<S>((ReactorSessionRepository<S>) null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSessionWhenNoAttributesThenNotStarted() {
|
||||
WebSession createdWebSession = this.webSessionStore.createSession().block();
|
||||
|
||||
assertThat(createdWebSession.isStarted()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSessionWhenAddAttributeThenStarted() {
|
||||
when(createSession.getAttributeNames()).thenReturn(Collections.singleton("a"));
|
||||
WebSession createdWebSession = this.webSessionStore.createSession().block();
|
||||
|
||||
assertThat(createdWebSession.isStarted()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSessionWhenGetAttributesAndSizeThenDelegatesToCreateSession() {
|
||||
WebSession createdWebSession = this.webSessionStore.createSession().block();
|
||||
|
||||
Map<String, Object> attributes = createdWebSession.getAttributes();
|
||||
|
||||
assertThat(attributes.size()).isEqualTo(0);
|
||||
|
||||
when(this.createSession.getAttributeNames()).thenReturn(Collections.singleton("a"));
|
||||
|
||||
assertThat(attributes.size()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSessionWhenGetAttributesAndIsEmptyThenDelegatesToCreateSession() {
|
||||
WebSession createdWebSession = this.webSessionStore.createSession().block();
|
||||
|
||||
Map<String, Object> attributes = createdWebSession.getAttributes();
|
||||
|
||||
assertThat(attributes.isEmpty()).isTrue();
|
||||
|
||||
when(this.createSession.getAttributeNames()).thenReturn(Collections.singleton("a"));
|
||||
|
||||
assertThat(attributes.isEmpty()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSessionWhenGetAttributesAndContainsKeyAndNotStringThenFalse() {
|
||||
WebSession createdWebSession = this.webSessionStore.createSession().block();
|
||||
|
||||
Map<String, Object> attributes = createdWebSession.getAttributes();
|
||||
|
||||
assertThat(attributes.containsKey(1L)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSessionWhenGetAttributesAndContainsKeyAndNotFoundThenFalse() {
|
||||
WebSession createdWebSession = this.webSessionStore.createSession().block();
|
||||
|
||||
Map<String, Object> attributes = createdWebSession.getAttributes();
|
||||
|
||||
assertThat(attributes.containsKey("a")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSessionWhenGetAttributesAndContainsKeyAndFoundThenTrue() {
|
||||
when(this.createSession.getAttributeNames()).thenReturn(Collections.singleton("a"));
|
||||
WebSession createdWebSession = this.webSessionStore.createSession().block();
|
||||
|
||||
Map<String, Object> attributes = createdWebSession.getAttributes();
|
||||
|
||||
assertThat(attributes.containsKey("a")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSessionWhenGetAttributesAndPutThenDelegatesToCreateSession() {
|
||||
WebSession createdWebSession = this.webSessionStore.createSession().block();
|
||||
|
||||
Map<String, Object> attributes = createdWebSession.getAttributes();
|
||||
attributes.put("a", "b");
|
||||
|
||||
verify(createSession).setAttribute("a", "b");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSessionWhenGetAttributesAndPutNullThenDelegatesToCreateSession() {
|
||||
WebSession createdWebSession = this.webSessionStore.createSession().block();
|
||||
|
||||
Map<String, Object> attributes = createdWebSession.getAttributes();
|
||||
attributes.put("a", null);
|
||||
|
||||
verify(createSession).setAttribute("a", null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSessionWhenGetAttributesAndRemoveThenDelegatesToCreateSession() {
|
||||
WebSession createdWebSession = this.webSessionStore.createSession().block();
|
||||
|
||||
Map<String, Object> attributes = createdWebSession.getAttributes();
|
||||
attributes.remove("a");
|
||||
|
||||
verify(createSession).removeAttribute("a");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSessionWhenGetAttributesAndPutAllThenDelegatesToCreateSession() {
|
||||
WebSession createdWebSession = this.webSessionStore.createSession().block();
|
||||
|
||||
Map<String, Object> attributes = createdWebSession.getAttributes();
|
||||
attributes.putAll(Collections.singletonMap("a","b"));
|
||||
|
||||
verify(createSession).setAttribute("a", "b");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSessionWhenGetAttributesAndClearThenDelegatesToCreateSession() {
|
||||
when(this.createSession.getAttributeNames()).thenReturn(Collections.singleton("a"));
|
||||
WebSession createdWebSession = this.webSessionStore.createSession().block();
|
||||
|
||||
Map<String, Object> attributes = createdWebSession.getAttributes();
|
||||
attributes.clear();
|
||||
|
||||
verify(createSession).removeAttribute("a");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSessionWhenGetAttributesAndKeySetThenDelegatesToCreateSession() {
|
||||
when(this.createSession.getAttributeNames()).thenReturn(Collections.singleton("a"));
|
||||
WebSession createdWebSession = this.webSessionStore.createSession().block();
|
||||
|
||||
Map<String, Object> attributes = createdWebSession.getAttributes();
|
||||
|
||||
assertThat(attributes.keySet()).containsExactly("a");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSessionWhenGetAttributesAndValuesThenDelegatesToCreateSession() {
|
||||
when(this.createSession.getAttributeNames()).thenReturn(Collections.singleton("a"));
|
||||
when(this.createSession.getAttribute("a")).thenReturn("b");
|
||||
WebSession createdWebSession = this.webSessionStore.createSession().block();
|
||||
|
||||
Map<String, Object> attributes = createdWebSession.getAttributes();
|
||||
|
||||
assertThat(attributes.values()).containsExactly("b");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSessionWhenGetAttributesAndEntrySetThenDelegatesToCreateSession() {
|
||||
String attrName = "attrName";
|
||||
when(createSession.getAttributeNames()).thenReturn(Collections.singleton(attrName));
|
||||
String attrValue = "attrValue";
|
||||
when(createSession.getAttribute(attrName)).thenReturn(attrValue);
|
||||
WebSession createdWebSession = this.webSessionStore.createSession().block();
|
||||
|
||||
Map<String, Object> attributes = createdWebSession.getAttributes();
|
||||
Set<Map.Entry<String, Object>> entries = attributes.entrySet();
|
||||
|
||||
assertThat(entries).containsExactly(new AbstractMap.SimpleEntry<String, Object>(attrName, attrValue));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void storeSessionWhenInvokedThenSessionSaved() {
|
||||
when(this.sessionRepository.save(this.createSession)).thenReturn(Mono.empty());
|
||||
WebSession createdSession = this.webSessionStore.createSession().block();
|
||||
|
||||
this.webSessionStore.storeSession(createdSession).block();
|
||||
|
||||
verify(this.sessionRepository).save(this.createSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void retrieveSessionThenStarted() {
|
||||
String id = "id";
|
||||
WebSession retrievedWebSession = this.webSessionStore.retrieveSession(id).block();
|
||||
|
||||
assertThat(retrievedWebSession.isStarted()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeSessionWhenInvokedThenSessionSaved() {
|
||||
String sessionId = "session-id";
|
||||
when(this.sessionRepository.delete(sessionId)).thenReturn(Mono.empty());
|
||||
|
||||
this.webSessionStore.removeSession(sessionId).block();
|
||||
|
||||
verify(this.sessionRepository).delete(sessionId);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user