Add WebFlux Support

This commit is contained in:
Rob Winch
2017-06-30 09:15:27 -05:00
committed by Vedran Pavic
parent f00c196430
commit 5abbe66b1d
6 changed files with 851 additions and 2 deletions

View File

@@ -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"

View File

@@ -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();
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -0,0 +1,8 @@
/**
* @author Rob Winch
* @since 5.0
*/
@NonNullApi
package org.springframework.session.web.server.session;
import org.springframework.lang.NonNullApi;

View File

@@ -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);
}
}

View File

@@ -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);
}
}