11/*
2- * Copyright 2002-2024 the original author or authors.
2+ * Copyright 2002-2025 the original author or authors.
33 *
44 * Licensed under the Apache License, Version 2.0 (the "License");
55 * you may not use this file except in compliance with the License.
1919import java .time .Clock ;
2020import java .time .Duration ;
2121import java .time .Instant ;
22- import java .util .Map ;
2322import java .util .stream .IntStream ;
2423
2524import org .junit .jupiter .api .Test ;
2625import reactor .core .scheduler .Schedulers ;
26+ import reactor .test .StepVerifier ;
2727
2828import org .springframework .beans .DirectFieldAccessor ;
2929import org .springframework .web .server .WebSession ;
3535 * Tests for {@link InMemoryWebSessionStore}.
3636 *
3737 * @author Rob Winch
38+ * @author Sam Brannen
3839 */
3940class InMemoryWebSessionStoreTests {
4041
41- private InMemoryWebSessionStore store = new InMemoryWebSessionStore ();
42+ private final InMemoryWebSessionStore store = new InMemoryWebSessionStore ();
4243
4344
4445 @ Test
@@ -53,13 +54,14 @@ void startsSessionExplicitly() {
5354 void startsSessionImplicitly () {
5455 WebSession session = this .store .createWebSession ().block ();
5556 assertThat (session ).isNotNull ();
56- session .start ();
57+ // We intentionally do not invoke start().
58+ // session.start();
5759 session .getAttributes ().put ("foo" , "bar" );
5860 assertThat (session .isStarted ()).isTrue ();
5961 }
6062
6163 @ Test // gh-24027, gh-26958
62- public void createSessionDoesNotBlock () {
64+ void createSessionDoesNotBlock () {
6365 this .store .createWebSession ()
6466 .doOnNext (session -> assertThat (Schedulers .isInNonBlockingThread ()).isTrue ())
6567 .block ();
@@ -103,7 +105,7 @@ void lastAccessTimeIsUpdatedOnRetrieve() {
103105 }
104106
105107 @ Test // SPR-17051
106- public void sessionInvalidatedBeforeSave () {
108+ void sessionInvalidatedBeforeSave () {
107109 // Request 1 creates session
108110 WebSession session1 = this .store .createWebSession ().block ();
109111 assertThat (session1 ).isNotNull ();
@@ -132,33 +134,69 @@ public void sessionInvalidatedBeforeSave() {
132134
133135 @ Test
134136 void expirationCheckPeriod () {
135-
136- DirectFieldAccessor accessor = new DirectFieldAccessor (this .store );
137- Map <?,?> sessions = (Map <?, ?>) accessor .getPropertyValue ("sessions" );
138- assertThat (sessions ).isNotNull ();
139-
140137 // Create 100 sessions
141- IntStream .range ( 0 , 100 ).forEach (i -> insertSession ());
142- assertThat ( sessions ). hasSize (100 );
138+ IntStream .rangeClosed ( 1 , 100 ).forEach (i -> insertSession ());
139+ assertNumSessions (100 );
143140
144- // Force a new clock (31 min later), don't use setter which would clean expired sessions
141+ // Force a new clock (31 min later). Don't use setter which would clean expired sessions.
142+ DirectFieldAccessor accessor = new DirectFieldAccessor (this .store );
145143 accessor .setPropertyValue ("clock" , Clock .offset (this .store .getClock (), Duration .ofMinutes (31 )));
146- assertThat ( sessions ). hasSize (100 );
144+ assertNumSessions (100 );
147145
148- // Create 1 more which forces a time-based check (clock moved forward)
146+ // Create 1 more which forces a time-based check (clock moved forward).
149147 insertSession ();
150- assertThat ( sessions ). hasSize (1 );
148+ assertNumSessions (1 );
151149 }
152150
153151 @ Test
154152 void maxSessions () {
153+ this .store .setMaxSessions (10 );
154+
155+ IntStream .rangeClosed (1 , 10 ).forEach (i -> insertSession ());
156+ assertThatIllegalStateException ()
157+ .isThrownBy (this ::insertSession )
158+ .withMessage ("Max sessions limit reached: 10" );
159+ }
155160
156- IntStream .range (0 , 10000 ).forEach (i -> insertSession ());
157- assertThatIllegalStateException ().isThrownBy (
158- this ::insertSession )
159- .withMessage ("Max sessions limit reached: 10000" );
161+ @ Test
162+ void updateSession () {
163+ WebSession session = insertSession ();
164+
165+ StepVerifier .create (session .save ())
166+ .expectComplete ()
167+ .verify ();
168+ }
169+
170+ @ Test // gh-35013
171+ void updateSessionAfterMaxSessionLimitIsExceeded () {
172+ this .store .setMaxSessions (10 );
173+
174+ WebSession session = insertSession ();
175+ assertNumSessions (1 );
176+
177+ IntStream .rangeClosed (1 , 9 ).forEach (i -> insertSession ());
178+ assertNumSessions (10 );
179+
180+ // Updating an existing session should succeed.
181+ StepVerifier .create (session .save ())
182+ .expectComplete ()
183+ .verify ();
184+ assertNumSessions (10 );
185+
186+ // Saving an additional new session should fail.
187+ assertThatIllegalStateException ()
188+ .isThrownBy (this ::insertSession )
189+ .withMessage ("Max sessions limit reached: 10" );
190+ assertNumSessions (10 );
191+
192+ // Updating an existing session again should still succeed.
193+ StepVerifier .create (session .save ())
194+ .expectComplete ()
195+ .verify ();
196+ assertNumSessions (10 );
160197 }
161198
199+
162200 private WebSession insertSession () {
163201 WebSession session = this .store .createWebSession ().block ();
164202 assertThat (session ).isNotNull ();
@@ -167,4 +205,8 @@ private WebSession insertSession() {
167205 return session ;
168206 }
169207
208+ private void assertNumSessions (int numSessions ) {
209+ assertThat (store .getSessions ()).hasSize (numSessions );
210+ }
211+
170212}
0 commit comments