Skip to content

Commit 467b0b6

Browse files
committed
test(auth): improve session store test coverage and QQQ standards
- Remove unused PROVIDER_INTERFACE constant from QSessionStoreHelper - Extend BaseTest in QSessionStoreHelperTest per QQQ conventions - Add flower-box Javadoc comments to all test methods - Add memoization test for isSessionStoreAvailable - Add integration tests for sessionStoreEnabled graceful fallback
1 parent 0a8d83f commit 467b0b6

File tree

3 files changed

+127
-25
lines changed

3 files changed

+127
-25
lines changed

qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/authentication/QSessionStoreHelper.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,16 @@
3232

3333
/*******************************************************************************
3434
** Helper class for optionally interacting with the QSessionStore QBit.
35+
**
3536
** Uses reflection to avoid a hard dependency on the qbit-session-store module.
37+
** All methods are designed to fail silently (returning empty/default values)
38+
** when the QBit is not on the classpath, ensuring backwards compatibility.
3639
*******************************************************************************/
3740
public class QSessionStoreHelper
3841
{
3942
private static final QLogger LOG = QLogger.getLogger(QSessionStoreHelper.class);
4043

4144
private static final String CONTEXT_CLASS = "com.kingsrook.qbits.sessionstore.QSessionStoreQBitContext";
42-
private static final String PROVIDER_INTERFACE = "com.kingsrook.qbits.sessionstore.QSessionStoreProviderInterface";
4345

4446
private static Boolean sessionStoreAvailable = null;
4547

qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/modules/authentication/QSessionStoreHelperTest.java

Lines changed: 41 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -24,48 +24,49 @@
2424

2525
import java.time.Duration;
2626
import java.util.Optional;
27+
import com.kingsrook.qqq.backend.core.BaseTest;
2728
import com.kingsrook.qqq.backend.core.model.session.QSession;
2829
import org.junit.jupiter.api.Test;
2930
import static org.assertj.core.api.Assertions.assertThat;
3031

3132

3233
/*******************************************************************************
33-
** Tests for QSessionStoreHelper.
34+
** Unit test for QSessionStoreHelper.
35+
**
36+
** These tests verify the behavior when the qbit-session-store module is NOT
37+
** on the classpath, ensuring graceful degradation and backwards compatibility.
3438
*******************************************************************************/
35-
class QSessionStoreHelperTest
39+
class QSessionStoreHelperTest extends BaseTest
3640
{
3741

38-
/***************************************************************************
42+
/*******************************************************************************
3943
** Test that session store is not available when QBit is not on classpath.
40-
***************************************************************************/
44+
*******************************************************************************/
4145
@Test
42-
void testSessionStoreNotAvailable()
46+
void testIsSessionStoreAvailable_returnsFalseWhenQBitNotPresent()
4347
{
44-
//////////////////////////////////////////////////////////////////////////
45-
// without the qbit-session-store dependency, the store is unavailable //
46-
//////////////////////////////////////////////////////////////////////////
4748
assertThat(QSessionStoreHelper.isSessionStoreAvailable()).isFalse();
4849
}
4950

5051

5152

52-
/***************************************************************************
53-
** Test that load returns empty when store is not available.
54-
***************************************************************************/
53+
/*******************************************************************************
54+
** Test that load returns empty Optional when store is not available.
55+
*******************************************************************************/
5556
@Test
56-
void testLoadReturnsEmptyWhenNotAvailable()
57+
void testLoadSession_returnsEmptyWhenNotAvailable()
5758
{
5859
Optional<QSession> result = QSessionStoreHelper.loadSession("test-uuid");
5960
assertThat(result).isEmpty();
6061
}
6162

6263

6364

64-
/***************************************************************************
65-
** Test that store is a no-op when not available.
66-
***************************************************************************/
65+
/*******************************************************************************
66+
** Test that store is a no-op when QBit is not available.
67+
*******************************************************************************/
6768
@Test
68-
void testStoreIsNoOpWhenNotAvailable()
69+
void testStoreSession_isNoOpWhenNotAvailable()
6970
{
7071
///////////////////////////////////////////
7172
// should not throw, just silently no-op //
@@ -77,11 +78,11 @@ void testStoreIsNoOpWhenNotAvailable()
7778

7879

7980

80-
/***************************************************************************
81-
** Test that touch is a no-op when not available.
82-
***************************************************************************/
81+
/*******************************************************************************
82+
** Test that touch is a no-op when QBit is not available.
83+
*******************************************************************************/
8384
@Test
84-
void testTouchIsNoOpWhenNotAvailable()
85+
void testTouchSession_isNoOpWhenNotAvailable()
8586
{
8687
///////////////////////////////////////////
8788
// should not throw, just silently no-op //
@@ -91,14 +92,30 @@ void testTouchIsNoOpWhenNotAvailable()
9192

9293

9394

94-
/***************************************************************************
95-
** Test that getDefaultTtl returns 1 hour when not available.
96-
***************************************************************************/
95+
/*******************************************************************************
96+
** Test that getDefaultTtl returns 1 hour fallback when QBit is not available.
97+
*******************************************************************************/
9798
@Test
98-
void testGetDefaultTtlReturnsOneHourWhenNotAvailable()
99+
void testGetDefaultTtl_returnsOneHourFallbackWhenNotAvailable()
99100
{
100101
Duration ttl = QSessionStoreHelper.getDefaultTtl();
101102
assertThat(ttl).isEqualTo(Duration.ofHours(1));
102103
}
103104

105+
106+
107+
/*******************************************************************************
108+
** Test that repeated calls to isSessionStoreAvailable are consistent.
109+
*******************************************************************************/
110+
@Test
111+
void testIsSessionStoreAvailable_isMemoized()
112+
{
113+
///////////////////////////////////////////////////////////////////////
114+
// call multiple times to verify the memoization doesn't break state //
115+
///////////////////////////////////////////////////////////////////////
116+
assertThat(QSessionStoreHelper.isSessionStoreAvailable()).isFalse();
117+
assertThat(QSessionStoreHelper.isSessionStoreAvailable()).isFalse();
118+
assertThat(QSessionStoreHelper.isSessionStoreAvailable()).isFalse();
119+
}
120+
104121
}

qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/modules/authentication/implementations/OAuth2AuthenticationModuleIntegrationTest.java

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,89 @@ void testSessionResume_finalCustomizeSessionIsCalled() throws Exception
222222

223223

224224

225+
/***************************************************************************
226+
** Test that session store integration works gracefully when QBit not present
227+
** When sessionStoreEnabled=true but qbit-session-store is not on classpath,
228+
** the module should fall back to the standard flow without errors.
229+
***************************************************************************/
230+
@Test
231+
void testSessionResume_sessionStoreEnabled_gracefulFallbackWhenQBitNotPresent() throws Exception
232+
{
233+
///////////////////////////////////////////////////////////////////////
234+
// Enable session store on the auth metadata //
235+
///////////////////////////////////////////////////////////////////////
236+
OAuth2AuthenticationMetaData authMetaData = (OAuth2AuthenticationMetaData) qInstance.getAuthentication();
237+
authMetaData.setSessionStoreEnabled(true);
238+
239+
///////////////////////////////////////////////////////////////////////
240+
// Create session the normal way //
241+
///////////////////////////////////////////////////////////////////////
242+
String accessToken = createTestJwt("store-test@example.com", "Store Test User", Map.of());
243+
String sessionUuid = "store-test-uuid-11111";
244+
insertUserSession(sessionUuid, accessToken, "store-test@example.com");
245+
246+
OAuth2AuthenticationModule module = new OAuth2AuthenticationModule();
247+
Map<String, String> context = new HashMap<>();
248+
context.put("sessionUUID", sessionUuid);
249+
250+
///////////////////////////////////////////////////////////////////////
251+
// Should complete successfully even though QBit is not on classpath //
252+
///////////////////////////////////////////////////////////////////////
253+
QSession session = module.createSession(qInstance, context);
254+
255+
assertNotNull(session);
256+
assertNotNull(session.getUser());
257+
assertEquals("store-test@example.com", session.getUser().getIdReference());
258+
assertEquals("Store Test User", session.getUser().getFullName());
259+
260+
///////////////////////////////////////////////////////////////////////
261+
// Customizers should still be called //
262+
///////////////////////////////////////////////////////////////////////
263+
assertTrue(session.hasSecurityKeyValue("testSecurityKey", "fromCustomizer"));
264+
assertTrue(session.hasSecurityKeyValue("finalSecurityKey", "fromFinalCustomizer"));
265+
}
266+
267+
268+
269+
/***************************************************************************
270+
** Test that token exchange works with sessionStoreEnabled=true
271+
***************************************************************************/
272+
@Test
273+
void testTokenExchange_sessionStoreEnabled_gracefulFallbackWhenQBitNotPresent() throws Exception
274+
{
275+
///////////////////////////////////////////////////////////////////////
276+
// Enable session store on the auth metadata //
277+
///////////////////////////////////////////////////////////////////////
278+
OAuth2AuthenticationMetaData authMetaData = (OAuth2AuthenticationMetaData) qInstance.getAuthentication();
279+
authMetaData.setSessionStoreEnabled(true);
280+
281+
///////////////////////////////////////////////////////////////////////
282+
// Set up mocks for token exchange //
283+
///////////////////////////////////////////////////////////////////////
284+
String accessToken = createTestJwt("pkce-store@example.com", "PKCE Store User", Map.of());
285+
stubOidcDiscovery();
286+
stubTokenEndpoint(accessToken);
287+
288+
///////////////////////////////////////////////////////////////////////
289+
// Create session via PKCE flow with session store enabled //
290+
///////////////////////////////////////////////////////////////////////
291+
OAuth2AuthenticationModule module = new OAuth2AuthenticationModule();
292+
Map<String, String> context = new HashMap<>();
293+
context.put("code", "test-authorization-code-2");
294+
context.put("redirectUri", "http://localhost:3000/callback");
295+
context.put("codeVerifier", "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk");
296+
297+
QSession session = module.createSession(qInstance, context);
298+
299+
///////////////////////////////////////////////////////////////////////
300+
// Should complete successfully //
301+
///////////////////////////////////////////////////////////////////////
302+
assertNotNull(session);
303+
assertEquals("pkce-store@example.com", session.getUser().getIdReference());
304+
}
305+
306+
307+
225308
/***************************************************************************
226309
** Build a QInstance configured for OAuth2 with memory backend
227310
***************************************************************************/

0 commit comments

Comments
 (0)