diff --git a/.travis.yml b/.travis.yml index 35c71ff9..7d14bcfb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,23 +1,13 @@ -language: objective-c +sudo: required +language: + - 3.6 addons: hosts: - authomatic.test - authomatic.com - authomatic.org -install: sudo pip install tox -script: sudo tox -- -vvx --tb=no +install: pip install tox +script: tox -e py36 -- -vvx --tb=no after_failure: - tail -n 1000000 tests/functional_tests/*.log - tail -n 1000000 tests/*.log -before_install: -- openssl aes-256-cbc -K $encrypted_cd197f1c301c_key -iv $encrypted_cd197f1c301c_iv - -in ./tests/functional_tests/config.py.enc -out ./tests/functional_tests/config.py - -d -- brew update -- brew install caskroom/cask/brew-cask -- brew cask install google-chrome -- brew install python3 -- wget -N http://chromedriver.storage.googleapis.com/2.10/chromedriver_mac32.zip -P - . -- unzip ./chromedriver_mac32.zip -d . -- chmod +x ./chromedriver diff --git a/CHANGES.rst b/CHANGES.rst index 43a30629..47ff6eb3 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,7 +1,10 @@ Unreleased ---------- -* Add :attr: email to :class: `oauth1.Twitter` provider. +* Removed support for :attr:`.User.country` and :attr:`.User.city` from + :class: `oauth2.Yammer` provider. +* Added support for :attr:`.User.city` to :class:`oauth2.LinkedIn` provider. +* Fixed the API urls of the :class:`oauth1.Plurk` provider. * Added support for :attr:`.User.country` and :attr:`.User.city` to :class: `oauth1.Twitter` provider. * Fix Twitter user info endpoint to include image url. @@ -11,12 +14,14 @@ Unreleased instead of the deprecated ``https://www.googleapis.com/plus/v1/people/me`` * Added support for :attr:`email_verified` and :attr:`hosted_domain` to :class:`.oauth2.Google` provider. -* Removed support for :attr:`gender`, :attr:`link` and :attr:`birth_date` +* Removed support for :attr:`gender` and :attr:`birth_date` from :class:`.oauth2.Google` provider. * Fix #160: Handle token_type of bearer (lower-case). * Fix #130: explicitly request fields from Facebook. * Adjusted naming of default scope for :class:`.oauth2.Facebook` to Facebook v2 API * Added support for :attr:`.User.email` to the :class:`.oauth1.Bitbucket` provider. +* Removed support for :attr:`.User.birth_date` and :attr:`.User.gender` + from the :class:`.oauth1.Yahoo` provider. Version 0.1.0 ------------- diff --git a/authomatic/exceptions.py b/authomatic/exceptions.py index 03561ccd..d4e94897 100644 --- a/authomatic/exceptions.py +++ b/authomatic/exceptions.py @@ -13,7 +13,8 @@ def __init__(self, message, original_message='', url='', status=None): #: Error message. self.message = message - + + # TODO: Refactor to original error #: Original message. self.original_message = original_message diff --git a/authomatic/providers/__init__.py b/authomatic/providers/__init__.py index 96a88a34..d77cfb08 100644 --- a/authomatic/providers/__init__.py +++ b/authomatic/providers/__init__.py @@ -20,15 +20,16 @@ """ import abc -import authomatic.core import base64 import hashlib import logging import random +import ssl import sys import traceback import uuid +import authomatic.core from authomatic.core import Session from authomatic.exceptions import ( ConfigError, @@ -123,6 +124,10 @@ class BaseProvider(object): """ PROVIDER_TYPE_ID = 0 + KNOWN_SSL_ERROR_SUFFIXES = ( + 'handshake failure', + 'violation of protocol', + ) _repr_ignore = ('user',) @@ -392,8 +397,25 @@ def _fetch(self, url, method='GET', params=None, headers=None, body='', max_redi try: connection.request(method, request_path, body, headers) except Exception as e: - raise FetchError('Could not connect!', - original_message=e.message, + message = 'Could not connect: {0}'.format(e) + major, minor, _, _, _ = sys.version_info + python_version = '{0}.{1}'.format(major, minor) + is_python26 = python_version == '2.6' + is_ssl = isinstance(e, ssl.SSLError) + is_known_ssl_error = str(e).endswith(self.KNOWN_SSL_ERROR_SUFFIXES) + + if is_python26 and is_ssl and is_known_ssl_error: + message = ( + '{message}. The server {host} probably uses TLS 1.2 ' + 'which is not supported by Python {version}.' + .format( + message=message, + host=host, + version=python_version, + )) + + original_message = e.message if hasattr(e, 'message') else str(e) + raise FetchError(message, original_message=original_message, url=request_path) response = connection.getresponse() @@ -453,7 +475,7 @@ def _update_or_create_user(self, data, credentials=None, content=None): # Extract every data item whose key matches the user property name, # but only if it has a value. value = data.get(key) - if value: + if value is not None: setattr(self.user, key, value) # Handle different structure of data by different providers. diff --git a/authomatic/providers/oauth1.py b/authomatic/providers/oauth1.py index e03b28be..8796557c 100644 --- a/authomatic/providers/oauth1.py +++ b/authomatic/providers/oauth1.py @@ -755,10 +755,10 @@ class Plurk(OAuth1): ) - request_token_url = 'http://www.plurk.com/OAuth/request_token' - user_authorization_url = 'http://www.plurk.com/OAuth/authorize' - access_token_url = 'http://www.plurk.com/OAuth/access_token' - user_info_url = 'http://www.plurk.com/APP/Profile/getOwnProfile' + request_token_url = 'https://www.plurk.com/OAuth/request_token' + user_authorization_url = 'https://www.plurk.com/OAuth/authorize' + access_token_url = 'https://www.plurk.com/OAuth/access_token' + user_info_url = 'https://www.plurk.com/APP/Profile/getOwnProfile' @staticmethod @@ -772,11 +772,11 @@ def _x_user_parser(user, data): user.locale = _user.get('default_lang') user.name = _user.get('full_name') user.nickname = _user.get('nick_name') - user.picture = 'http://avatars.plurk.com/{0}-big2.jpg'.format(user.id) + user.picture = 'https://avatars.plurk.com/{0}-big2.jpg'.format(user.id) user.timezone = _user.get('timezone') user.username = _user.get('display_name') - user.link = 'http://www.plurk.com/{0}/'.format(user.username) + user.link = 'https://www.plurk.com/{0}/'.format(user.username) user.city, user.country = _user.get('location', ',').split(',') user.city = user.city.strip() @@ -809,7 +809,6 @@ class Twitter(OAuth1): Supported :class:`.User` properties: - * email * city * country * id @@ -824,6 +823,7 @@ class Twitter(OAuth1): * birth_date * gender + * email * first_name * last_name * nickname @@ -837,7 +837,6 @@ class Twitter(OAuth1): city=True, country=True, id=True, - email=True, link=True, locale=True, location=True, @@ -967,6 +966,13 @@ class Vimeo(OAuth1): * Docs: https://developer.vimeo.com/apis/advanced#oauth-endpoints * API reference: https://developer.vimeo.com/apis + .. note:: + + Vimeo seems to be using TLS 1.2 for SSL connection, which is not + supported by Python 2.6. For more info read `this stackoverflow question + `__. + Supported :class:`.User` properties: * id @@ -1105,10 +1111,8 @@ class Yahoo(OAuth1): Supported :class:`.User` properties: - * birth_date * city * country - * gender * id * link * location @@ -1118,6 +1122,8 @@ class Yahoo(OAuth1): Unsupported :class:`.User` properties: + * birth_date + * gender * locale * phone * postal_code @@ -1127,10 +1133,8 @@ class Yahoo(OAuth1): """ supported_user_attributes = core.SupportedUserAttributes( - birth_date=True, city=True, country=True, - gender=True, id=True, link=True, location=True, @@ -1177,16 +1181,6 @@ def _x_user_parser(user, data): user.city = None user.country = None - _date = _user.get('birthdate') - _year = _user.get('birthYear') - - if _date and _year: - _full = _date + '/' + _year - try: - user.birth_date = datetime.datetime.strptime(_full, "%m/%d/%Y") - except: - user.birth_date = _full - return user diff --git a/authomatic/providers/oauth2.py b/authomatic/providers/oauth2.py index 11112c9c..c42afac3 100644 --- a/authomatic/providers/oauth2.py +++ b/authomatic/providers/oauth2.py @@ -356,13 +356,15 @@ def login(self): self.credentials.token = authorization_code - request_elements = self.create_request_elements(request_type=self.ACCESS_TOKEN_REQUEST_TYPE, - credentials=self.credentials, - url=self.access_token_url, - method=self.token_request_method, - redirect_uri=self.url, - params=self.access_token_params, - headers=self.access_token_headers) + request_elements = self.create_request_elements( + request_type=self.ACCESS_TOKEN_REQUEST_TYPE, + credentials=self.credentials, + url=self.access_token_url, + method=self.token_request_method, + redirect_uri=self.url, + params=self.access_token_params, + headers=self.access_token_headers + ) response = self._fetch(*request_elements) self.access_token_response = response @@ -371,11 +373,20 @@ def login(self): refresh_token = response.data.get('refresh_token', '') if response.status != 200 or not access_token: - raise FailureError('Failed to obtain OAuth 2.0 access token from {0}! HTTP status: {1}, message: {2}.'\ - .format(self.access_token_url, response.status, response.content), - original_message=response.content, - status=response.status, - url=self.access_token_url) + message = (u'Failed to obtain OAuth 2.0 access token from {0}! ' + u'HTTP status: {1}, message: {2}.' + .format(self.access_token_url, response.status, + response.content)) + + # TODO: Check whether provider requires the User-Agent header + # if so, use more meaningful message. + + raise FailureError( + message=message, + original_message=response.content, + status=response.status, + url=self.access_token_url, + ) self._log(logging.INFO, u'Got access token.') @@ -662,6 +673,32 @@ class DeviantART(OAuth2): * Docs: https://www.deviantart.com/developers/authentication * API reference: http://www.deviantart.com/developers/oauth2 + .. note:: + + DeviantART seems to be using TLS 1.2 for SSL connection, which is not + supported by Python 2.6. For more info read `this stackoverflow question + `__. + + .. note:: + + Although it is not documented anywhere, DeviantART requires the + *access token* request to contain a ``User-Agent`` header. + You can apply a default ``User-Agent`` header for all API calls in the + config like this: + + .. code-block:: python + :emphasize-lines: 6 + + CONFIG = { + 'deviantart': { + 'class_': oauth2.DeviantART, + 'consumer_key': '#####', + 'consumer_secret': '#####', + 'access_headers': {'User-Agent': 'Some User Agent'}, + } + } + Supported :class:`.User` properties: * name @@ -687,8 +724,8 @@ class DeviantART(OAuth2): """ - user_authorization_url = 'https://www.deviantart.com/oauth2/draft15/authorize' - access_token_url = 'https://www.deviantart.com/oauth2/draft15/token' + user_authorization_url = 'https://www.deviantart.com/oauth2/authorize' + access_token_url = 'https://www.deviantart.com/oauth2/token' user_info_url = 'https://www.deviantart.com/api/oauth2/user/whoami' user_info_scope = ['basic'] @@ -720,8 +757,8 @@ class Eventbrite(OAuth2): Thanks to `Paul Brown `__. * Dashboard: http://www.eventbrite.com/myaccount/apps/ - * Docs: https://developer.eventbrite.com/docs/auth/ - * API: http://developer.eventbrite.com/docs/ + * Docs: https://www.eventbrite.com/developer/v3/api_overview/authentication/ + * API: https://www.eventbrite.com/developer/v3/ Supported :class:`.User` properties: @@ -759,6 +796,14 @@ class Eventbrite(OAuth2): last_name=True, name=True, ) + + @staticmethod + def csrf_generator(secret): + # Eventbrite throws the "The application you're trying to use + # has made an invalid request to Eventbrite" error if the + # "state" request parameter is longer than 64 characters. + # Thus we need to truncate the CSRF token to a maximum of 14 characters. + return super(Eventbrite, Eventbrite).csrf_generator(secret)[:14] @classmethod def _x_credentials_parser(cls, credentials, data): @@ -884,11 +929,13 @@ def _x_user_parser(user, data): if len(split_location) > 1: user.country = split_location[1].strip() + user.link = u'https://www.facebook.com/{0}'.format(user.id) + return user - @staticmethod - def _x_credentials_parser(credentials, data): + @classmethod + def _x_credentials_parser(cls, credentials, data): """ We need to override this method to fix Facebooks naming deviation. """ @@ -1169,12 +1216,14 @@ class Google(OAuth2): 'email'] supported_user_attributes = core.SupportedUserAttributes( - id=True, email=True, - name=True, first_name=True, + gender=True, + id=True, last_name=True, + link=True, locale=True, + name=True, picture=True ) @@ -1218,6 +1267,7 @@ def _x_user_parser(user, data): user.name = data.get('name') user.first_name = data.get('given_name', '') user.last_name = data.get('family_name', '') + user.link = data.get('profile', '') user.locale = data.get('locale', '') user.picture = data.get('picture', '') @@ -1246,6 +1296,7 @@ class LinkedIn(OAuth2): Supported :class:`.User` properties: + * city * country * email * first_name @@ -1259,7 +1310,6 @@ class LinkedIn(OAuth2): Unsupported :class:`.User` properties: * birth_date - * city * gender * locale * nickname @@ -1282,6 +1332,7 @@ class LinkedIn(OAuth2): # http://developer.linkedin.com/forum/unauthorized-invalid-or-expired-token-immediately-after-receiving-oauth2-token supported_user_attributes = core.SupportedUserAttributes( + city=True, country=True, email=True, first_name=True, @@ -1312,7 +1363,9 @@ def _x_user_parser(user, data): user.last_name = data.get('lastName') user.email = data.get('emailAddress') user.name = data.get('formattedName') - user.country = data.get('location', {}).get('name') + user.city, user.country = data.get('location', {})\ + .get('name', ',').split(',') + user.location = data.get('location', {}).get('country', {}).get('code') user.phone = data.get('phoneNumbers', {}).get('values', [{}])[0]\ .get('phoneNumber') @@ -1344,6 +1397,13 @@ class PayPal(OAuth2): It grants you an **access token** based on your **app's** key and secret instead. + .. note:: + + Paypal seems to be using TLS 1.2 for SSL connection, which is not + supported by Python 2.6. For more info read `this stackoverflow question + `__. + """ _x_use_authorization_header = True @@ -1399,6 +1459,13 @@ class Reddit(OAuth2): } } + .. note:: + + Reddit seems to be using TLS 1.2 for SSL connection, which is not + supported by Python 2.6. For more info read `this stackoverflow question + `__. + Supported :class:`.User` properties: * id @@ -1520,12 +1587,13 @@ class VK(OAuth2): VK.com |oauth2| provider. * Dashboard: http://vk.com/apps?act=manage - * Docs: http://vk.com/developers.php?oid=-17680044&p=Authorizing_Sites - * API reference: http://vk.com/developers.php?oid=-17680044&p=API_Method_Description + * Docs: https://vk.com/dev/authcode_flow_user + * API reference: https://vk.com/dev/methods .. note:: - VK uses a `bitmask scope `_! + VK uses a `bitmask scope + `_! Use it like this: .. code-block:: python @@ -1566,9 +1634,9 @@ class VK(OAuth2): * username """ - - user_authorization_url = 'http://api.vkontakte.ru/oauth/authorize' - access_token_url = 'https://api.vkontakte.ru/oauth/access_token' + + user_authorization_url = 'https://oauth.vk.com/authorize' + access_token_url = 'https://oauth.vk.com/access_token' user_info_url = 'https://api.vk.com/method/getProfiles?' + \ 'fields=uid,first_name,last_name,nickname,sex,bdate,city,country,timezone,photo_big' @@ -1697,8 +1765,6 @@ class Yammer(OAuth2): Supported :class:`.User` properties: * birth_date - * city - * country * email * first_name * id @@ -1714,6 +1780,8 @@ class Yammer(OAuth2): Unsupported :class:`.User` properties: + * city + * country * gender * nickname * postal_code @@ -1726,8 +1794,6 @@ class Yammer(OAuth2): supported_user_attributes = core.SupportedUserAttributes( birth_date=True, - city=True, - country=True, email=True, first_name=True, id=True, @@ -1744,7 +1810,6 @@ class Yammer(OAuth2): @classmethod def _x_credentials_parser(cls, credentials, data): - # import pdb; pdb.set_trace() credentials.token_type = cls.BEARER _access_token = data.get('access_token', {}) credentials.token = _access_token.get('token') @@ -1767,10 +1832,6 @@ def _x_user_parser(user, data): user.name = _user.get('full_name') user.link = _user.get('web_url') user.picture = _user.get('mugshot_url') - - user.city, user.country = _user.get('location', ',').split(',') - user.city = user.city.strip() - user.country = user.country.strip() user.locale = _user.get('web_preferences', {}).get('locale') # Contact diff --git a/doc/source/conf.py b/doc/source/conf.py index e1dcabeb..322069c2 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -124,7 +124,7 @@ # built documents. # # The short X.Y version. -version = '0.1.0' +version = '0.1.1' # The full version, including alpha/beta/rc tags. release = version diff --git a/doc/source/development.rst b/doc/source/development.rst index 7813a2cb..6742cee1 100644 --- a/doc/source/development.rst +++ b/doc/source/development.rst @@ -115,8 +115,7 @@ Testing Tests are written in `pytest `__ and `Tox `__ is used to run them against -**Python 2.6**, **Python 2.7** and **Python 3.4**. - +**Python 2.7** and **Python 3.6**. There are currently only *functional* (*end-to-end*) `Selenium `__ tests. diff --git a/requirements.txt b/requirements.txt index 6cd4f61f..053148f8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1 @@ -pip>=6.0.8 tox diff --git a/setup.py b/setup.py index f55f41c6..d7b992a5 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name='Authomatic', - version='0.1.0.post1', # TODO: Put version in one place. + version='0.1.1', # TODO: Put version in one place. packages=find_packages(), package_data={'': ['*.txt', '*.rst']}, author='Peter Hudec', diff --git a/tests/functional_tests/config-template.py b/tests/functional_tests/config-template.py index a9e6a87f..e268563d 100644 --- a/tests/functional_tests/config-template.py +++ b/tests/functional_tests/config-template.py @@ -61,12 +61,14 @@ def teardown(): 'tumblr', # 'ubuntuone', # UbuntuOne service is no longer available 'vimeo', - 'xero', + # Xero requires creation of a new trial project every month which makes + # the setup of the automated test too laborious to support it. + # 'xero', 'xing', 'yahoo', # OAuth 2.0 - 'amazon', + # 'amazon', # Asks for a captcha (cannot be automated) # 'behance', # doesn't support third party authorization anymore. 'bitly', 'deviantart', @@ -74,7 +76,7 @@ def teardown(): 'foursquare', 'google', 'github', - 'linkedin', + 'linkedin', # Asks for verification when running in Travis CI evnironment 'paypal', 'reddit', 'vk', @@ -83,29 +85,37 @@ def teardown(): 'yandex', # OpenID - 'openid_livejournal', - 'openid_verisignlabs', + # 'openid_livejournal', # Login and password elements are not visible. 'openid_wordpress', 'openid_yahoo', ] +# Exclude providers who use TLS 1.2 from Python 2.6 tests +if sys.version.startswith('2.6'): + EXCLUDED = EXCLUDED + [ + 'deviantart', + 'paypal', + 'reddit', + ] + +# There are some problems with OpenSSL on Python 3 on OSX +if sys.version.startswith('3') and sys.platform == 'darwin': + EXCLUDED = EXCLUDED + [ + 'paypal', + ] + # Recommended setup for Travis CI environment. if os.environ.get('TRAVIS'): - MAX_LOGIN_ATTEMPTS = 20 + MAX_LOGIN_ATTEMPTS = 10 WAIT_MULTIPLIER = 2 MIN_WAIT = 2 - - # LinkedIn and WindowsLive include a captcha in the login form - # if a user logs in from an unusual location. - INCLUDE_PROVIDERS = list(set(INCLUDE_PROVIDERS) - - set(['linkedin', 'windowslive'])) - - def get_browser(): - # Eventbrite has problems with the login form on Firefox - return webdriver.Chrome() - - def teardown(): - pass + EXCLUDED = EXCLUDED + [ + 'deviantart', + 'google', + 'linkedin', + 'paypal', + 'windowslive', + ] # Use these constants if you have the same user info by all tested providers. EMAIL = 'andy.pipkin@littlebritain.co.uk' @@ -115,8 +125,10 @@ def teardown(): USERNAME = 'andypipkin' USERNAME_REVERSE = 'pipkinandy' NICKNAME = 'Mr. Pipkin' -BIRTH_YEAR = '1979' -BIRTH_DATE = datetime.datetime(1979, 12, 31).strftime('%x') +BIRTH_YEAR = 1979 +BIRTH_MONTH = 11 +BIRTH_DAY = 18 +BIRTH_DATE = datetime.datetime(BIRTH_YEAR, BIRTH_MONTH, BIRTH_DAY) CITY = 'London' COUNTRY = 'Great Britain' COUNTRY_ISO2 = 'gb' @@ -131,6 +143,9 @@ def teardown(): COMMON = { # Could be same if the user sets it so 'user_birth_date': BIRTH_DATE, + 'user_birth_day': BIRTH_DAY, + 'user_birth_month': BIRTH_MONTH, + 'user_birth_year': BIRTH_YEAR, 'user_login': EMAIL, 'user_email': EMAIL, 'user_first_name': FIRST_NAME, @@ -201,6 +216,9 @@ def teardown(): 'consumer_secret': '##########', 'user_password': '##########', 'user_id': '??????????', + # Twitter considers selenium login attempts suspicious and occasionally + # asks a security challenge question. This will be used as the answer. + 'user_challenge_answer': '??????????', }, 'tumblr': { 'consumer_key': '##########', @@ -343,10 +361,6 @@ def teardown(): # user_username is used in the OpenID identifier 'user_password': '##########', }, - 'openid_verisignlabs': { - 'user_login': USERNAME, - 'user_password': '##########', - }, 'openid_yahoo': { 'user_id': 'https://me.yahoo.com/a/???', 'user_login': USERNAME, diff --git a/tests/functional_tests/config.py.enc b/tests/functional_tests/config.py.enc index 06a24712..531524c9 100644 Binary files a/tests/functional_tests/config.py.enc and b/tests/functional_tests/config.py.enc differ diff --git a/tests/functional_tests/expected_values/bitbucket.py b/tests/functional_tests/expected_values/bitbucket.py index cba9e135..71a2486e 100644 --- a/tests/functional_tests/expected_values/bitbucket.py +++ b/tests/functional_tests/expected_values/bitbucket.py @@ -11,17 +11,15 @@ CONFIG = { 'logout_url': 'https://bitbucket.org/account/signout/', - 'login_xpath': '//*[@id="id_username"]', - 'password_xpath': '//*[@id="id_password"]', - 'consent_xpaths': [ - '//*[@id="oauth-authorize"]/section/form/div/div/button', - ], + 'login_xpath': '//*[@id="js-email-field"]', + 'password_xpath': '//*[@id="js-password-field"]', + 'consent_xpaths': [], 'class_': oauth1.Bitbucket, 'user': { 'birth_date': None, 'city': None, 'country': None, - 'email': None, + 'email': conf.user_email, 'first_name': conf.user_first_name, 'gender': None, 'id': conf.user_id, diff --git a/tests/functional_tests/expected_values/deviantart.py b/tests/functional_tests/expected_values/deviantart.py index c2fa1884..2421dcda 100644 --- a/tests/functional_tests/expected_values/deviantart.py +++ b/tests/functional_tests/expected_values/deviantart.py @@ -19,6 +19,9 @@ '//*[@id="authorize_form"]/fieldset/div[2]/div[2]/a[1]', ], # 'consent_wait_seconds': 2, + 'access_headers': { + 'User-Agent': 'Authomatic.py Automated Functional Tests', + }, 'class_': oauth2.DeviantART, 'scope': oauth2.DeviantART.user_info_scope, 'user': { diff --git a/tests/functional_tests/expected_values/facebook.py b/tests/functional_tests/expected_values/facebook.py index 6acf14f1..4fa9e224 100644 --- a/tests/functional_tests/expected_values/facebook.py +++ b/tests/functional_tests/expected_values/facebook.py @@ -7,7 +7,8 @@ conf = fixtures.get_configuration('facebook') -LINK = u'http://www.facebook.com/' + conf.user_id +# LINK = u'http://www.facebook.com/' + conf.user_id +LINK = u'https://www.facebook.com/{0}'.format(conf.user_id) PICTURE = (u'http://graph.facebook.com/{0}/picture?type=large' .format(conf.user_id)) @@ -37,7 +38,7 @@ 'phone': None, 'picture': PICTURE, 'postal_code': None, - 'timezone': re.compile(r'\d+'), + 'timezone': re.compile(r'\d'), 'username': None, }, 'content_should_contain': [ @@ -49,15 +50,13 @@ conf.user_gender, conf.user_id, conf.user_last_name, - LINK.replace('/', '\/'), conf.user_locale, conf.user_location, - conf.user_name, # User info JSON keys - 'bio', 'birthday', 'email', 'first_name', 'gender', 'id', 'last_name', - 'link', 'locale', 'location', 'name', 'timezone', 'updated_time', - 'verified' + 'birthday', 'data', 'email', 'first_name', 'gender', 'id', + 'is_silhouette', 'last_name', 'locale', 'location', 'name', 'picture', + 'timezone', 'url' ], # Case insensitive 'content_should_not_contain': @@ -65,9 +64,9 @@ conf.no_phone + conf.no_postal_code + conf.no_username, - # True means that any thruthy value is expected + # True means that any truthy value is expected 'credentials': { - 'token_type': None, + 'token_type': 'Bearer', 'provider_type_id': '2-5', '_expiration_time': True, 'consumer_key': None, diff --git a/tests/functional_tests/expected_values/flickr.py b/tests/functional_tests/expected_values/flickr.py index 784de863..8569a280 100644 --- a/tests/functional_tests/expected_values/flickr.py +++ b/tests/functional_tests/expected_values/flickr.py @@ -8,9 +8,11 @@ 'login_xpath': '//*[@id="login-username"]', 'password_xpath': '//*[@id="login-passwd"]', 'consent_xpaths': [ - '//*[@id="permissions"]/form/div/input[1]', + '//input[contains(@value, "OK")]', ], - 'consent_wait_seconds': 6, + 'enter_after_login_input': True, + 'before_password_input_wait': 1, + 'consent_wait_seconds': 1, 'logout_url': 'https://login.yahoo.com/config/login?logout=1', 'class_': oauth1.Flickr, 'user_authorization_params': dict(perms='read'), diff --git a/tests/functional_tests/expected_values/foursquare.py b/tests/functional_tests/expected_values/foursquare.py index c0deaa05..ca11cbce 100644 --- a/tests/functional_tests/expected_values/foursquare.py +++ b/tests/functional_tests/expected_values/foursquare.py @@ -47,23 +47,22 @@ conf.user_phone, # User info JSON keys - 'meta', 'code', 'notifications', 'type', 'item', 'unreadCount', - 'response', 'user', 'id', 'firstName', 'lastName', 'gender', - 'relationship', 'photo', 'prefix', 'suffix', 'friends', 'count', - 'groups', 'name', 'items', 'tips', 'lists', 'homeCity', 'bio', - 'contact', 'email', 'facebook', 'twitter', 'superuser', 'default', - 'birthday', 'phone', 'checkinPings', 'pings', 'badges', 'badgeId', - 'unlockMessage', 'description', 'badgeText', 'image', 'sizes', - 'unlocks', 'checkins', 'createdAt', 'shout', 'timeZoneOffset', - 'location', 'lat', 'lng', 'photos', 'posts', 'textCount', 'comments', - 'source', 'url', 'mayorships', 'venue', 'formattedPhone', - 'facebookName', 'address', 'postalCode', 'cc', 'neighborhood', 'city', - 'state', 'country', 'formattedAddress', 'categories', 'pluralName', - 'shortName', 'icon', 'primary', 'verified', 'stats', 'checkinsCount', - 'usersCount', 'tipCount', 'like', 'likes', 'width', 'height', - 'visibility', 'requests', 'editable', 'public', 'collaborative', - 'canonicalUrl', 'followers', 'listItems', 'checkin', 'blockedStatus', - 'referralId' + 'address', 'allowMenuUrlEdit', 'bio', 'birthday', 'blockedStatus', + 'canonicalUrl', 'categories', 'cc', 'checkin', 'checkinPings', + 'checkins', 'checkinsCount', 'city', 'code', 'collaborative', + 'comments', 'contact', 'count', 'country', 'createdAt', 'default', + 'description', 'editable', 'email', 'entities', 'facebook', + 'facebookName', 'firstName', 'formattedAddress', 'formattedPhone', + 'friends', 'gender', 'groups', 'height', 'homeCity', 'icon', 'id', + 'isMayor', 'item', 'items', 'lastName', 'lat', 'lenses', 'like', + 'likes', 'listItems', 'lists', 'lng', 'location', 'mayorships', 'meta', + 'name', 'neighborhood', 'notifications', 'phone', 'photo', 'photos', + 'pings', 'pluralName', 'postalCode', 'posts', 'prefix', 'primary', + 'public', 'referralId', 'relationship', 'requestId', 'requests', + 'response', 'shortName', 'shout', 'source', 'state', 'stats', 'suffix', + 'superuser', 'textCount', 'timeZoneOffset', 'tipCount', 'tips', + 'twitter', 'type', 'unreadCount', 'url', 'user', 'usersCount', 'venue', + 'verified', 'verifiedPhone', 'visibility', 'width' ], # Case insensitive 'content_should_not_contain': [ diff --git a/tests/functional_tests/expected_values/github.py b/tests/functional_tests/expected_values/github.py index a92cbc87..9d90a896 100644 --- a/tests/functional_tests/expected_values/github.py +++ b/tests/functional_tests/expected_values/github.py @@ -8,7 +8,7 @@ conf = fixtures.get_configuration('github') LINK = 'https://github.com/{0}'.format(conf.user_username) -PICTURE = re.compile(r'https://avatars\.githubusercontent.com/u/{0}\?v=\d' +PICTURE = re.compile(r'https://avatars\d*\.githubusercontent.com/u/{0}\?v=\d+' .format(conf.user_id)) CONFIG = { @@ -45,7 +45,8 @@ 'content_should_contain': [ conf.user_id, conf.user_name, conf.user_first_name, conf.user_last_name, - conf.user_city, conf.user_country, + conf.user_city, + conf.user_location, # User info JSON keys 'login', 'id', 'avatar_url', 'gravatar_id', 'url', 'html_url', diff --git a/tests/functional_tests/expected_values/google.py b/tests/functional_tests/expected_values/google.py index 5fb18725..28f5daf6 100644 --- a/tests/functional_tests/expected_values/google.py +++ b/tests/functional_tests/expected_values/google.py @@ -8,11 +8,12 @@ conf = fixtures.get_configuration('google') LINK = 'https://plus.google.com/' + conf.user_id +PICTURE = re.compile(r'^http.://.*(jpg|png|gif)') CONFIG = { 'logout_url': 'https://accounts.google.com/Logout', - 'login_xpath': '//*[@id="Email"]', - 'password_xpath': '//*[@id="Passwd"]', + 'login_xpath': '//*[@id="identifierId"]', + 'password_xpath': '//input[@type="password"]', 'enter_after_login_input': True, 'consent_xpaths': [ '//*[@id="submit_approve_access"]', @@ -41,7 +42,7 @@ 'locale': re.compile(r'^\w{2}$'), 'location': None, 'phone': None, - 'picture': re.compile(r'^https://\w+\.googleusercontent.com/-\w+/\w+/\w+/\w+/photo\.jpg\?sz=50$'), + 'picture': PICTURE, 'postal_code': None, 'timezone': None, }, @@ -53,11 +54,8 @@ LINK, # User info JSON keys - 'kind', 'etag', 'occupation', 'gender', 'emails', 'value', 'type', - 'urls', 'label', 'objectType', 'id', 'displayName', 'name', - 'familyName', 'givenName', 'aboutMe', 'url', 'image', 'organizations', - 'title', 'startDate', 'endDate', 'primary', 'placesLived', 'isPlusUser', - 'language', 'circledByCount', 'verified' + 'email', 'email_verified', 'family_name', 'gender', 'given_name', + 'locale', 'name', 'picture', 'profile', 'sub', ], # Case insensitive 'content_should_not_contain': [conf.user_postal_code] @@ -92,7 +90,7 @@ 'consumer_secret': True, 'token': False, 'token_secret': True, - '_expire_in': None, # Somtimes differ in one second. + '_expire_in': None, # Somtimes differ in one second. 'provider_name': True, 'refresh_token': True, 'provider_type': True, diff --git a/tests/functional_tests/expected_values/linkedin.py b/tests/functional_tests/expected_values/linkedin.py index daae7814..41db440d 100644 --- a/tests/functional_tests/expected_values/linkedin.py +++ b/tests/functional_tests/expected_values/linkedin.py @@ -16,14 +16,13 @@ 'scope': oauth2.LinkedIn.user_info_scope, 'user': { 'birth_date': None, - 'city': None, + 'city': conf.user_city, 'country': conf.user_country, 'email': conf.user_email, 'first_name': conf.user_first_name, 'gender': None, 'id': conf.user_id, 'last_name': conf.user_last_name, - # 'link': re.compile(r'^https://www\.linkedin\.com/in/\w+$'), 'link': re.compile(r'^https://www\.linkedin\.com/.*'), 'locale': None, 'location': re.compile(r'^\w{2}$'), @@ -36,6 +35,7 @@ 'username': None, }, 'content_should_contain': [ + conf.user_city, conf.user_country, conf.user_email, conf.user_first_name, @@ -50,7 +50,6 @@ # Case insensitive 'content_should_not_contain': conf.no_birth_date + - conf.no_city + conf.no_gender + conf.no_locale + conf.no_nickname + diff --git a/tests/functional_tests/expected_values/meetup.py b/tests/functional_tests/expected_values/meetup.py index 78c08a85..50c85c5a 100644 --- a/tests/functional_tests/expected_values/meetup.py +++ b/tests/functional_tests/expected_values/meetup.py @@ -7,7 +7,7 @@ conf = fixtures.get_configuration('meetup') LINK = 'http://www.meetup.com/members/{0}'.format(conf.user_id) -PICTURE = re.compile(r'http://photos\d+.meetupstatic.com/photos/member/' +PICTURE = re.compile(r'https://.*?.meetupstatic.com/photos/member/' r'\w/\d+/\w/\d+/member_\d+.jpeg') CONFIG = { diff --git a/tests/functional_tests/expected_values/openid_livejournal.py b/tests/functional_tests/expected_values/openid_livejournal.py index 2eb6ce0e..da222979 100644 --- a/tests/functional_tests/expected_values/openid_livejournal.py +++ b/tests/functional_tests/expected_values/openid_livejournal.py @@ -11,11 +11,11 @@ '//*[@id="js"]/body/div[7]/header/span', '//*[@id="js"]/body/div[4]/header/div/nav[1]/ul[2]/li[1]/a', ], - # 'login_url': 'http://www.livejournal.com/login.bml', + 'login_url': 'http://www.livejournal.com/login.bml', 'login_xpath': '//*[@id="user"]', 'password_xpath': '//*[@id="lj_loginwidget_password"]', 'consent_xpaths': [ - # '//*[@id="js"]/body/div[4]/div[2]/div/form/div[3]/div[2]/button', + '//*[@id="LJWidget_893"]/div/div/form[1]/div[3]/div[1]/div/button', '//*[@id="js"]/body/div[4]/div[2]/div/div/form/table/tbody/tr/td/input[1]', ], 'class_': openid.OpenID, diff --git a/tests/functional_tests/expected_values/openid_verisignlabs.py b/tests/functional_tests/expected_values/openid_verisignlabs.py deleted file mode 100644 index 69077f22..00000000 --- a/tests/functional_tests/expected_values/openid_verisignlabs.py +++ /dev/null @@ -1,40 +0,0 @@ -import re - -import fixtures -from authomatic.providers import openid - -conf = fixtures.get_configuration('openid_verisignlabs') - -OPENID_IDENTIFIER = 'http://{0}.pip.verisignlabs.com/'.format(conf.user_login) - -CONFIG = { - 'openid_identifier': OPENID_IDENTIFIER, - 'logout_url': 'https://pip.verisignlabs.com/logout.do', - 'login_xpath': '//*[@id="mainbody"]/form/table/tbody/tr[2]/td[2]/input', - 'password_xpath': '//*[@id="mainbody"]/form/table/tbody/tr[3]/td[2]/input', - 'consent_xpaths': [ - '//*[@id="TrustAuthenticateExchange"]/div[2]/input[2]', - ], - 'alert_wait_seconds': 1, - 'class_': openid.OpenID, - 'user': { - 'id': OPENID_IDENTIFIER, - 'email': conf.user_email, - 'username': None, - 'name': conf.user_name, - 'first_name': None, - 'last_name': None, - 'nickname': conf.user_nickname, - 'birth_date': conf.user_birth_date_str, - 'city': None, - 'country': conf.user_country, - 'gender': re.compile(r'^\w{1}$'), - 'link': None, - 'location': conf.user_country, - 'locale': re.compile(r'^\w+$'), - 'phone': None, - 'picture': None, - 'postal_code': conf.user_postal_code, - 'timezone': re.compile(r'^\w+/\w+$'), - }, -} \ No newline at end of file diff --git a/tests/functional_tests/expected_values/openid_yahoo.py b/tests/functional_tests/expected_values/openid_yahoo.py index 898566f3..72b1382a 100644 --- a/tests/functional_tests/expected_values/openid_yahoo.py +++ b/tests/functional_tests/expected_values/openid_yahoo.py @@ -7,11 +7,13 @@ 'openid_identifier': 'me.yahoo.com', 'login_xpath': '//*[@id="login-username"]', 'password_xpath': '//*[@id="login-passwd"]', + 'enter_after_login_input': True, + 'before_password_input_wait': 1, 'consent_xpaths': [ '//*[@id="login-signin"]', '//*[@id="agree"]', ], - 'after_consent_wait_seconds': 3, + 'after_consent_wait_seconds': 1, 'logout_url': 'https://login.yahoo.com/config/login?logout=1', 'class_': openid.OpenID, 'user': { diff --git a/tests/functional_tests/expected_values/plurk.py b/tests/functional_tests/expected_values/plurk.py index 954760a1..c25d06b1 100644 --- a/tests/functional_tests/expected_values/plurk.py +++ b/tests/functional_tests/expected_values/plurk.py @@ -7,8 +7,8 @@ conf = fixtures.get_configuration('plurk') -LINK = 'http://www.plurk.com/{0}/'.format(conf.user_username) -PICTURE = 'http://avatars.plurk.com/{0}-big2.jpg'.format(conf.user_id) +LINK = 'https://www.plurk.com/{0}/'.format(conf.user_username) +PICTURE = 'https://avatars.plurk.com/{0}-big2.jpg'.format(conf.user_id) CONFIG = { 'login_xpath': '//*[@id="input_nick_name"]', @@ -52,21 +52,23 @@ # User info JSON keys 'about', 'accept_private_plurk_from', 'alerts_count', 'anonymous', - 'avatar', 'avatar_big', 'avatar_medium', 'avatar_small', 'bday_privacy', - 'content', 'content_raw', 'date_of_birth', 'dateformat', 'default_lang', - 'display_name', 'email', 'email_confirmed', 'fans_count', 'favorers', - 'favorite', 'favorite_count', 'friends_count', 'full_name', 'gender', - 'has_profile_image', 'has_read_permission', 'id', 'is_unread', 'karma', - 'lang', 'limited_to', 'location', 'name_color', 'nick_name', + 'avatar', 'avatar_big', 'avatar_medium', 'avatar_small', + 'background_id', 'bday_privacy', 'coins', 'content', 'content_raw', + 'creature_url', 'date_of_birth', 'dateformat', 'default_lang', + 'display_name', 'email', 'email_confirmed', 'excluded', 'fans_count', + 'favorers', 'favorite', 'favorite_count', 'friends_count', 'full_name', + 'gender', 'has_gift', 'has_profile_image', 'has_read_permission', 'id', + 'is_unread', 'karma', 'lang', 'last_edited', 'limited_to', + 'link_facebook', 'location', 'mentioned', 'name_color', 'nick_name', 'no_comments', 'owner_id', 'page_title', 'plurk_id', 'plurk_type', 'plurks', 'plurks_count', 'plurks_users', 'porn', - 'post_anonymous_plurk', 'posted', 'privacy', 'profile_views', + 'post_anonymous_plurk', 'posted', 'premium', 'privacy', 'profile_views', 'qualifier', 'qualifier_translated', 'recruited', 'relationship', 'replurkable', 'replurked', 'replurker_id', 'replurkers', - 'replurkers_count', 'response_count', 'responses_seen', + 'replurkers_count', 'responded', 'response_count', 'responses_seen', 'setup_facebook_sync', 'setup_twitter_sync', 'setup_weibo_sync', - 'timezone', 'uid', 'unread_count', 'user_id', 'user_info', - 'verified_account' + 'show_ads', 'show_location', 'timeline_privacy', 'timezone', 'uid', + 'unread_count', 'user_id', 'user_info', 'verified_account' ], # Case insensitive 'content_should_not_contain': diff --git a/tests/functional_tests/expected_values/reddit.py b/tests/functional_tests/expected_values/reddit.py index d3f7069b..a405dbd8 100644 --- a/tests/functional_tests/expected_values/reddit.py +++ b/tests/functional_tests/expected_values/reddit.py @@ -8,7 +8,7 @@ 'login_xpath': '//*[@id="user_login"]', 'password_xpath': '//*[@id="passwd_login"]', 'consent_xpaths': [ - '/html/body/div[2]/div/div[2]/form/div/input[1]', + '/html/body/div[3]/div/div[2]/form/div/input[1]', ], 'consent_wait_seconds': 3, 'class_': oauth2.Reddit, diff --git a/tests/functional_tests/expected_values/tumblr.py b/tests/functional_tests/expected_values/tumblr.py index 24fb7df9..f18923df 100644 --- a/tests/functional_tests/expected_values/tumblr.py +++ b/tests/functional_tests/expected_values/tumblr.py @@ -9,11 +9,14 @@ CONFIG = { 'logout_url': 'https://www.tumblr.com/logout', - 'login_xpath': '//*[@id="signup_email"]', + 'login_xpath': '//*[@id="signup_determine_email"]', 'password_xpath': '//*[@id="signup_password"]', + 'enter_after_login_input': True, + 'before_password_input_wait': 1, 'consent_xpaths': [ - '//*[@id="api_v1_oauth_authorize"]' - '/div[2]/div/div[1]/div/div/div[2]/form/button[2]', + '//*[@id="signup_forms_submit"]', + ('//*[@id="api_v1_oauth_authorize"]' + '/div[2]/div/div[1]/div/div/div[2]/form/button[2]'), ], 'consent_wait_seconds': 3, 'class_': oauth1.Tumblr, diff --git a/tests/functional_tests/expected_values/twitter.py b/tests/functional_tests/expected_values/twitter.py index ddace4af..e46e662f 100644 --- a/tests/functional_tests/expected_values/twitter.py +++ b/tests/functional_tests/expected_values/twitter.py @@ -1,20 +1,39 @@ import re +from selenium.common.exceptions import NoSuchElementException +from selenium.webdriver.common.keys import Keys + import fixtures import constants from authomatic.providers import oauth1 conf = fixtures.get_configuration('twitter') + +def after_login_hook(browser, log): + try: + log(4, 'twitter', 'Finding challenge element') + challenge = browser.find_element_by_xpath('//*[@id="challenge_response"]') + + log(4, 'twitter', 'Answering challenge') + challenge.send_keys(conf.user_challenge_answer) + + log(4, 'twitter', 'Submitting challenge') + challenge.send_keys(Keys.ENTER) + except NoSuchElementException: + log(5, 'twitter', 'Challenge element not found, hopefully not needed') + + CONFIG = { 'login_xpath': '//*[@id="username_or_email"]', 'password_xpath': '//*[@id="password"]', 'consent_xpaths': [], + 'after_login_hook': after_login_hook, 'class_': oauth1.Twitter, 'user': { 'birth_date': None, - 'city': None, - 'country': None, + 'city': conf.user_city, + 'country': conf.user_country, 'email': None, 'gender': None, 'id': conf.user_id, diff --git a/tests/functional_tests/expected_values/vimeo.py b/tests/functional_tests/expected_values/vimeo.py index 740cbfcd..7c556938 100644 --- a/tests/functional_tests/expected_values/vimeo.py +++ b/tests/functional_tests/expected_values/vimeo.py @@ -5,16 +5,17 @@ from authomatic.providers import oauth1 conf = fixtures.get_configuration('vimeo') -LINK = 'http://vimeo.com/user{0}'.format(conf.user_id) -PICTURE = re.compile(r'http://\w+.vimeocdn.com/portrait/\d+_300x300.jpg') +LINK = 'https://vimeo.com/user{0}'.format(conf.user_id) +PICTURE = re.compile(r'http.?://.*?/portrait/\d+_300x300') CONFIG = { 'logout_url': 'https://vimeo.com/log_out', - 'login_xpath': '//*[@id="email"]', - 'password_xpath': '//*[@id="password"]', + 'login_xpath': '//*[@id="signup_email"]', + 'password_xpath': '//*[@id="login_password"]', 'consent_xpaths': [ '//*[@id="security"]/form/input[4]', + '//*[@id="main"]/article/div/div/div/form/input[4]', ], 'class_': oauth1.Vimeo, 'user': { diff --git a/tests/functional_tests/expected_values/vk.py b/tests/functional_tests/expected_values/vk.py index 571868dd..841d39ec 100644 --- a/tests/functional_tests/expected_values/vk.py +++ b/tests/functional_tests/expected_values/vk.py @@ -1,4 +1,3 @@ -import datetime import re import fixtures @@ -8,11 +7,11 @@ conf = fixtures.get_configuration('vk') -PICTURE = re.compile(r'http://[A-Za-z0-9]+\.vk\.me/[A-Za-z0-9-/]+\.jpg') +PICTURE = re.compile(r'http.?://.*?(jpg|png|gif)') CONFIG = { - 'login_xpath': '//*[@id="box"]/div/input[6]', - 'password_xpath': '//*[@id="box"]/div/input[7]', + 'login_xpath': '//input[@type="text"]', + 'password_xpath': '//input[@type="password"]', 'consent_xpaths': [ '//*[@id="install_allow"]', ], diff --git a/tests/functional_tests/expected_values/windowslive.py b/tests/functional_tests/expected_values/windowslive.py index b64d0b70..7c60beaf 100644 --- a/tests/functional_tests/expected_values/windowslive.py +++ b/tests/functional_tests/expected_values/windowslive.py @@ -1,5 +1,3 @@ -import datetime - import fixtures import constants from authomatic.providers import oauth2 @@ -10,12 +8,16 @@ PICTURE = 'https://apis.live.net/v5.0/{0}/picture'.format(conf.user_id) CONFIG = { - 'login_xpath': '//*[@id="i0116"]', - 'password_xpath': '//*[@id="i0118"]', + 'login_xpath': '//*[@id="CredentialsInputPane"]' + '/div[2]/div/div/div[3]/div/div/div[2]/div', + 'password_xpath': '//input[@type="password"]', 'consent_xpaths': [ + '//input[@type="submit"]', + '//*[@id="iNext"]', '//*[@id="idBtn_Accept"]', + '//*[@id="iLooksGood"]', ], - 'consent_wait_seconds': 0, + 'consent_wait_seconds': 1, 'class_': oauth2.WindowsLive, 'scope': oauth2.WindowsLive.user_info_scope, 'offline': True, diff --git a/tests/functional_tests/expected_values/xing.py b/tests/functional_tests/expected_values/xing.py index bcd26514..22bd2475 100644 --- a/tests/functional_tests/expected_values/xing.py +++ b/tests/functional_tests/expected_values/xing.py @@ -7,8 +7,11 @@ conf = fixtures.get_configuration('xing') LINK = 'https://www.xing.com/profile/{0}'.format(conf.user_username) -PITURE = re.compile(r'https://x\d+.xingassets.com/assets/frontend_minified/img/' - r'users/\w+.140x185.jpg') +# PITURE = re.compile(r'https://x\d+.xingassets.com/assets/frontend_minified/img/' +# r'users/\w+.140x185.jpg') + +PICTURE = re.compile(r'https://www.xing.com/assets/frontend_minified/' + r'img/users/\w+\.\d+x\d+.jpg') CONFIG = { @@ -35,7 +38,7 @@ 'name': conf.user_name, 'nickname': None, 'phone': conf.user_phone, - 'picture': PITURE, + 'picture': PICTURE, 'postal_code': conf.user_postal_code, 'timezone': re.compile(r'^\w+/\w+$'), 'username': conf.user_username, diff --git a/tests/functional_tests/expected_values/yahoo.py b/tests/functional_tests/expected_values/yahoo.py index a69d94f6..8875e0b3 100644 --- a/tests/functional_tests/expected_values/yahoo.py +++ b/tests/functional_tests/expected_values/yahoo.py @@ -7,24 +7,26 @@ conf = fixtures.get_configuration('yahoo') LINK = 'http://profile.yahoo.com/{0}'.format(conf.user_id) -PITURE = re.compile(r'https://\w+.yimg.com/dg/users/\w+==.large.png') +PITURE = re.compile(r'http.?://.*') CONFIG = { 'login_xpath': '//*[@id="login-username"]', 'password_xpath': '//*[@id="login-passwd"]', + 'enter_after_login_input': True, + 'before_password_input_wait': 1, 'consent_xpaths': [ '//*[@id="xagree"]', ], - 'consent_wait_seconds': 3, + 'consent_wait_seconds': 1, 'logout_url': 'https://login.yahoo.com/config/login?logout=1', 'class_': oauth1.Yahoo, 'user': { - 'birth_date': conf.user_birth_date_str, + 'birth_date': None, 'city': conf.user_city, 'country': conf.user_country, 'email': None, - 'gender': re.compile(r'^\w$'), + 'gender': None, 'id': conf.user_id, 'first_name': None, 'last_name': None, @@ -40,23 +42,23 @@ 'username': None, }, 'content_should_contain': [ - '"birthYear":"{0:%Y}",'.format(conf.user_birth_date), - '"birthdate":"{0:%m/%d}",'.format(conf.user_birth_date), conf.user_id, LINK, conf.user_location, conf.user_name, # User info JSON keys - 'aboutMe', 'ageCategory', 'birthYear', 'birthdate', 'count', 'created', - 'displayAge', 'gender', 'guid', 'height', 'image', 'imageUrl', - 'isConnected', 'lang', 'location', 'memberSince', 'nickname', 'profile', - 'profileUrl', 'query', 'results', 'size', 'width' + 'aboutMe', 'ageCategory', 'birthdate', 'count', 'created', 'guid', + 'height', 'image', 'imageUrl', 'isConnected', 'lang', 'location', + 'memberSince', 'nickname', 'profile', 'profileUrl', 'query', 'results', + 'size', 'width' ], # Case insensitive 'content_should_not_contain': ['city', 'country'] + + conf.no_birth_year + conf.no_email + + conf.no_gender + conf.no_locale + ['first_name', 'last_name', 'firstname', 'lastname'] + conf.no_phone + diff --git a/tests/functional_tests/expected_values/yammer.py b/tests/functional_tests/expected_values/yammer.py index 5ee20a1f..ab386736 100644 --- a/tests/functional_tests/expected_values/yammer.py +++ b/tests/functional_tests/expected_values/yammer.py @@ -8,7 +8,7 @@ conf = fixtures.get_configuration('yammer') LINK = 'https://www.yammer.com/peterhudec.com/users/{0}'\ - .format(conf.user_username) + .format(conf.user_id) # Yammer allows users to only set month and day of their birth day. # The year is always 1900. @@ -25,8 +25,8 @@ 'scope': oauth2.Yammer.user_info_scope, 'user': { 'birth_date': BIRTH_DATE, - 'city': conf.user_city, - 'country': conf.user_country, + 'city': None, + 'country': None, 'email': conf.user_email, 'first_name': conf.user_first_name, 'gender': None, @@ -61,28 +61,28 @@ # User info JSON keys 'absolute_timestamps', 'activated_at', 'address', 'admin', 'admin_can_delete_messages', 'allow_attachments', - 'allow_external_sharing', 'allow_inline_document_view', - 'allow_inline_video', 'allow_notes', 'allow_yammer_apps', 'birth_date', - 'can_broadcast', 'can_browse_external_networks', - 'can_create_new_network', 'contact', 'department', - 'dismissed_apps_tooltip', 'dismissed_browser_lifecycle_banner', - 'dismissed_feed_tooltip', 'dismissed_group_tooltip', - 'dismissed_invite_tooltip', 'dismissed_invite_tooltip_at', - 'dismissed_profile_prompt', 'email', 'email_addresses', 'enable_chat', - 'enable_groups', 'enable_private_messages', - 'enter_does_not_submit_message', 'expertise', 'external_urls', - 'first_name', 'follow_general_messages', 'followers', 'following', - 'full_name', 'guid', 'has_fake_email', 'has_mobile_client', + 'allow_external_sharing', 'allow_inline_document_view', 'allow_notes', + 'allow_yammer_apps', 'auto_activated', 'birth_date', 'can_broadcast', + 'can_browse_external_networks', 'can_create_new_network', 'contact', + 'department', 'dismissed_apps_tooltip', + 'dismissed_browser_lifecycle_banner', 'dismissed_feed_tooltip', + 'dismissed_group_tooltip', 'dismissed_invite_tooltip', + 'dismissed_invite_tooltip_at', 'dismissed_profile_prompt', 'email', + 'email_addresses', 'enable_chat', 'enable_groups', + 'enable_private_messages', 'enter_does_not_submit_message', 'expertise', + 'external_urls', 'first_name', 'follow_general_messages', 'followers', + 'following', 'full_name', 'guid', 'has_fake_email', 'has_mobile_client', 'has_yammer_now', 'id', 'im', 'interests', 'job_title', 'kids_names', 'last_name', 'locale', 'location', 'make_yammer_homepage', 'message_prompt', 'mugshot_url', 'mugshot_url_template', 'name', 'network_domains', 'network_id', 'network_name', 'network_settings', 'number', 'phone_numbers', 'preferred_my_feed', 'prescribed_my_feed', 'previous_companies', 'provider', 'schools', 'settings', - 'show_ask_for_photo', 'show_communities_directory', 'significant_other', - 'state', 'stats', 'sticky_my_feed', 'summary', 'threaded_mode', - 'timezone', 'type', 'updates', 'url', 'username', 'verified_admin', - 'web_preferences', 'web_url', 'xdr_proxy', 'yammer_now_app_id' + 'show_ask_for_photo', 'show_communities_directory', + 'show_invite_lightbox', 'significant_other', 'state', 'stats', + 'sticky_my_feed', 'summary', 'supervisor_admin', 'timezone', 'type', + 'updates', 'url', 'username', 'verified_admin', 'web_preferences', + 'web_url', 'xdr_proxy' ], # Case insensitive 'content_should_not_contain': diff --git a/tests/functional_tests/expected_values/yandex.py b/tests/functional_tests/expected_values/yandex.py index a543792f..351b744f 100644 --- a/tests/functional_tests/expected_values/yandex.py +++ b/tests/functional_tests/expected_values/yandex.py @@ -5,8 +5,8 @@ conf = fixtures.get_configuration('yandex') CONFIG = { - 'login_xpath': '//*[@id="login"]', - 'password_xpath': '//*[@id="passwd"]', + 'login_xpath': '//input[@type="text"]', + 'password_xpath': '//input[@type="password"]', 'consent_xpaths': [ '//*[@id="nb-2"]', ], @@ -64,8 +64,26 @@ 'token_secret': None, '_expire_in': True, 'provider_name': 'yandex', - 'refresh_token': None, + 'refresh_token': True, 'provider_type': 'authomatic.providers.oauth2.OAuth2', - 'refresh_status': constants.CREDENTIALS_REFRESH_NOT_SUPPORTED, + 'refresh_status': constants.CREDENTIALS_REFRESH_OK, + }, + # Testing changes after credentials refresh + # same: True + # not same: False + # don't test: None + 'credentials_refresh_change': { + 'token_type': True, + 'provider_type_id': True, + '_expiration_time': True, + 'consumer_key': True, + 'provider_id': True, + 'consumer_secret': True, + 'token': True, + 'token_secret': True, + '_expire_in': None, # Somtimes differ in one second. + 'provider_name': True, + 'refresh_token': False, + 'provider_type': True, }, } \ No newline at end of file diff --git a/tests/functional_tests/fixtures/__init__.py b/tests/functional_tests/fixtures/__init__.py index 7c9125fb..4b6dee85 100644 --- a/tests/functional_tests/fixtures/__init__.py +++ b/tests/functional_tests/fixtures/__init__.py @@ -137,11 +137,15 @@ def get_configuration(provider): Res.user_birth_date_str = bday.strftime(Res.BIRTH_DATE_FORMAT) Res.no_birth_date = ['birth'] + Res.no_birth_year = [conf['user_birth_year'], 'birth_year', 'birthYear'] + Res.no_birth_month = [conf['user_birth_month'], 'birth_month', 'birthMonth'] + Res.no_birth_day = [conf['user_birth_day'], 'birth_day', 'birthDay'] Res.no_city = [conf['user_city'], 'city'] Res.no_country = [conf['user_country'], 'country'] Res.no_email = [conf['user_email'], 'email'] Res.no_first_name = ['"{0}"'.format(conf['user_first_name']), 'first'] - Res.no_last_name = ['"{0}"'.format(conf['user_last_name']), 'last'] + Res.no_last_name = ['"{0}"'.format(conf['user_last_name']), 'last_name', + 'lastname'] Res.no_gender = [conf['user_gender'], 'gender'] Res.no_link = ['link'] Res.no_locale = [conf['user_locale'], 'language', 'locale'] diff --git a/tests/functional_tests/old_config.py b/tests/functional_tests/old_config.py new file mode 100644 index 00000000..dd7ce564 --- /dev/null +++ b/tests/functional_tests/old_config.py @@ -0,0 +1,384 @@ +# -*- coding: utf-8 -*- +import datetime + +# from pyvirtualdisplay import Display +from selenium import webdriver + +import constants + +# Choose and configure the browser of your choice +def get_browser(): + return webdriver.Chrome() + # global display + # + # display = Display(visible=0, size=(1024, 768)) + # display.start() + # return webdriver.Firefox() + + +def teardown(): + pass + # global display + # display.stop() + + +# A failed login by a provider will be retried so many times as set here +MAX_LOGIN_ATTEMPTS = 3 +# Multiplies the wait times set in expected values +WAIT_MULTIPLIER = 1 +# Minimum wait time +MIN_WAIT = 0 + +# The host and port where the tested ap shoud listen. +HOST = '127.0.0.1' +PORT = 80 + +# The host alias set in the /etc/hosts file. +# The actual tests will navigate selenium browser to this host. +# This is necessary because some providers don't support localhost as the +# callback url. +HOST_ALIAS = 'authomatic.org' + +# Only frameworks included here will be tested. +INCLUDE_FRAMEWORKS = [ + # 'django', + 'flask', + # 'pyramid', +] + +# Only providers included here will be tested. +# This is a convenience to easily exclude providers from tests by commenting +# them out. +INCLUDE_PROVIDERS = [ + # 'bitbucket', + # 'flickr', + # # Meetup has a bug in its OAuth 1.0a flow: + # # https://github.com/meetup/api/issues/83 + # 'meetup', + # 'plurk', + # 'twitter', + # 'tumblr', + # # UbuntuOne service is no longer available + # 'ubuntuone', + # 'vimeo', + # 'xero', + # 'xing', + # 'yahoo', + # + # 'amazon', + # # 'behance', # Behance doesn't support third party authorization anymore. + # 'bitly', + # 'deviantart', + # 'eventbrite', + # 'facebook', + # 'foursquare', + # 'google', + # 'github', + # 'linkedin', + # 'paypal', + # 'reddit', + # 'vk', + # 'windowslive', + # 'yammer', + # 'yandex', + # + # 'openid_livejournal', + # 'openid_verisignlabs', + # 'openid_wordpress', + # 'openid_yahoo', +] + + +# Use these constants if you have the same user info by all tested providers. +PASSWORD = '1Kokotina' +EMAIL = 'peterhudec@peterhudec.com' +FIRST_NAME = 'Peter' +LAST_NAME = 'Hudec' +NAME = FIRST_NAME + ' ' + LAST_NAME +USERNAME = 'peterhudec' +USERNAME_REVERSE = 'hudecpeter' +NICKNAME = 'hudo' +BIRTH_YEAR = 1979 +BIRTH_MONTH = 11 +BIRTH_DAY = 18 +BIRTH_DATE = datetime.datetime(BIRTH_YEAR, BIRTH_MONTH, BIRTH_DAY) +CITY = 'Bratislava' +COUNTRY = 'Slovakia' +COUNTRY_ISO2 = 'sk' +POSTAL_CODE = '82107' +PHONE = '0903972845' +PHONE_INTERNATIONAL = '00421903972845' +GENDER = constants.GENDER_MALE +LOCALE = 'en_US' +LOCATION = CITY + ', ' + COUNTRY + +# Common values for all providers +COMMON = { + # Could be same if the user sets it so + 'user_birth_date': BIRTH_DATE, + 'user_birth_day': BIRTH_DAY, + 'user_birth_month': BIRTH_MONTH, + 'user_birth_year': BIRTH_YEAR, + 'user_login': EMAIL, + 'user_password': PASSWORD, + 'user_email': EMAIL, + 'user_first_name': FIRST_NAME, + 'user_last_name': LAST_NAME, + 'user_name': NAME, + 'user_username': USERNAME, + 'user_username_reverse': USERNAME_REVERSE, + 'user_nickname': NICKNAME, + 'user_city': CITY, + 'user_country': COUNTRY, + 'user_gender': GENDER, + 'user_phone': PHONE, + 'user_postal_code': POSTAL_CODE, + 'user_locale': LOCALE, + 'user_location': LOCATION, + + # Provider and user specific value + # 'user_id': '', + # 'user_locale': None, + # 'user_timezone': None, + + # Provider specific format + # 'user_picture': '', + # 'user_link': '', + + # Provider specific value + # 'consumer_key': '', + # 'consumer_secret': '', +} + +# Values from COMMON will be overriden by values from PROVIDERS[provider_name] +# if set. +PROVIDERS = { + # OAuth 1.0a + 'bitbucket': { + 'consumer_key': 'pusFTPPcYe7UjdPLeb', + 'consumer_secret': 'xHMUxrqZqM2bZ7UPdu85y2fyffYsKJzq', + 'user_id': USERNAME, + 'user_password': 'edenbedenAtlassianHreben8' + }, + 'flickr': { + 'consumer_key': '518b447b07302e1a96cd0c4db9ae7580', + 'consumer_secret': 'b9b34d4b31d98b3d', + 'user_login': USERNAME_REVERSE, + 'user_id': '38774396@N06', + 'user_username': NAME, + }, + 'meetup': { + 'consumer_key': 's5ke2j3rfjnb3famghelr4cakh', + 'consumer_secret': 'imemaigvgjh9n44vtv89gr5uv9', + 'user_login': EMAIL, + 'user_id': '84631542', + 'user_country': COUNTRY_ISO2, + 'user_location': '{0}, {1}'.format(CITY, COUNTRY_ISO2), + }, + 'plurk': { + 'consumer_key': 't4kdmAEUZfxe', + 'consumer_secret': 'SabRa44lgMTt69gpLq7pcxYIFUTHSu1u', + 'user_id': '9433315', + 'user_gender': '1', + 'user_locale': 'en', + 'user_nickname': USERNAME, + 'user_country': 'Slovak Republic', + 'user_location': 'Bratislava, Slovak Republic', + 'user_timezone': 'UTC', + }, + 'twitter': { + 'consumer_key': 'zku3BZ9wi7e4e07h0M3VA', + 'consumer_secret': 'WQ0SYSUWXlBiLvYAJ32DDMfwsJ4up79EfifNXks', + 'user_id': '243645555', + 'user_password': PASSWORD, + # TODO: Change to regex + 'user_link': 'http://t.co/2DHh59Jg', + 'user_picture': 'http://pbs.twimg.com/profile_images/1815572456/ja_normal.jpg', + 'user_username': 'peterhudec_com', + 'user_locale': 'en', + }, + 'tumblr': { + 'consumer_key': 'MEVRboAn9x7zEayX8QKUrxiOgkPJqv0BSijvplzL4KxXBDeGoC', + 'consumer_secret': 'tWT7sWoF9gI1pZ7guNH5KEW2K4vDVes7jikhHNVcDqoGqEefaZ', + 'user_id': USERNAME, + 'user_name': USERNAME, + }, + 'vimeo': { + 'consumer_key': '8e8730d3bb2a15b6d172bfa8e8bd82cafe66e62b', + 'consumer_secret': 'J8G43W6B75i8W84vz2epyU0ztkTrlxwH8/Jdo8qpvdx7r1A3RPPg82dgwR094Hlp4H1Jw4MOQJLzS+pyX64pX5fydqi98FEgmGXdGQ1+odfmImt91PGbkmH2AOddZHQ7', + 'user_id': '16990697', + }, + 'xero': { + 'consumer_key': 'LSDLAW8H3DRFGTBBCIKOQQOWOJTY2J', + 'consumer_secret': 'INAULZZWB2X5JEDWKKXRGZK0SCREYI', + 'user_id': '0c604bb8-7598-472e-90ce-a69e0fc20cd8', + }, + 'xing': { + 'consumer_key': 'aed27cac96f05dff725a', + 'consumer_secret': '41778fef68e84fa7082e18fca6b9c7eb2ac4482e', + 'user_id': '20439769_c51b8d', + 'user_username': 'Peter_Hudec4', + 'user_country': COUNTRY_ISO2.upper(), + 'user_gender': 'm', # m or f + 'user_locale': 'en', + 'user_location': '{0}, {1}'.format(CITY, COUNTRY_ISO2.upper()), + 'user_timezone': 'Europe/Bratislava', + 'user_phone': '421903972845', + }, + 'yahoo': { + 'consumer_key': 'dj0yJmk9cU1wQ3N2aTFlYzlXJmQ9WVdrOVlrZFdiVzVCTkdNbWNHbzlNVE16TVRFNU5ERTJNZy0tJnM9Y29uc3VtZXJzZWNyZXQmeD03Mw--', + 'consumer_secret': '41cb637f38ff5225a20f4e2b9c321eb96cdea6ee', + 'user_login': USERNAME_REVERSE, + 'user_id': 'OV6YUPBP7S5FI25QLNJ3BNP5BY', + 'user_gender': 'M' # M or F + }, + + # OAuth 2.0 + 'amazon': { + 'consumer_key': 'amzn1.application-oa2-client.aac94a29737c4d86a734e5964696d2bd', + 'consumer_secret': 'bb1aa3772cd755d009ba419211fccde1e785021e1bbea4334235ca298dac42b0', + 'user_id': 'amzn1.account.AFQXBE5AJMX73ZABIBYD25ET2MZQ', + 'user_password': '19Pitelova79', + }, + 'behance': { + 'consumer_key': 'J6MwhGHdTHwwYQEHyTq2jNgly0EEXixe', + 'consumer_secret': 'A3IP7wH2pZ0zpiCCCYwO.Z7HkK', + 'user_id': '???', + }, + 'bitly': { + 'consumer_key': 'd4afbd49a8abfbbf288730725dd9609dbb167320', + 'consumer_secret': 'cd30cb160445410d2825aed974ae5fdb15a3db9b', + 'user_id': 'o_a1o5pegh9', + }, + 'deviantart': { + 'consumer_key': '376', + 'consumer_secret': 'd56ec842a9558fe9916210b376988c2d', + }, + 'eventbrite': { + 'consumer_key': 'M5T4QRF5TNIQQJ76EC', + 'consumer_secret': 'C4HNTPVUKCMK3XBWQJ45KFV5H2AIII5C7XNNPZIYSQIQAPMQP7', + 'user_id': '125996068589', + }, + 'facebook': { + 'consumer_key': '482771598445314', + 'consumer_secret': '7163e6e8dedc2c055445b553823ec910', + 'user_id': '737583375', + 'user_password': 'kokotina', + # This changes when switching from and to Daylight Saving Time + # 'user_timezone': '2', + }, + 'foursquare': { + 'consumer_key': '11Q4GU3IGQYN3QTR5542VAGE1ZCQD5QDAOXVR1LA3FZ3Q24Z', + 'consumer_secret': 'WN1HYN4W0QJ4H0YV2KET1XKJHA1W2PFQT4WFOKDG3J3T05GK', + 'user_id': '8698379', + 'user_country': u'Slovenská republika', + 'user_location': u'Bratislava, Slovenská republika', + 'user_picture': 'https://irs2.4sqi.net/img/user/SEG1MMJYH0XHZTSY.jpg', + }, + 'google': { + 'consumer_key': '75167970188.apps.googleusercontent.com', + 'consumer_secret': 'Y70ZmvlkMKOTB8kKGBgN_BME', + 'user_login': 'peterhudec.com@gmail.com', + 'user_id': '117034840853387702598', + 'user_password': 'google19Pitelova79', + 'user_email': 'peterhudec.com@gmail.com', + 'user_locale': 'en', + 'user_picture': ('https://lh5.googleusercontent.com/-LbPepOoFAfA/' + 'AAAAAAAAAAI/AAAAAAAAOWY/3rWutUjFRGw/photo.jpg?sz=50'), + }, + 'github': { + 'user_login': 'peterhudec@peterhudec.com', + 'user_password': 'edenbedenGithubHreben8', + 'consumer_key': '4dde1fd8f548bfe87f0c', + 'consumer_secret': '05ee98945b55fc214a6d1d0c30abee12c3044e12', + 'user_id': '2333157', + 'access_headers': {'User-Agent': ('Authomatic.py Automated Functional ' + 'Tests')}, + 'user_country': 'United Kingdom', + 'user_city': 'London', + 'user_location': 'London, UK', + }, + 'linkedin': { + 'consumer_key': '771ej662emreyu', + 'consumer_secret': 'g3AMiN2vid6Dnn7Q', + 'user_id': 'nqpJbN5wLs', + 'user_country': 'Slovak Republic', + 'user_location': COUNTRY_ISO2, + 'user_link': 'https://www.linkedin.com/in/phudec', + }, + 'paypal': { + 'consumer_key': 'AR58GRBgr81q6vZQOUxwB1OF9_62PXb1CouQVaENf5dbTLgPkr3K7YZXgnc6', + 'consumer_secret': 'EJqCvRAZ4sFobX_NlzyVMyD3PSXH41pAWpeCFpiepworglq_f_5dl3TrCYFl', + }, + 'reddit': { + 'consumer_key': 'Jx3WyR5xS4pm1A', + 'consumer_secret': 'Z0RWV65T5e-qKf0OHDIu1baKhrI', + 'user_login': USERNAME, + 'user_id': 'aurmu', + 'access_headers': {'User-Agent': ('Authomatic.py Automated Functional ' + 'Tests')} + }, + # 'viadeo': { + # 'consumer_key': 'AuthomaticDevNyWlGx', + # 'consumer_secret': 'NvQuiowaMRBcs', + # }, + 'vk': { + 'consumer_key': '3479081', + 'consumer_secret': '3QKH4U5tpKytUYDGMWbO', + 'user_id': '203822236', + 'user_city': '1908070', + 'user_country': '184', + 'user_location': '1908070, 184', + 'user_gender': '2', + 'user_picture': 'http://cs7010.vk.me/c619226/v619226236/57a1/LJ4KAzr-byY.jpg', + 'user_timezone': '1', + }, + 'windowslive': { + 'consumer_key': '00000000440E8C5B', + 'consumer_secret': 'gu57AluGQnMkzxzdVp0ficFC00pBkl4S', + 'user_id': '5706420657626adb', + }, + 'yammer': { + 'consumer_key': '9JsB3qvVnptaR8GOkfA', + 'consumer_secret': 'Rt11EFemrHIZ6VY8pk8K4CvovTM6Y4hQmo8E3HDKY', + 'user_id': '1496566333', + 'user_picture': ('https://mug0.assets-yammer.com/mugshot/images/48x48/' + 'gWV0tl9Ln-NvG4V5-1rghnNwzxkBGfbn'), + 'user_timezone': 'Pacific Time (US & Canada)', + 'user_locale': 'en-US', + }, + 'yandex': { + 'consumer_key': '9c77329488b24bc6b8edb66777e1236a', + 'consumer_secret': '4b4408cda2f0459fab278b0880ebcf4d', + 'user_login': USERNAME, + 'user_id': '203067641', + }, + + # OpenID + 'openid_yahoo': { + 'openid_identifier': 'me.yahoo.com', + 'user_id': 'https://me.yahoo.com/a/w04F2kgxq.RX.VjfbCiOuQ0CwmbG8Qs-', + 'user_login': USERNAME_REVERSE, + 'user_email': 'hudecpeter@yahoo.com', + }, + 'openid_livejournal': { + 'openid_identifier': '{0}.livejournal.com'.format(USERNAME), + 'user_login': USERNAME, + 'user_id': 'http://{0}.livejournal.com/'.format(USERNAME) + }, + 'openid_wordpress': { + 'openid_identifier': '{0}.wordpress.com'.format(USERNAME), + 'user_login': EMAIL, + 'user_id': 'https://{0}.wordpress.com/'.format(USERNAME), + 'user_nickname': 'veporclique', + }, + 'openid_verisignlabs': { + 'openid_identifier': '{0}.pip.verisignlabs.com'.format(USERNAME), + 'user_login': USERNAME, + 'user_id': 'http://{0}.pip.verisignlabs.com/'.format(USERNAME), + 'user_nickname': NICKNAME, + 'user_timezone': 'Europe/Bratislava', + 'user_locale': 'Slovak', + 'user_gender': 'M', + }, +} diff --git a/tests/functional_tests/templates/index.html b/tests/functional_tests/templates/index.html index aa4645d4..6f2cfc06 100644 --- a/tests/functional_tests/templates/index.html +++ b/tests/functional_tests/templates/index.html @@ -3,7 +3,7 @@ Authomatic Functional Test - +