@@ -6,21 +6,16 @@ import { ContextFactories } from '../../../src/types/graphql';
66import { WorkspaceSsoConfig } from '../../../src/sso/types' ;
77import { WorkspaceDBScheme , UserDBScheme } from '@hawk.so/types' ;
88import SamlService from '../../../src/sso/saml/service' ;
9- import samlStore from '../../../src/sso/saml/store' ;
9+ import { MemorySamlStateStore } from '../../../src/sso/saml/store/memory. store' ;
1010import * as mongo from '../../../src/mongo' ;
11+ import WorkspaceModel from '../../../src/models/workspace' ;
12+ import UserModel from '../../../src/models/user' ;
1113
1214/**
1315 * Mock dependencies
1416 */
1517jest . mock ( '../../../src/sso/saml/service' ) ;
1618
17- /**
18- * Import models AFTER mongo setup to ensure databases.hawk is initialized
19- * This must be done after beforeAll sets up connections
20- */
21- import WorkspaceModel from '../../../src/models/workspace' ;
22- import UserModel from '../../../src/models/user' ;
23-
2419beforeAll ( async ( ) => {
2520 /**
2621 * Ensure MONGO_HAWK_DB_URL is set from MONGO_URL (set by @shelf/jest-mongodb)
@@ -55,6 +50,7 @@ describe('SamlController', () => {
5550 let mockSamlService : jest . Mocked < SamlService > ;
5651 let mockReq : Partial < Request > ;
5752 let mockRes : Partial < Response > ;
53+ let samlStore : MemorySamlStateStore ;
5854
5955 const testWorkspaceId = new ObjectId ( ) . toString ( ) ;
6056 const testUserId = new ObjectId ( ) . toString ( ) ;
@@ -140,6 +136,11 @@ describe('SamlController', () => {
140136 * Clear all mocks
141137 */
142138 jest . clearAllMocks ( ) ;
139+
140+ /**
141+ * Create fresh store instance for each test
142+ */
143+ samlStore = new MemorySamlStateStore ( ) ;
143144 samlStore . clear ( ) ;
144145
145146 /**
@@ -182,9 +183,9 @@ describe('SamlController', () => {
182183 ( SamlService as jest . Mock ) . mockImplementation ( ( ) => mockSamlService ) ;
183184
184185 /**
185- * Create controller
186+ * Create controller with store
186187 */
187- controller = new SamlController ( mockFactories ) ;
188+ controller = new SamlController ( mockFactories , samlStore ) ;
188189
189190 /**
190191 * Mock Express Request
@@ -264,7 +265,7 @@ describe('SamlController', () => {
264265 /**
265266 * Verify AuthnRequest ID was saved by checking it can be validated
266267 */
267- expect ( samlStore . validateAndConsumeAuthnRequest ( mockRequestId , testWorkspaceId ) ) . toBe ( true ) ;
268+ expect ( await samlStore . validateAndConsumeAuthnRequest ( mockRequestId , testWorkspaceId ) ) . toBe ( true ) ;
268269 } ) ;
269270
270271 it ( 'should use default returnUrl when not provided' , async ( ) => {
@@ -292,7 +293,7 @@ describe('SamlController', () => {
292293 * Verify that default returnUrl was saved in RelayState
293294 * Default returnUrl is `/workspace/${workspaceId}`
294295 */
295- const relayState = samlStore . getRelayState ( relayStateId ! ) ;
296+ const relayState = await samlStore . getRelayState ( relayStateId ! ) ;
296297 expect ( relayState ) . not . toBeNull ( ) ;
297298 expect ( relayState ?. returnUrl ) . toBe ( `/workspace/${ testWorkspaceId } ` ) ;
298299 expect ( relayState ?. workspaceId ) . toBe ( testWorkspaceId ) ;
@@ -370,7 +371,7 @@ describe('SamlController', () => {
370371 * Setup test data
371372 */
372373 const testReturnUrl = '/workspace/test' ;
373- const expectedFrontendUrl = `${ process . env . GARAGE_URL } ${ testReturnUrl } ` ;
374+ const expectedCallbackPath = `/login/sso/ ${ testWorkspaceId } ` ;
374375
375376 mockWorkspacesFactory . findById . mockResolvedValue ( workspace ) ;
376377 mockUsersFactory . findBySamlIdentity . mockResolvedValue ( user ) ;
@@ -379,11 +380,11 @@ describe('SamlController', () => {
379380 /**
380381 * Setup samlStore to return valid state for tests
381382 */
382- samlStore . saveRelayState ( testRelayStateId , {
383+ await samlStore . saveRelayState ( testRelayStateId , {
383384 returnUrl : testReturnUrl ,
384385 workspaceId : testWorkspaceId ,
385386 } ) ;
386- samlStore . saveAuthnRequest ( testRequestId , testWorkspaceId ) ;
387+ await samlStore . saveAuthnRequest ( testRequestId , testWorkspaceId ) ;
387388
388389 await controller . handleAcs ( mockReq as Request , mockRes as Response ) ;
389390
@@ -421,16 +422,18 @@ describe('SamlController', () => {
421422 expect ( user . generateTokensPair ) . toHaveBeenCalled ( ) ;
422423
423424 /**
424- * Verify redirect to frontend with returnUrl from RelayState
425+ * Verify redirect to Garage SSO callback page with tokens and returnUrl
425426 * GARAGE_URL is set in beforeEach: 'https://garage.example.com'
426427 */
427428 expect ( mockRes . redirect ) . toHaveBeenCalledWith (
428- expect . stringContaining ( expectedFrontendUrl )
429+ expect . stringContaining ( expectedCallbackPath )
429430 ) ;
430431
431432 const redirectUrl = new URL ( ( mockRes . redirect as jest . Mock ) . mock . calls [ 0 ] [ 0 ] ) ;
433+ expect ( redirectUrl . pathname ) . toBe ( expectedCallbackPath ) ;
432434 expect ( redirectUrl . searchParams . get ( 'access_token' ) ) . toBe ( 'test-access-token' ) ;
433435 expect ( redirectUrl . searchParams . get ( 'refresh_token' ) ) . toBe ( 'test-refresh-token' ) ;
436+ expect ( redirectUrl . searchParams . get ( 'returnUrl' ) ) . toBe ( testReturnUrl ) ;
434437 } ) ;
435438
436439 it ( 'should return 400 error when workspace is not found' , async ( ) => {
@@ -502,11 +505,11 @@ describe('SamlController', () => {
502505 /**
503506 * Setup samlStore with valid state
504507 */
505- samlStore . saveRelayState ( testRelayStateId , {
508+ await samlStore . saveRelayState ( testRelayStateId , {
506509 returnUrl : '/workspace/test' ,
507510 workspaceId : testWorkspaceId ,
508511 } ) ;
509- samlStore . saveAuthnRequest ( testRequestId , testWorkspaceId ) ;
512+ await samlStore . saveAuthnRequest ( testRequestId , testWorkspaceId ) ;
510513 ( workspace . getMemberInfo as jest . Mock ) . mockResolvedValue ( null ) ;
511514
512515 await controller . handleAcs ( mockReq as Request , mockRes as Response ) ;
@@ -546,11 +549,11 @@ describe('SamlController', () => {
546549 /**
547550 * Setup samlStore with valid state
548551 */
549- samlStore . saveRelayState ( testRelayStateId , {
552+ await samlStore . saveRelayState ( testRelayStateId , {
550553 returnUrl : '/workspace/test' ,
551554 workspaceId : testWorkspaceId ,
552555 } ) ;
553- samlStore . saveAuthnRequest ( testRequestId , testWorkspaceId ) ;
556+ await samlStore . saveAuthnRequest ( testRequestId , testWorkspaceId ) ;
554557 ( workspace . getMemberInfo as jest . Mock ) . mockResolvedValue ( null ) ;
555558
556559 await controller . handleAcs ( mockReq as Request , mockRes as Response ) ;
@@ -582,11 +585,11 @@ describe('SamlController', () => {
582585 /**
583586 * Setup samlStore with valid state
584587 */
585- samlStore . saveRelayState ( testRelayStateId , {
588+ await samlStore . saveRelayState ( testRelayStateId , {
586589 returnUrl : '/workspace/test' ,
587590 workspaceId : testWorkspaceId ,
588591 } ) ;
589- samlStore . saveAuthnRequest ( testRequestId , testWorkspaceId ) ;
592+ await samlStore . saveAuthnRequest ( testRequestId , testWorkspaceId ) ;
590593 ( workspace . getMemberInfo as jest . Mock ) . mockResolvedValue ( {
591594 userEmail : testEmail ,
592595 } ) ;
@@ -621,17 +624,23 @@ describe('SamlController', () => {
621624 /**
622625 * Setup samlStore with AuthnRequest but no RelayState
623626 */
624- samlStore . saveAuthnRequest ( testRequestId , testWorkspaceId ) ;
627+ await samlStore . saveAuthnRequest ( testRequestId , testWorkspaceId ) ;
625628
626629 await controller . handleAcs ( mockReq as Request , mockRes as Response ) ;
627630
628631 /**
629- * Verify redirect uses default returnUrl
632+ * Verify redirect to Garage SSO callback page with default returnUrl
630633 */
634+ const expectedCallbackPath = `/login/sso/${ testWorkspaceId } ` ;
635+ const defaultReturnUrl = `/workspace/${ testWorkspaceId } ` ;
636+
631637 expect ( mockRes . redirect ) . toHaveBeenCalledWith (
632- expect . stringContaining ( `/workspace/ ${ testWorkspaceId } ` )
638+ expect . stringContaining ( expectedCallbackPath )
633639 ) ;
640+
641+ const redirectUrl = new URL ( ( mockRes . redirect as jest . Mock ) . mock . calls [ 0 ] [ 0 ] ) ;
642+ expect ( redirectUrl . pathname ) . toBe ( expectedCallbackPath ) ;
643+ expect ( redirectUrl . searchParams . get ( 'returnUrl' ) ) . toBe ( defaultReturnUrl ) ;
634644 } ) ;
635645 } ) ;
636646} ) ;
637-
0 commit comments