@@ -131,6 +131,110 @@ def mock_get_side_effect(key):
131131 self .oauth ._framework = self .mock_framework
132132
133133
134+ # -- Invitation code tests --
135+
136+ def test_generate_auth_url_with_invitation_code (self ):
137+ """invitation_code is included as a query parameter in the auth URL."""
138+ result = run_async (self .oauth .generate_auth_url (
139+ login_options = {LoginOptions .INVITATION_CODE : "abc123" }
140+ ))
141+ params = parse_qs (urlparse (result ["url" ]).query )
142+ self .assertEqual (params ["invitation_code" ], ["abc123" ])
143+
144+ def test_generate_auth_url_invitation_code_auto_sets_is_invitation (self ):
145+ """is_invitation is automatically set to 'true' when invitation_code is present."""
146+ result = run_async (self .oauth .generate_auth_url (
147+ login_options = {LoginOptions .INVITATION_CODE : "abc123" }
148+ ))
149+ params = parse_qs (urlparse (result ["url" ]).query )
150+ self .assertEqual (params ["is_invitation" ], ["true" ])
151+
152+ def test_generate_auth_url_invitation_code_with_explicit_is_invitation (self ):
153+ """Explicit is_invitation=True is honoured alongside invitation_code."""
154+ result = run_async (self .oauth .generate_auth_url (
155+ login_options = {
156+ LoginOptions .INVITATION_CODE : "abc123" ,
157+ LoginOptions .IS_INVITATION : True ,
158+ }
159+ ))
160+ params = parse_qs (urlparse (result ["url" ]).query )
161+ self .assertEqual (params ["invitation_code" ], ["abc123" ])
162+ self .assertEqual (params ["is_invitation" ], ["true" ])
163+
164+ def test_generate_auth_url_invitation_code_overrides_false_is_invitation (self ):
165+ """invitation_code forces is_invitation='true' even when explicitly set to False."""
166+ result = run_async (self .oauth .generate_auth_url (
167+ login_options = {
168+ LoginOptions .INVITATION_CODE : "abc123" ,
169+ LoginOptions .IS_INVITATION : False ,
170+ }
171+ ))
172+ params = parse_qs (urlparse (result ["url" ]).query )
173+ self .assertEqual (params ["is_invitation" ], ["true" ])
174+
175+ def test_generate_auth_url_is_invitation_alone (self ):
176+ """is_invitation=True works independently of invitation_code."""
177+ result = run_async (self .oauth .generate_auth_url (
178+ login_options = {LoginOptions .IS_INVITATION : True }
179+ ))
180+ params = parse_qs (urlparse (result ["url" ]).query )
181+ self .assertEqual (params ["is_invitation" ], ["true" ])
182+ self .assertNotIn ("invitation_code" , params )
183+
184+ def test_generate_auth_url_no_invitation_params_by_default (self ):
185+ """Neither invitation_code nor is_invitation appear when not specified."""
186+ result = run_async (self .oauth .generate_auth_url (login_options = {}))
187+ params = parse_qs (urlparse (result ["url" ]).query )
188+ self .assertNotIn ("invitation_code" , params )
189+ self .assertNotIn ("is_invitation" , params )
190+
191+ def test_generate_auth_url_empty_invitation_code_ignored (self ):
192+ """An empty invitation_code does not add is_invitation."""
193+ result = run_async (self .oauth .generate_auth_url (
194+ login_options = {LoginOptions .INVITATION_CODE : "" }
195+ ))
196+ params = parse_qs (urlparse (result ["url" ]).query )
197+ self .assertNotIn ("is_invitation" , params )
198+
199+ def test_generate_auth_url_none_invitation_code_ignored (self ):
200+ """invitation_code=None is ignored entirely."""
201+ result = run_async (self .oauth .generate_auth_url (
202+ login_options = {LoginOptions .INVITATION_CODE : None }
203+ ))
204+ params = parse_qs (urlparse (result ["url" ]).query )
205+ self .assertNotIn ("invitation_code" , params )
206+ self .assertNotIn ("is_invitation" , params )
207+
208+ def test_login_passes_invitation_code (self ):
209+ """login() forwards invitation_code to the generated auth URL."""
210+ url = run_async (self .oauth .login (
211+ login_options = {LoginOptions .INVITATION_CODE : "inv_xyz" }
212+ ))
213+ params = parse_qs (urlparse (url ).query )
214+ self .assertEqual (params ["invitation_code" ], ["inv_xyz" ])
215+ self .assertEqual (params ["is_invitation" ], ["true" ])
216+
217+ def test_login_without_invitation_code (self ):
218+ """login() without invitation options produces no invitation params."""
219+ url = run_async (self .oauth .login ())
220+ params = parse_qs (urlparse (url ).query )
221+ self .assertNotIn ("invitation_code" , params )
222+ self .assertNotIn ("is_invitation" , params )
223+
224+ def test_invitation_code_coexists_with_org_code (self ):
225+ """invitation_code and org_code can be used together."""
226+ result = run_async (self .oauth .generate_auth_url (
227+ login_options = {
228+ LoginOptions .INVITATION_CODE : "abc123" ,
229+ LoginOptions .ORG_CODE : "org_456" ,
230+ }
231+ ))
232+ params = parse_qs (urlparse (result ["url" ]).query )
233+ self .assertEqual (params ["invitation_code" ], ["abc123" ])
234+ self .assertEqual (params ["is_invitation" ], ["true" ])
235+ self .assertEqual (params ["org_code" ], ["org_456" ])
236+
237+
134238class TestOAuthMethodSignatures (unittest .TestCase ):
135239 """Test OAuth method signatures to verify the fix for incorrect request parameter passing."""
136240
0 commit comments