Skip to content

Commit e5733fb

Browse files
author
Koman Rudden
committed
fix: tests improved for flask & fastapi frameworks and oauth
1 parent f53b3a5 commit e5733fb

3 files changed

Lines changed: 233 additions & 0 deletions

File tree

testv2/testv2_auth/test_oauth.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
134238
class TestOAuthMethodSignatures(unittest.TestCase):
135239
"""Test OAuth method signatures to verify the fix for incorrect request parameter passing."""
136240

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import unittest
2+
from unittest.mock import AsyncMock, MagicMock, patch
3+
4+
from fastapi import FastAPI
5+
from starlette.testclient import TestClient
6+
from starlette.middleware.sessions import SessionMiddleware
7+
8+
from kinde_fastapi.framework.fastapi_framework import FastAPIFramework
9+
10+
11+
class TestFastAPIFramework(unittest.TestCase):
12+
"""Tests for the FastAPI framework implementation."""
13+
14+
def setUp(self):
15+
self.app = FastAPI()
16+
self.app.add_middleware(SessionMiddleware, secret_key="test-secret")
17+
18+
self.framework = FastAPIFramework(app=self.app)
19+
20+
# Mock OAuth so no real auth happens
21+
self.mock_oauth = MagicMock()
22+
self.mock_oauth.login = AsyncMock(return_value="https://kinde.example.com/authorize")
23+
self.framework._oauth = self.mock_oauth
24+
25+
# Register routes
26+
self.framework._register_kinde_routes()
27+
28+
self.client = TestClient(self.app, follow_redirects=False)
29+
30+
def test_login_with_invitation_code(self):
31+
"""invitation_code query param is forwarded to oauth.login()."""
32+
resp = self.client.get("/login?invitation_code=inv_abc123")
33+
34+
self.mock_oauth.login.assert_called_once()
35+
login_options = self.mock_oauth.login.call_args[0][0]
36+
self.assertEqual(login_options["invitation_code"], "inv_abc123")
37+
38+
def test_login_without_invitation_code(self):
39+
"""No invitation_code means oauth.login() gets an empty dict."""
40+
resp = self.client.get("/login")
41+
42+
self.mock_oauth.login.assert_called_once()
43+
login_options = self.mock_oauth.login.call_args[0][0]
44+
self.assertNotIn("invitation_code", login_options)
45+
46+
def test_login_with_empty_invitation_code(self):
47+
"""An empty invitation_code query param is not forwarded."""
48+
resp = self.client.get("/login?invitation_code=")
49+
50+
self.mock_oauth.login.assert_called_once()
51+
login_options = self.mock_oauth.login.call_args[0][0]
52+
self.assertNotIn("invitation_code", login_options)
53+
54+
def test_login_redirects_to_oauth_url(self):
55+
"""The route returns a redirect to the URL from oauth.login()."""
56+
resp = self.client.get("/login?invitation_code=inv_xyz")
57+
58+
self.assertEqual(resp.status_code, 307)
59+
self.assertEqual(resp.headers["location"], "https://kinde.example.com/authorize")
60+
61+
62+
if __name__ == "__main__":
63+
unittest.main()
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import unittest
2+
from unittest.mock import AsyncMock, MagicMock, patch
3+
from flask import Flask
4+
5+
from kinde_flask.framework.flask_framework import FlaskFramework
6+
7+
8+
class TestFlaskFramework(unittest.TestCase):
9+
"""Tests for the Flask framework implementation."""
10+
11+
def setUp(self):
12+
self.app = Flask(__name__)
13+
self.app.config["SECRET_KEY"] = "test-secret"
14+
self.app.config["TESTING"] = True
15+
16+
self.framework = FlaskFramework(app=self.app)
17+
18+
# Mock OAuth so no real auth happens
19+
self.mock_oauth = MagicMock()
20+
self.mock_oauth.login = AsyncMock(return_value="https://kinde.example.com/authorize")
21+
self.framework._oauth = self.mock_oauth
22+
23+
# Register routes
24+
self.framework._initialized = False
25+
self.framework._register_kinde_routes()
26+
27+
self.client = self.app.test_client()
28+
29+
def test_login_with_invitation_code(self):
30+
"""invitation_code query param is forwarded to oauth.login()."""
31+
with self.app.test_request_context():
32+
resp = self.client.get("/login?invitation_code=inv_abc123")
33+
34+
self.mock_oauth.login.assert_called_once()
35+
login_options = self.mock_oauth.login.call_args[0][0]
36+
self.assertEqual(login_options["invitation_code"], "inv_abc123")
37+
38+
def test_login_without_invitation_code(self):
39+
"""No invitation_code means oauth.login() gets an empty dict."""
40+
with self.app.test_request_context():
41+
resp = self.client.get("/login")
42+
43+
self.mock_oauth.login.assert_called_once()
44+
login_options = self.mock_oauth.login.call_args[0][0]
45+
self.assertNotIn("invitation_code", login_options)
46+
47+
def test_login_with_empty_invitation_code(self):
48+
"""An empty invitation_code query param is not forwarded."""
49+
with self.app.test_request_context():
50+
resp = self.client.get("/login?invitation_code=")
51+
52+
self.mock_oauth.login.assert_called_once()
53+
login_options = self.mock_oauth.login.call_args[0][0]
54+
self.assertNotIn("invitation_code", login_options)
55+
56+
def test_login_redirects_to_oauth_url(self):
57+
"""The route returns a redirect to the URL from oauth.login()."""
58+
with self.app.test_request_context():
59+
resp = self.client.get("/login?invitation_code=inv_xyz")
60+
61+
self.assertEqual(resp.status_code, 302)
62+
self.assertEqual(resp.headers["Location"], "https://kinde.example.com/authorize")
63+
64+
65+
if __name__ == "__main__":
66+
unittest.main()

0 commit comments

Comments
 (0)