@@ -88,6 +88,321 @@ func TestAutorizationCodeFlowClient(t *testing.T) {
8888
8989}
9090
91+ func TestGetAuthURLWithInvitation (t * testing.T ) {
92+ assert := assert .New (t )
93+
94+ testBackendServerURL := "https://api.com"
95+ testKindeServerURL := "https://mytest.kinde.com"
96+
97+ callbackURL := fmt .Sprintf ("%v/callback" , testBackendServerURL )
98+ kindeAuthFlow , _ := NewAuthorizationCodeFlow (
99+ testKindeServerURL , "b9da18c441b44d81bab3e8232de2e18d" , "client_secret" , callbackURL ,
100+ WithSessionHooks (newTestSessionHooks ()),
101+ WithCustomStateGenerator (func (* AuthorizationCodeFlow ) string { return "test_state" }),
102+ )
103+
104+ // Test with invitation code
105+ invitationCode := "inv_123456789"
106+ authURL := kindeAuthFlow .GetAuthURLWithInvitation (invitationCode )
107+ assert .NotEmpty (authURL , "AuthURL cannot be empty" )
108+ assert .Contains (authURL , "invitation_code=inv_123456789" , "AuthURL should contain invitation_code parameter" )
109+ assert .Contains (authURL , "is_invitation=true" , "AuthURL should contain is_invitation parameter" )
110+
111+ // Test without invitation code (empty string)
112+ authURLNoInvitation := kindeAuthFlow .GetAuthURLWithInvitation ("" )
113+ assert .NotEmpty (authURLNoInvitation , "AuthURL cannot be empty" )
114+ assert .NotContains (authURLNoInvitation , "invitation_code" , "AuthURL should not contain invitation_code when empty" )
115+ assert .NotContains (authURLNoInvitation , "is_invitation" , "AuthURL should not contain is_invitation when empty" )
116+ }
117+
118+ func TestWithInvitationCodeOption (t * testing.T ) {
119+ assert := assert .New (t )
120+
121+ testBackendServerURL := "https://api.com"
122+ testKindeServerURL := "https://mytest.kinde.com"
123+
124+ callbackURL := fmt .Sprintf ("%v/callback" , testBackendServerURL )
125+ invitationCode := "inv_987654321"
126+ kindeAuthFlow , _ := NewAuthorizationCodeFlow (
127+ testKindeServerURL , "b9da18c441b44d81bab3e8232de2e18d" , "client_secret" , callbackURL ,
128+ WithSessionHooks (newTestSessionHooks ()),
129+ WithCustomStateGenerator (func (* AuthorizationCodeFlow ) string { return "test_state" }),
130+ WithInvitationCode (invitationCode ),
131+ )
132+
133+ flow := kindeAuthFlow .(* AuthorizationCodeFlow )
134+ invitationCodeValues , hasInvitationCode := flow .authURLOptions ["invitation_code" ]
135+ assert .True (hasInvitationCode , "invitation_code should be set in authURLOptions" )
136+ if hasInvitationCode {
137+ assert .Contains (invitationCodeValues , invitationCode , "invitation_code should contain the provided value" )
138+ }
139+
140+ isInvitationValues , hasIsInvitation := flow .authURLOptions ["is_invitation" ]
141+ assert .True (hasIsInvitation , "is_invitation should be set in authURLOptions" )
142+ if hasIsInvitation {
143+ assert .Contains (isInvitationValues , "true" , "is_invitation should be set to 'true'" )
144+ }
145+
146+ authURL := kindeAuthFlow .GetAuthURL ()
147+ assert .Contains (authURL , "invitation_code=inv_987654321" , "AuthURL should contain invitation_code parameter" )
148+ assert .Contains (authURL , "is_invitation=true" , "AuthURL should contain is_invitation parameter" )
149+ }
150+
151+ func TestWithInvitationCodeOptionEmpty (t * testing.T ) {
152+ assert := assert .New (t )
153+
154+ testBackendServerURL := "https://api.com"
155+ testKindeServerURL := "https://mytest.kinde.com"
156+
157+ callbackURL := fmt .Sprintf ("%v/callback" , testBackendServerURL )
158+ kindeAuthFlow , _ := NewAuthorizationCodeFlow (
159+ testKindeServerURL , "b9da18c441b44d81bab3e8232de2e18d" , "client_secret" , callbackURL ,
160+ WithSessionHooks (newTestSessionHooks ()),
161+ WithCustomStateGenerator (func (* AuthorizationCodeFlow ) string { return "test_state" }),
162+ WithInvitationCode ("" ), // Empty invitation code should not add parameters
163+ )
164+
165+ flow := kindeAuthFlow .(* AuthorizationCodeFlow )
166+ _ , hasInvitationCode := flow .authURLOptions ["invitation_code" ]
167+ _ , hasIsInvitation := flow .authURLOptions ["is_invitation" ]
168+ assert .False (hasInvitationCode , "invitation_code should not be set when empty" )
169+ assert .False (hasIsInvitation , "is_invitation should not be set when empty" )
170+ }
171+
172+ func TestWithInvitationCodeOptionWhitespace (t * testing.T ) {
173+ assert := assert .New (t )
174+
175+ testBackendServerURL := "https://api.com"
176+ testKindeServerURL := "https://mytest.kinde.com"
177+
178+ callbackURL := fmt .Sprintf ("%v/callback" , testBackendServerURL )
179+ testCases := []string {" " , " " , "\t " , "\n " , " \t \n " }
180+
181+ for _ , whitespaceCode := range testCases {
182+ t .Run (fmt .Sprintf ("whitespace_%q" , whitespaceCode ), func (t * testing.T ) {
183+ kindeAuthFlow , _ := NewAuthorizationCodeFlow (
184+ testKindeServerURL , "b9da18c441b44d81bab3e8232de2e18d" , "client_secret" , callbackURL ,
185+ WithSessionHooks (newTestSessionHooks ()),
186+ WithCustomStateGenerator (func (* AuthorizationCodeFlow ) string { return "test_state" }),
187+ WithInvitationCode (whitespaceCode ), // Whitespace-only invitation code should not add parameters
188+ )
189+
190+ flow := kindeAuthFlow .(* AuthorizationCodeFlow )
191+ _ , hasInvitationCode := flow .authURLOptions ["invitation_code" ]
192+ _ , hasIsInvitation := flow .authURLOptions ["is_invitation" ]
193+ assert .False (hasInvitationCode , "invitation_code should not be set when whitespace-only" )
194+ assert .False (hasIsInvitation , "is_invitation should not be set when whitespace-only" )
195+ })
196+ }
197+ }
198+
199+ // TestGetAuthURLWithInvitationParameterPrecedence tests that invitation code parameter
200+ // takes precedence over option when both are provided
201+ func TestGetAuthURLWithInvitationParameterPrecedence (t * testing.T ) {
202+ assert := assert .New (t )
203+
204+ testBackendServerURL := "https://api.com"
205+ testKindeServerURL := "https://mytest.kinde.com"
206+
207+ callbackURL := fmt .Sprintf ("%v/callback" , testBackendServerURL )
208+ optionInvitationCode := "inv_from_option"
209+ parameterInvitationCode := "inv_from_parameter"
210+ kindeAuthFlow , _ := NewAuthorizationCodeFlow (
211+ testKindeServerURL , "b9da18c441b44d81bab3e8232de2e18d" , "client_secret" , callbackURL ,
212+ WithSessionHooks (newTestSessionHooks ()),
213+ WithCustomStateGenerator (func (* AuthorizationCodeFlow ) string { return "test_state" }),
214+ WithInvitationCode (optionInvitationCode ),
215+ )
216+
217+ // When GetAuthURLWithInvitation is called with a parameter, it should override the option
218+ authURL := kindeAuthFlow .GetAuthURLWithInvitation (parameterInvitationCode )
219+ assert .Contains (authURL , fmt .Sprintf ("invitation_code=%s" , parameterInvitationCode ), "Parameter invitation code should take precedence" )
220+ assert .Contains (authURL , "is_invitation=true" , "is_invitation should be set when parameter is provided" )
221+ assert .NotContains (authURL , optionInvitationCode , "Option invitation code should not appear when parameter is provided" )
222+ }
223+
224+ // TestGetAuthURLWithInvitationWithOtherOptions tests that invitation code works
225+ // correctly when combined with other options like PKCE, audience, etc.
226+ func TestGetAuthURLWithInvitationWithOtherOptions (t * testing.T ) {
227+ assert := assert .New (t )
228+
229+ testBackendServerURL := "https://api.com"
230+ testKindeServerURL := "https://mytest.kinde.com"
231+
232+ callbackURL := fmt .Sprintf ("%v/callback" , testBackendServerURL )
233+ invitationCode := "inv_combined_test"
234+ kindeAuthFlow , _ := NewAuthorizationCodeFlow (
235+ testKindeServerURL , "b9da18c441b44d81bab3e8232de2e18d" , "client_secret" , callbackURL ,
236+ WithSessionHooks (newTestSessionHooks ()),
237+ WithCustomStateGenerator (func (* AuthorizationCodeFlow ) string { return "test_state" }),
238+ WithInvitationCode (invitationCode ),
239+ WithAudience ("http://my.api.com/api" ),
240+ WithPKCE (),
241+ )
242+
243+ authURL := kindeAuthFlow .GetAuthURLWithInvitation (invitationCode )
244+ assert .Contains (authURL , fmt .Sprintf ("invitation_code=%s" , invitationCode ), "Should contain invitation_code" )
245+ assert .Contains (authURL , "is_invitation=true" , "Should contain is_invitation" )
246+ assert .Contains (authURL , "audience=http%3A%2F%2Fmy.api.com%2Fapi" , "Should contain audience parameter" )
247+ assert .Contains (authURL , "code_challenge=" , "Should contain PKCE code_challenge" )
248+ assert .Contains (authURL , "code_challenge_method=S256" , "Should contain PKCE method" )
249+ }
250+
251+ // TestGetAuthURLWithInvitationSpecialCharacters tests URL encoding of invitation codes
252+ // with special characters
253+ func TestGetAuthURLWithInvitationSpecialCharacters (t * testing.T ) {
254+ assert := assert .New (t )
255+
256+ testBackendServerURL := "https://api.com"
257+ testKindeServerURL := "https://mytest.kinde.com"
258+
259+ callbackURL := fmt .Sprintf ("%v/callback" , testBackendServerURL )
260+ kindeAuthFlow , _ := NewAuthorizationCodeFlow (
261+ testKindeServerURL , "b9da18c441b44d81bab3e8232de2e18d" , "client_secret" , callbackURL ,
262+ WithSessionHooks (newTestSessionHooks ()),
263+ WithCustomStateGenerator (func (* AuthorizationCodeFlow ) string { return "test_state" }),
264+ )
265+
266+ testCases := []struct {
267+ name string
268+ invitationCode string
269+ expectedInURL string
270+ }{
271+ {
272+ name : "invitation code with spaces" ,
273+ invitationCode : "inv code with spaces" ,
274+ expectedInURL : "invitation_code=inv+code+with+spaces" ,
275+ },
276+ {
277+ name : "invitation code with special chars" ,
278+ invitationCode : "inv_123-456@789" ,
279+ expectedInURL : "invitation_code=inv_123-456%40789" ,
280+ },
281+ {
282+ name : "invitation code with unicode" ,
283+ invitationCode : "inv_测试_123" ,
284+ expectedInURL : "invitation_code=inv_%E6%B5%8B%E8%AF%95_123" ,
285+ },
286+ }
287+
288+ for _ , tc := range testCases {
289+ t .Run (tc .name , func (t * testing.T ) {
290+ authURL := kindeAuthFlow .GetAuthURLWithInvitation (tc .invitationCode )
291+ assert .Contains (authURL , tc .expectedInURL , "URL should contain properly encoded invitation code" )
292+ assert .Contains (authURL , "is_invitation=true" , "Should contain is_invitation parameter" )
293+ })
294+ }
295+ }
296+
297+ // TestGetAuthURLWithInvitationMultipleCalls tests that multiple calls with different
298+ // invitation codes work correctly
299+ func TestGetAuthURLWithInvitationMultipleCalls (t * testing.T ) {
300+ assert := assert .New (t )
301+
302+ testBackendServerURL := "https://api.com"
303+ testKindeServerURL := "https://mytest.kinde.com"
304+
305+ callbackURL := fmt .Sprintf ("%v/callback" , testBackendServerURL )
306+ kindeAuthFlow , _ := NewAuthorizationCodeFlow (
307+ testKindeServerURL , "b9da18c441b44d81bab3e8232de2e18d" , "client_secret" , callbackURL ,
308+ WithSessionHooks (newTestSessionHooks ()),
309+ WithCustomStateGenerator (func (* AuthorizationCodeFlow ) string { return "test_state" }),
310+ )
311+
312+ // First call with invitation code
313+ invitationCode1 := "inv_first"
314+ authURL1 := kindeAuthFlow .GetAuthURLWithInvitation (invitationCode1 )
315+ assert .Contains (authURL1 , fmt .Sprintf ("invitation_code=%s" , invitationCode1 ), "First URL should contain first invitation code" )
316+ assert .Contains (authURL1 , "is_invitation=true" , "First URL should contain is_invitation" )
317+
318+ // Second call with different invitation code
319+ invitationCode2 := "inv_second"
320+ authURL2 := kindeAuthFlow .GetAuthURLWithInvitation (invitationCode2 )
321+ assert .Contains (authURL2 , fmt .Sprintf ("invitation_code=%s" , invitationCode2 ), "Second URL should contain second invitation code" )
322+ assert .Contains (authURL2 , "is_invitation=true" , "Second URL should contain is_invitation" )
323+ assert .NotContains (authURL2 , invitationCode1 , "Second URL should not contain first invitation code" )
324+
325+ // Third call without invitation code
326+ authURL3 := kindeAuthFlow .GetAuthURLWithInvitation ("" )
327+ assert .NotContains (authURL3 , "invitation_code" , "Third URL should not contain invitation_code" )
328+ assert .NotContains (authURL3 , "is_invitation" , "Third URL should not contain is_invitation" )
329+ }
330+
331+ // TestGetAuthURLWithInvitationOptionAndEmptyParameter tests that when option is set
332+ // but empty parameter is passed, the option values are still used (since empty parameter
333+ // doesn't override the option values in authURLOptions)
334+ func TestGetAuthURLWithInvitationOptionAndEmptyParameter (t * testing.T ) {
335+ assert := assert .New (t )
336+
337+ testBackendServerURL := "https://api.com"
338+ testKindeServerURL := "https://mytest.kinde.com"
339+
340+ callbackURL := fmt .Sprintf ("%v/callback" , testBackendServerURL )
341+ optionInvitationCode := "inv_from_option"
342+ kindeAuthFlow , _ := NewAuthorizationCodeFlow (
343+ testKindeServerURL , "b9da18c441b44d81bab3e8232de2e18d" , "client_secret" , callbackURL ,
344+ WithSessionHooks (newTestSessionHooks ()),
345+ WithCustomStateGenerator (func (* AuthorizationCodeFlow ) string { return "test_state" }),
346+ WithInvitationCode (optionInvitationCode ),
347+ )
348+
349+ // When empty parameter is passed, it doesn't override option values
350+ // The option values are already in authURLOptions and will be included
351+ authURL := kindeAuthFlow .GetAuthURLWithInvitation ("" )
352+ assert .Contains (authURL , fmt .Sprintf ("invitation_code=%s" , optionInvitationCode ), "Should use invitation code from option when parameter is empty" )
353+ assert .Contains (authURL , "is_invitation=true" , "Should contain is_invitation from option" )
354+ }
355+
356+ // TestGetAuthURLWithInvitationWhitespaceOnly tests that whitespace-only invitation codes
357+ // are treated as empty
358+ func TestGetAuthURLWithInvitationWhitespaceOnly (t * testing.T ) {
359+ assert := assert .New (t )
360+
361+ testBackendServerURL := "https://api.com"
362+ testKindeServerURL := "https://mytest.kinde.com"
363+
364+ callbackURL := fmt .Sprintf ("%v/callback" , testBackendServerURL )
365+ kindeAuthFlow , _ := NewAuthorizationCodeFlow (
366+ testKindeServerURL , "b9da18c441b44d81bab3e8232de2e18d" , "client_secret" , callbackURL ,
367+ WithSessionHooks (newTestSessionHooks ()),
368+ WithCustomStateGenerator (func (* AuthorizationCodeFlow ) string { return "test_state" }),
369+ )
370+
371+ testCases := []string {" " , " " , "\t " , "\n " , " \t \n " }
372+
373+ for _ , whitespaceCode := range testCases {
374+ t .Run (fmt .Sprintf ("whitespace_%q" , whitespaceCode ), func (t * testing.T ) {
375+ authURL := kindeAuthFlow .GetAuthURLWithInvitation (whitespaceCode )
376+ // Whitespace-only codes should be trimmed and treated as empty
377+ assert .NotContains (authURL , "invitation_code=" , "AuthURL should not contain invitation_code parameter for whitespace-only codes" )
378+ assert .NotContains (authURL , "is_invitation=" , "AuthURL should not contain is_invitation parameter for whitespace-only codes" )
379+ })
380+ }
381+ }
382+
383+ // TestGetAuthURLIncludesInvitationCodeFromOption tests that GetAuthURL() includes
384+ // invitation code when set via option
385+ func TestGetAuthURLIncludesInvitationCodeFromOption (t * testing.T ) {
386+ assert := assert .New (t )
387+
388+ testBackendServerURL := "https://api.com"
389+ testKindeServerURL := "https://mytest.kinde.com"
390+
391+ callbackURL := fmt .Sprintf ("%v/callback" , testBackendServerURL )
392+ invitationCode := "inv_via_option"
393+ kindeAuthFlow , _ := NewAuthorizationCodeFlow (
394+ testKindeServerURL , "b9da18c441b44d81bab3e8232de2e18d" , "client_secret" , callbackURL ,
395+ WithSessionHooks (newTestSessionHooks ()),
396+ WithCustomStateGenerator (func (* AuthorizationCodeFlow ) string { return "test_state" }),
397+ WithInvitationCode (invitationCode ),
398+ )
399+
400+ // GetAuthURL() should include invitation code from option
401+ authURL := kindeAuthFlow .GetAuthURL ()
402+ assert .Contains (authURL , fmt .Sprintf ("invitation_code=%s" , invitationCode ), "GetAuthURL should include invitation code from option" )
403+ assert .Contains (authURL , "is_invitation=true" , "GetAuthURL should include is_invitation from option" )
404+ }
405+
91406func getTestAuthorizationServer () * httptest.Server {
92407 return httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
93408
0 commit comments