Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add QueryableFake gateway #293

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ General Settings
Twilio_.
* ``'two_factor.gateways.fake.Fake'`` for development, recording tokens to the
default logger.
* ``'two_factor.gateways.fake.QueryableFake'`` for testing, recording tokens
to ``QueryableFake.call_tokens``.

``TWO_FACTOR_SMS_GATEWAY`` (default: ``None``)
Which gateway to use for sending text messages. Should be set to a module or
Expand All @@ -28,6 +30,8 @@ General Settings
Twilio_.
* ``'two_factor.gateways.fake.Fake'`` for development, recording tokens to the
default logger.
* ``'two_factor.gateways.fake.QueryableFake'`` for testing, recording tokens
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is merged, the text "Currently two gateways are bundled:" no longer holds.

to the ``QueryableFake.sms_tokens``.

``LOGIN_URL``
Should point to the login view provided by this application as described in
Expand Down Expand Up @@ -118,6 +122,10 @@ Fake Gateway
------------
.. autoclass:: two_factor.gateways.fake.Fake

QueryableFake Gateway
------------
.. autoclass:: two_factor.gateways.fake.QueryableFake

.. _LOGIN_URL: https://docs.djangoproject.com/en/dev/ref/settings/#login-url
.. _LOGIN_REDIRECT_URL: https://docs.djangoproject.com/en/dev/ref/settings/#login-redirect-url
.. _LOGOUT_REDIRECT_URL: https://docs.djangoproject.com/en/dev/ref/settings/#logout-redirect-url
Expand Down
31 changes: 30 additions & 1 deletion tests/test_gateways.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from django.utils.six.moves.urllib.parse import urlencode
from phonenumber_field.phonenumber import PhoneNumber

from two_factor.gateways.fake import Fake
from two_factor.gateways.fake import Fake, QueryableFake
from two_factor.gateways.twilio.gateway import Twilio

try:
Expand Down Expand Up @@ -117,3 +117,32 @@ def test_gateway(self, logger):
fake.send_sms(device=Mock(number=PhoneNumber.from_string('+123')), token=code)
logger.info.assert_called_with(
'Fake SMS to %s: "Your token is: %s"', '+123', code)


class QueryableFakeGatewayTest(TestCase):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More tests please. E.g. do call_tokens and sms_tokens get reset properly after each testcase?


def tearDown(self):
QueryableFake.reset()

def test_gateway(self):
fake = QueryableFake()

for code in ['654321', '87654321']:
fake.make_call(device=Mock(number=PhoneNumber.from_string('+123')), token=code)
self.assertEqual(fake.call_tokens['+123'].pop(), code)

fake.send_sms(device=Mock(number=PhoneNumber.from_string('+123')), token=code)
self.assertEqual(fake.sms_tokens['+123'].pop(), code)

def test_gateway_must_be_reset_between_tests(self):
fake = QueryableFake()
codes = ['654321', '87654321']
for code in codes:
fake.make_call(device=Mock(number=PhoneNumber.from_string('+123')), token=code)
fake.send_sms(device=Mock(number=PhoneNumber.from_string('+123')), token=code)

self.assertEqual(len(fake.call_tokens['+123']), len(codes))
self.assertEqual(len(fake.sms_tokens['+123']), len(codes))
fake.reset()
self.assertEqual(len(fake.call_tokens['+123']), 0)
self.assertEqual(len(fake.sms_tokens['+123']), 0)
51 changes: 51 additions & 0 deletions two_factor/gateways/fake.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import logging
from collections import defaultdict

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -33,3 +34,53 @@ def make_call(device, token):
@staticmethod
def send_sms(device, token):
logger.info('Fake SMS to %s: "Your token is: %s"', device.number.as_e164, token)


class QueryableFake(object):
"""A Fake gateway that can be queried.

For example, you may use this in your unit tests::

>>> from django.test import TestCase, override_settings
>>> from django.contrib.auth import get_user_model
>>> from django.conf import settings
>>> from two_factor.gateways.fake import QueryableFake
>>> from two_factor.models import PhoneDevice
>>> from phonenumber_field.phonenumber import PhoneNumber
>>>
>>> class MyTestCase(TestCase):
... def tearDown(self):
... QueryableFake.reset()
...
... @override_settings(
... TWO_FACTOR_SMS_GATEWAY='two_factor.gateways.fake.QueryableFake',
... )
... def test_something(self):
... user = get_user_model().objects.create(...)
... PhoneDevice.objects.create(
... user=user, name='default', method='sms',
... number=PhoneNumber.from_string('+441234567890'),
... )
... self.client.post(settings.LOGIN_URL,
... 'username': user.username,
... 'password': 'password',
... })
... token = QueryableFake.sms_tokens['+441234567890'].pop()
... self.client.post(settings.LOGIN_URL, {'token': token})
"""
sms_tokens = defaultdict(list)
call_tokens = defaultdict(list)

@classmethod
def clear_all_tokens(cls):
cls.sms_tokens = defaultdict(list)
cls.call_tokens = defaultdict(list)
reset = clear_all_tokens

@classmethod
def make_call(cls, device, token):
cls.call_tokens[str(device.number)].append(token)

@classmethod
def send_sms(cls, device, token):
cls.sms_tokens[str(device.number)].append(token)