@@ -30,6 +30,7 @@ describe('SamlService', () => {
3030
3131 const mockSamlInstance = {
3232 validatePostResponseAsync : jest . fn ( ) ,
33+ getAuthorizeMessageAsync : jest . fn ( ) ,
3334 } ;
3435
3536 beforeEach ( ( ) => {
@@ -47,14 +48,109 @@ describe('SamlService', () => {
4748 } ) ;
4849
4950 describe ( 'generateAuthnRequest' , ( ) => {
51+ const testRelayState = 'test-relay-state-123' ;
52+
5053 /**
51- * TODO: Add tests for:
52- * 1. Should generate valid AuthnRequest with correct structure
53- * 2. Should include correct ACS URL
54- * 3. Should include correct SP Entity ID
55- * 4. Should return unique request ID
56- * 5. Should return base64-encoded request
54+ * Helper to create a mock SAML AuthnRequest (deflated + base64 encoded)
5755 */
56+ function createMockEncodedRequest ( requestId : string ) : string {
57+ const zlib = require ( 'zlib' ) ;
58+ const xml = `<?xml version="1.0"?>
59+ <samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
60+ ID="${ requestId } "
61+ Version="2.0"
62+ IssueInstant="2025-01-01T00:00:00Z"
63+ Destination="https://idp.example.com/sso"
64+ AssertionConsumerServiceURL="https://api.example.com/auth/sso/saml/507f1f77bcf86cd799439011/acs">
65+ <saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">urn:hawk:tracker:saml</saml:Issuer>
66+ </samlp:AuthnRequest>` ;
67+
68+ const deflated = zlib . deflateRawSync ( xml ) ;
69+
70+ return deflated . toString ( 'base64' ) ;
71+ }
72+
73+ it ( 'should generate AuthnRequest and return request ID' , async ( ) => {
74+ const mockRequestId = '_test-request-id-12345' ;
75+ const mockEncodedRequest = createMockEncodedRequest ( mockRequestId ) ;
76+
77+ mockSamlInstance . getAuthorizeMessageAsync . mockResolvedValue ( {
78+ SAMLRequest : mockEncodedRequest ,
79+ RelayState : testRelayState ,
80+ } ) ;
81+
82+ const result = await samlService . generateAuthnRequest (
83+ testWorkspaceId ,
84+ testAcsUrl ,
85+ testRelayState ,
86+ testSamlConfig
87+ ) ;
88+
89+ expect ( result . requestId ) . toBe ( mockRequestId ) ;
90+ expect ( result . encodedRequest ) . toBe ( mockEncodedRequest ) ;
91+ } ) ;
92+
93+ it ( 'should call getAuthorizeMessageAsync with correct relay state' , async ( ) => {
94+ const mockRequestId = '_another-request-id' ;
95+ const mockEncodedRequest = createMockEncodedRequest ( mockRequestId ) ;
96+
97+ mockSamlInstance . getAuthorizeMessageAsync . mockResolvedValue ( {
98+ SAMLRequest : mockEncodedRequest ,
99+ } ) ;
100+
101+ await samlService . generateAuthnRequest (
102+ testWorkspaceId ,
103+ testAcsUrl ,
104+ testRelayState ,
105+ testSamlConfig
106+ ) ;
107+
108+ expect ( mockSamlInstance . getAuthorizeMessageAsync ) . toHaveBeenCalledWith (
109+ testRelayState ,
110+ undefined ,
111+ { }
112+ ) ;
113+ } ) ;
114+
115+ it ( 'should throw error when SAMLRequest is not returned' , async ( ) => {
116+ mockSamlInstance . getAuthorizeMessageAsync . mockResolvedValue ( {
117+ /**
118+ * No SAMLRequest in response
119+ */
120+ } ) ;
121+
122+ await expect (
123+ samlService . generateAuthnRequest (
124+ testWorkspaceId ,
125+ testAcsUrl ,
126+ testRelayState ,
127+ testSamlConfig
128+ )
129+ ) . rejects . toThrow ( 'Failed to generate SAML AuthnRequest' ) ;
130+ } ) ;
131+
132+ it ( 'should throw error when request ID cannot be extracted' , async ( ) => {
133+ const zlib = require ( 'zlib' ) ;
134+ /**
135+ * Invalid XML without ID attribute
136+ */
137+ const invalidXml = '<invalid>no id here</invalid>' ;
138+ const deflated = zlib . deflateRawSync ( invalidXml ) ;
139+ const invalidEncodedRequest = deflated . toString ( 'base64' ) ;
140+
141+ mockSamlInstance . getAuthorizeMessageAsync . mockResolvedValue ( {
142+ SAMLRequest : invalidEncodedRequest ,
143+ } ) ;
144+
145+ await expect (
146+ samlService . generateAuthnRequest (
147+ testWorkspaceId ,
148+ testAcsUrl ,
149+ testRelayState ,
150+ testSamlConfig
151+ )
152+ ) . rejects . toThrow ( 'Failed to extract request ID from AuthnRequest' ) ;
153+ } ) ;
58154 } ) ;
59155
60156 describe ( 'validateAndParseResponse' , ( ) => {
0 commit comments