From 4ea7d98e2eabdcd3d049efe751353e4c5801247b Mon Sep 17 00:00:00 2001 From: =?utf8?q?Mat=C3=ADas=20Aguirre?= Date: Sun, 27 Feb 2011 14:24:34 -0200 Subject: [PATCH] Initial testing suite. Only twitter so far, more to come soon. Refs gh-25 --- social_auth/tests/__init__.py | 1 + social_auth/tests/base.py | 83 +++++++++++++++++++++ social_auth/tests/twitter.py | 131 ++++++++++++++++++++++++++++++++++ 3 files changed, 215 insertions(+) create mode 100644 social_auth/tests/__init__.py create mode 100644 social_auth/tests/base.py create mode 100644 social_auth/tests/twitter.py diff --git a/social_auth/tests/__init__.py b/social_auth/tests/__init__.py new file mode 100644 index 0000000..196a8ca --- /dev/null +++ b/social_auth/tests/__init__.py @@ -0,0 +1 @@ +from social_auth.tests.twitter import * diff --git a/social_auth/tests/base.py b/social_auth/tests/base.py new file mode 100644 index 0000000..3bd40a6 --- /dev/null +++ b/social_auth/tests/base.py @@ -0,0 +1,83 @@ +import urllib +import urlparse +import unittest +from sgmllib import SGMLParser + +from django.test.client import Client +from django.core.urlresolvers import reverse + + +class SocialAuthTestsCase(unittest.TestCase): + """Base class for social auth tests""" + def __init__(self, *args, **kwargs): + self.client = Client() + super(SocialAuthTestsCase, self).__init__(*args, **kwargs) + + def get_content(self, url, data=None): + """Return content for given url, if data is not None, then a POST + request will be issued, otherwise GET will be used""" + data = data and urllib.urlencode(data) or data + return ''.join(urllib.urlopen(url, data=data).readlines()) + + def reverse(self, name, backend): + """Reverses backend URL by name""" + return reverse(name, args=(backend,)) + + def make_relative(self, value): + """Converst URL to relative, useful for server responses""" + parsed = urlparse.urlparse(value) + return urlparse.urlunparse(('', '', parsed.path, parsed.params, + parsed.query, parsed.fragment)) + + +class CustomParser(SGMLParser): + """Custom SGMLParser that closes the parser once it's fed""" + def feed(self, data): + SGMLParser.feed(self, data) + self.close() + + +class FormParser(CustomParser): + """Form parser, load form data and action for given form identified + by its id""" + def __init__(self, form_id, *args, **kwargs): + CustomParser.__init__(self, *args, **kwargs) + self.form_id = form_id + self.inside_form = False + self.action = None + self.values = {} + + def start_form(self, attributes): + """Start form parsing detecting if form is the one requested""" + attrs = dict(attributes) + if attrs.get('id') == self.form_id: + # flag that we are inside the form and save action + self.inside_form = True + self.action = attrs.get('action') + + def end_form(self): + """End form parsing, unset inside_form flag""" + self.inside_form = False + + def start_input(self, attributes): + """Parse input fields, we only keep data for fields of type text, + hidden or password and that has a valid name.""" + attrs = dict(attributes) + if self.inside_form: + type, name, value = attrs.get('type'), attrs.get('name'), \ + attrs.get('value') + if name and type in ('text', 'hidden', 'password'): + self.values[name] = value + + +class RefreshParser(CustomParser): + """Refresh parser, will check refresh by meta tag and store refresh URL""" + def __init__(self, *args, **kwargs): + CustomParser.__init__(self, *args, **kwargs) + self.value = None + + def start_meta(self, attributes): + """Start meta parsing checking by http-equiv attribute""" + attrs = dict(attributes) + if attrs.get('http-equiv') == 'refresh': + self.value = attrs.get('content').lstrip('0;url=') diff --git a/social_auth/tests/twitter.py b/social_auth/tests/twitter.py new file mode 100644 index 0000000..5e8986f --- /dev/null +++ b/social_auth/tests/twitter.py @@ -0,0 +1,131 @@ +import urllib +import urlparse +import unittest +from sgmllib import SGMLParser + +from django.conf import settings +from django.test.client import Client +from django.core.urlresolvers import reverse + +from social_auth.backends.twitter import TwitterAuth + + +SERVER_NAME = 'myapp.com' +SERVER_PORT = '8000' +USER = 'social_auth' +PASSWD = 'pass(twitter).7' + + +class SocialAuthTestsCase(unittest.TestCase): + def __init__(self, *args, **kwargs): + self.client = Client() + super(SocialAuthTestsCase, self).__init__(*args, **kwargs) + + def get_content(self, url, data=None): + """Open URL and return content""" + data = data and urllib.urlencode(data) or data + return ''.join(urllib.urlopen(url, data=data).readlines()) + + def reverse(self, name, backend): + return reverse(name, args=(backend,)) + + def make_relative(self, value): + parsed = urlparse.urlparse(value) + return urlparse.urlunparse(('', '', parsed.path, parsed.params, + parsed.query, parsed.fragment)) + + +class TwitterTestCase(SocialAuthTestsCase): + def testLogin(self): + response = self.client.get(self.reverse('begin', 'twitter')) + # social_auth must redirect to service page + self.assertEqual(response.status_code, 302) + + # Open first redirect page, it contains user login form because + # we don't have cookie to send to twitter + login_content = self.get_content(response['Location']) + parser = FormParser('login_form') + parser.feed(login_content) + auth = {'session[username_or_email]': USER, + 'session[password]': PASSWD} + + # Check that action and values were loaded properly + self.assertTrue(parser.action) + self.assertTrue(parser.values) + + # Post login form, will return authorization or redirect page + parser.values.update(auth) + content = self.get_content(parser.action, data=parser.values) + + # If page contains a form#login_form, then we are in the app + # authorization page because the app is not authorized yet, + # otherwise the app already gained permission and twitter sends + # a page that redirects to redirect_url + if 'login_form' in content: + # authorization form post, returns redirect_page + parser = FormParser('login_form').feed(content) + self.assertTrue(parser.action) + self.assertTrue(parser.values) + parser.values.update(auth) + redirect_page = self.get_content(parser.action, data=parser.values) + else: + redirect_page = content + + parser = RefreshParser() + parser.feed(redirect_page) + self.assertTrue(parser.value) + + response = self.client.get(self.make_relative(parser.value)) + self.assertEqual(response.status_code, 302) + location = self.make_relative(response['Location']) + login_redirect = getattr(settings, 'LOGIN_REDIRECT_URL', '') + self.assertTrue(location == login_redirect) + + def runTest(self): + # don't run tests if it's not enabled + if TwitterAuth.enabled: + self.testLogin() + + +class TwitterParser(SGMLParser): + def feed(self, data): + SGMLParser.feed(self, data) + self.close() + return self + + +class FormParser(TwitterParser): + def __init__(self, form_id, *args, **kwargs): + TwitterParser.__init__(self, *args, **kwargs) + self.form_id = form_id + self.inside_form = False + self.action = None + self.values = {} + + def start_form(self, attributes): + attrs = dict(attributes) + if attrs.get('id') == self.form_id: + self.inside_form = True + self.action = attrs.get('action') + + def end_form(self): + self.inside_form = False + + def start_input(self, attributes): + attrs = dict(attributes) + if self.inside_form: + type, name, value = attrs.get('type'), attrs.get('name'), \ + attrs.get('value') + if name and value and type in ('text', 'hidden', 'password'): + self.values[name] = value + + +class RefreshParser(TwitterParser): + def __init__(self, *args, **kwargs): + TwitterParser.__init__(self, *args, **kwargs) + self.value = None + + def start_meta(self, attributes): + attrs = dict(attributes) + if attrs.get('http-equiv') == 'refresh': + self.value = attrs.get('content').lstrip('0;url=') -- 2.39.5