Skip to content

Commit

Permalink
Unit tests refactoring (remove code duplication for Flask and Django …
Browse files Browse the repository at this point in the history
…test cases)
  • Loading branch information
dmitry-viskov committed Mar 23, 2020
1 parent 85f6c13 commit 35a7371
Show file tree
Hide file tree
Showing 7 changed files with 195 additions and 486 deletions.
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ license_file = LICENSE

[pycodestyle]
max_line_length=120
exclude=.git,.idea,.tox,venv,env
exclude=.git,.idea,.tox,venv,venv3,env

[coverage:run]
branch = True
Expand Down
1 change: 0 additions & 1 deletion tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,3 @@
from .test_grades import TestGrades
from .test_names_roles import TestNamesRolesProvisioningService
from .test_resource_link import TestResourceLink
from .test_flask_resource_link import TestFlaskResourceLink
95 changes: 30 additions & 65 deletions tests/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,73 +6,35 @@
from unittest.mock import patch
except ImportError:
from mock import patch
try:
from urllib import quote
except ImportError:
from urllib.parse import quote
from .request import FakeRequest
from .response import FakeResponse
from .tool_config import get_test_tool_conf, TOOL_CONFIG
from .django_mixin import DjangoMixin
from .flask_mixin import FlaskMixin
from .tool_config import TOOL_CONFIG


class TestLinkBase(unittest.TestCase):
class TestLinkBase(DjangoMixin, FlaskMixin, unittest.TestCase):
iss = 'replace-me'
get_login_data = {}
post_login_data = {}

def _make_oidc_login(self, uuid_val=None, tool_conf_cls=None):
tool_conf = get_test_tool_conf(tool_conf_cls)
request = None
login_data = {}
if not uuid_val:
uuid_val = 'test-uuid-1234'

if self.get_login_data:
request = FakeRequest(get=self.get_login_data)
login_data = self.get_login_data.copy()
elif self.post_login_data:
request = FakeRequest(post=self.post_login_data)
login_data = self.post_login_data.copy()

with patch('django.shortcuts.redirect') as mock_redirect:
from pylti1p3.contrib.django import DjangoOIDCLogin
with patch.object(DjangoOIDCLogin, "_get_uuid", autospec=True) as get_uuid:
get_uuid.side_effect = lambda x: uuid_val # pylint: disable=unnecessary-lambda
oidc_login = DjangoOIDCLogin(request, tool_conf)
mock_redirect.side_effect = lambda x: FakeResponse(x) # pylint: disable=unnecessary-lambda
launch_url = 'http://lti.django.test/launch/'
response = oidc_login.redirect(launch_url)

# check cookie data
self.assertEqual(len(response.cookies), 1)
self.assertTrue(('lti1p3-state-' + uuid_val) in response.cookies)
self.assertEqual(response.cookies['lti1p3-state-' + uuid_val]['value'], 'state-' + uuid_val)

# check session data
self.assertEqual(len(request.session), 1)
self.assertEqual(request.session['lti1p3-nonce-' + uuid_val], True)

# check redirect_url
redirect_url = response.data
self.assertTrue(redirect_url.startswith(TOOL_CONFIG[login_data['iss']]['auth_login_url']))
url_params = redirect_url.split('?')[1].split('&')
self.assertTrue(('nonce=' + uuid_val) in url_params)
self.assertTrue(('state=state-' + uuid_val) in url_params)
self.assertTrue(('state=state-' + uuid_val) in url_params)
self.assertTrue('prompt=none' in url_params)
self.assertTrue('response_type=id_token' in url_params)
self.assertTrue(('client_id=' + TOOL_CONFIG[login_data['iss']]['client_id']) in url_params)
self.assertTrue(('login_hint=' + login_data['login_hint']) in url_params)
self.assertTrue(('lti_message_hint=' + login_data['lti_message_hint']) in url_params)
self.assertTrue('scope=openid' in url_params)
self.assertTrue('response_mode=form_post' in url_params)
self.assertTrue(('redirect_uri=' + quote(launch_url, '')) in url_params)

return tool_conf, request, response

def _launch(self, request, tool_conf, key_set_url_response=None, force_validation=False):
from pylti1p3.contrib.django import DjangoMessageLaunch
obj = DjangoMessageLaunch(request, tool_conf)
def _make_oidc_login(self, adapter=None, uuid_val=None, tool_conf_cls=None, secure=False):
if adapter == 'flask':
return self._make_flask_oidc_login(uuid_val, tool_conf_cls, secure)
else:
return self._make_django_oidc_login(uuid_val, tool_conf_cls)

def _get_request(self, login_request, login_response, request_is_secure=False, empty_session=False,
empty_cookies=False, post_data=None, adapter=None):
if adapter == 'flask':
return self._get_flask_request(login_request, login_response, request_is_secure, post_data,
empty_session, empty_cookies)
else:
return self._get_django_request(login_request, login_response, post_data, empty_session, empty_cookies)

def _launch(self, request, tool_conf, key_set_url_response=None, force_validation=False, adapter=None):
if adapter == 'flask':
obj = self._get_flask_launch_obj(request, tool_conf)
else:
obj = self._get_django_launch_obj(request, tool_conf)
obj.set_jwt_verify_options({
'verify_aud': False,
'verify_exp': False
Expand All @@ -87,11 +49,14 @@ def _launch(self, request, tool_conf, key_set_url_response=None, force_validatio
else:
return obj.get_launch_data()

def _launch_with_invalid_jwt_body(self, side_effect, request, tool_conf):
from pylti1p3.contrib.django import DjangoMessageLaunch
with patch.object(DjangoMessageLaunch, "_get_jwt_body", autospec=True) as get_jwt_body:
def _launch_with_invalid_jwt_body(self, side_effect, request, tool_conf, adapter=None):
if adapter == 'flask':
klass = self._get_flask_launch_cls()
else:
klass = self._get_django_launch_cls()
with patch.object(klass, "_get_jwt_body", autospec=True) as get_jwt_body:
get_jwt_body.side_effect = side_effect
return self._launch(request, tool_conf, force_validation=True)
return self._launch(request, tool_conf, force_validation=True, adapter=adapter)


class TestServicesBase(unittest.TestCase):
Expand Down
82 changes: 82 additions & 0 deletions tests/django_mixin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
try:
from unittest.mock import patch
except ImportError:
from mock import patch
try:
from urllib import quote
except ImportError:
from urllib.parse import quote
from .request import FakeRequest
from .response import FakeResponse
from .tool_config import get_test_tool_conf, TOOL_CONFIG


class DjangoMixin(object):

def _get_django_request(self, login_request, login_response, post_data=None,
empty_session=False, empty_cookies=False):
session = None if empty_session else login_request.session
cookies = None if empty_cookies else login_response.get_cookies_dict()
post_launch_data = post_data if post_data else self.post_launch_data
return FakeRequest(post=post_launch_data,
cookies=cookies,
session=session)

def _make_django_oidc_login(self, uuid_val=None, tool_conf_cls=None):
tool_conf = get_test_tool_conf(tool_conf_cls)
request = None
login_data = {}
if not uuid_val:
uuid_val = 'test-uuid-1234'

if self.get_login_data:
request = FakeRequest(get=self.get_login_data)
login_data = self.get_login_data.copy()
elif self.post_login_data:
request = FakeRequest(post=self.post_login_data)
login_data = self.post_login_data.copy()

with patch('django.shortcuts.redirect') as mock_redirect:
from pylti1p3.contrib.django import DjangoOIDCLogin
with patch.object(DjangoOIDCLogin, "_get_uuid", autospec=True) as get_uuid:
get_uuid.side_effect = lambda x: uuid_val # pylint: disable=unnecessary-lambda
oidc_login = DjangoOIDCLogin(request, tool_conf)
mock_redirect.side_effect = lambda x: FakeResponse(x) # pylint: disable=unnecessary-lambda
launch_url = 'http://lti.django.test/launch/'
response = oidc_login.redirect(launch_url)

# check cookie data
self.assertEqual(len(response.cookies), 1)
self.assertTrue(('lti1p3-state-' + uuid_val) in response.cookies)
self.assertEqual(response.cookies['lti1p3-state-' + uuid_val]['value'], 'state-' + uuid_val)

# check session data
self.assertEqual(len(request.session), 1)
self.assertEqual(request.session['lti1p3-nonce-' + uuid_val], True)

# check redirect_url
redirect_url = response.data
self.assertTrue(redirect_url.startswith(TOOL_CONFIG[login_data['iss']]['auth_login_url']))
url_params = redirect_url.split('?')[1].split('&')
self.assertTrue(('nonce=' + uuid_val) in url_params)
self.assertTrue(('state=state-' + uuid_val) in url_params)
self.assertTrue(('state=state-' + uuid_val) in url_params)
self.assertTrue('prompt=none' in url_params)
self.assertTrue('response_type=id_token' in url_params)
self.assertTrue(('client_id=' + TOOL_CONFIG[login_data['iss']]['client_id']) in url_params)
self.assertTrue(('login_hint=' + login_data['login_hint']) in url_params)
self.assertTrue(('lti_message_hint=' + login_data['lti_message_hint']) in url_params)
self.assertTrue('scope=openid' in url_params)
self.assertTrue('response_mode=form_post' in url_params)
self.assertTrue(('redirect_uri=' + quote(launch_url, '')) in url_params)

return tool_conf, request, response

def _get_django_launch_obj(self, request, tool_conf):
from pylti1p3.contrib.django import DjangoMessageLaunch
obj = DjangoMessageLaunch(request, tool_conf)
return obj

def _get_django_launch_cls(self):
from pylti1p3.contrib.django import DjangoMessageLaunch
return DjangoMessageLaunch
46 changes: 21 additions & 25 deletions tests/flask_base.py → tests/flask_mixin.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
import json
import unittest
import requests_mock

from pylti1p3.contrib.flask import FlaskRequest, FlaskCookieService, \
FlaskSessionService

Expand All @@ -17,10 +13,24 @@
from .tool_config import get_test_tool_conf, TOOL_CONFIG


class TestFlaskLinkBase(unittest.TestCase):
iss = 'replace-me'
class FlaskMixin(object):

def get_cookies_dict_from_response(self, response):
cookie_name, cookie_value = response.headers['Set-Cookie']\
.split(';')[0].split('=')
return {cookie_name: cookie_value}

def _make_oidc_login(self, secure, uuid_val=None, tool_conf_cls=None):
def _get_flask_request(self, login_request, login_response, request_is_secure=False, post_data=None,
empty_session=False, empty_cookies=False):
session = {} if empty_session else login_request.session
cookies = {} if empty_cookies else self.get_cookies_dict_from_response(login_response)
post_launch_data = post_data if post_data else self.post_launch_data
return FlaskRequest(request_data=post_launch_data,
cookies=cookies,
session=session,
request_is_secure=request_is_secure)

def _make_flask_oidc_login(self, uuid_val=None, tool_conf_cls=None, secure=None):
tool_conf = get_test_tool_conf(tool_conf_cls)
if not uuid_val:
uuid_val = 'test-uuid-1234'
Expand Down Expand Up @@ -91,27 +101,13 @@ def _make_oidc_login(self, secure, uuid_val=None, tool_conf_cls=None):

return tool_conf, request, response

def _launch(self, request, tool_conf, key_set_url_response=None, force_validation=False):
def _get_flask_launch_obj(self, request, tool_conf):
from pylti1p3.contrib.flask import FlaskMessageLaunch
obj = FlaskMessageLaunch(request, tool_conf,
cookie_service=FlaskCookieService(request),
session_service=FlaskSessionService(request))
obj.set_jwt_verify_options({
'verify_aud': False,
'verify_exp': False
})

with patch('socket.gethostbyname', return_value="127.0.0.1"):
with requests_mock.Mocker() as m:
key_set_url_text = key_set_url_response if key_set_url_response else json.dumps(self.jwt_canvas_keys)
m.get(TOOL_CONFIG[self.iss]['key_set_url'], text=key_set_url_text)
if force_validation:
return obj.validate()
else:
return obj.get_launch_data()
return obj

def _launch_with_invalid_jwt_body(self, side_effect, request, tool_conf):
def _get_flask_launch_cls(self):
from pylti1p3.contrib.flask import FlaskMessageLaunch
with patch.object(FlaskMessageLaunch, "_get_jwt_body", autospec=True) as get_jwt_body:
get_jwt_body.side_effect = side_effect
return self._launch(request, tool_conf, force_validation=True)
return FlaskMessageLaunch
Loading

0 comments on commit 35a7371

Please sign in to comment.