diff --git a/src/postorius/forms.py b/src/postorius/forms.py index a4a9760..bd17c87 100644 --- a/src/postorius/forms.py +++ b/src/postorius/forms.py @@ -906,3 +906,12 @@ class AddressActivationForm(forms.Form): email = forms.EmailField() + user_email = forms.EmailField(widget=forms.HiddenInput) + + def clean(self): + cleaned_data = super(AddressActivationForm, self).clean() + email = cleaned_data.get('email') + user_email = cleaned_data.get('user_email') + if email == user_email: + raise forms.ValidationError(_('Please provide a different email address than your own.')) + return cleaned_data diff --git a/src/postorius/tests/test_address_activation.py b/src/postorius/tests/test_address_activation.py index d8c9167..506c292 100644 --- a/src/postorius/tests/test_address_activation.py +++ b/src/postorius/tests/test_address_activation.py @@ -2,6 +2,7 @@ unicode_literals) +from django.contrib.auth.models import User from django.conf import settings from django.core.urlresolvers import reverse from django.test.client import Client, RequestFactory @@ -16,52 +17,144 @@ class TestAddressActivationForm(unittest.TestCase): def test_valid_email_is_valid(self): - form = AddressActivationForm({'email': 'les@example.org'}) + data = { + 'email': 'les@example.org', + 'user_email': 'me@example.org', + } + form = AddressActivationForm(data) self.assertTrue(form.is_valid()) + def test_identical_emails_are_invalid(self): + data = { + 'email': 'les@example.org', + 'user_email': 'les@example.org', + } + form = AddressActivationForm(data) + self.assertFalse(form.is_valid()) + def test_invalid_email_is_not_valid(self): - form = AddressActivationForm({'email': 'les@example'}) + data = { + 'email': 'les@example', + 'user_email': 'me@example.org', + } + form = AddressActivationForm(data) self.assertFalse(form.is_valid()) class TestAddressActivationView(unittest.TestCase): + """ + Tests to make sure the view is properly connected, renders the form + correctly and starts the actual address activation process if a valid + form is submitted. + """ + + def setUp(self): + # We create a new user and log that user in. + # We don't use Client().login because it triggers the browserid dance. + self.user = User.objects.create_user( + username='les', email='les@example.org', password='secret') + self.client = Client() + self.client.post(reverse('user_login'), + {'username': 'les', 'password': 'secret'}) + + def tearDown(self): + # Log out and delete user. + self.client.logout() + self.user.delete() def test_view_is_connected(self): # The view should be connected in the url configuration. - response = Client().get(reverse('address_activation')) + response = self.client.get(reverse('address_activation')) self.assertEqual(response.status_code, 200) def test_view_contains_form(self): # The view context should contain a form. - response = Client().get(reverse('address_activation')) + response = self.client.get(reverse('address_activation')) self.assertTrue('form' in response.context) def test_view_renders_correct_template(self): # The view should render the user_address_activation template. - response = Client().get(reverse('address_activation')) + response = self.client.get(reverse('address_activation')) self.assertTrue('postorius/user_address_activation.html' in [t.name for t in response.templates]) def test_post_invalid_form_shows_error_msg(self): # Entering an invalid email address should render an error message. - response = Client().post(reverse('address_activation'), - {'email': 'invalid_email'}) + response = self.client.post(reverse('address_activation'), + { + 'email': 'invalid_email', + 'user_email': self.user.email, + }) self.assertTrue('Enter a valid email address.' in response.content) @patch.object(AddressActivationView, '_handle_address') def test_post_valid_form_renders_success_template(self, handle_address_mock): # Entering a valid email should render the activation_sent template. - response = Client().post(reverse('address_activation'), - {'email': 'new_address@example.org'}) + response = self.client.post(reverse('address_activation'), + { + 'email': 'new_address@example.org', + 'user_email': self.user.email, + }) self.assertTrue('postorius/user_address_activation_sent.html' in [t.name for t in response.templates]) @patch.object(AddressActivationView, '_handle_address') - def test_post_valid_form_calls_handle_address_method(self, handle_address_mock): - # Entering a valid email should render the activation_sent template. - response = Client().post(reverse('address_activation'), - {'email': 'new_address@example.org'}) + def test_post_valid_form_calls_handle_address_method(self, + handle_address_mock): + # Entering a valid email should call _handle_address with the request's + # user instance as well as the email address to activate. + response = self.client.post(reverse('address_activation'), + { + 'email': 'new_address@example.org', + 'user_email': self.user.email, + }) self.assertEqual(handle_address_mock.call_count, 1) args, kwargs = handle_address_mock.call_args - self.assertTrue('new_address@example.org' in args) + self.assertTrue(isinstance(args[0], User)) + self.assertEqual(args[1], 'new_address@example.org') + + +class TestAddressActivationStart(unittest.TestCase): + """ + Tests the initiation of the address activation (sending the appropriate + emails, generating the token etc.). + """ + + def setUp(self): + self.foo_user = User.objects.create_user( + 'foo', email='foo@example.org', password='pass') + self.bar_user = User.objects.create_user( + 'bar', email='bar@example.org', password='pass') + + def tearDown(self): + self.foo_user.delete() + self.bar_user.delete() + + @patch.object(AddressActivationView, '_start_confirmation') + @patch.object(AddressActivationView, '_notify_existing_user') + def test_existing_user_detected( + self, notify_existing_user_mock, start_confirmation_mock): + # Using the email address of an existing user should hit the + # _notify_existing_user method. + AddressActivationView._handle_address(self.foo_user, 'bar@example.org') + self.assertEqual(notify_existing_user_mock.call_count, 1) + self.assertEqual(start_confirmation_mock.call_count, 0) + + @patch.object(AddressActivationView, '_start_confirmation') + @patch.object(AddressActivationView, '_notify_existing_user') + def test_confirmation_start( + self, notify_existing_user_mock, start_confirmation_mock): + # Using the email address of an existing user should hit the + # _notify_existing_user method. + AddressActivationView._handle_address(self.foo_user, 'new@address.org') + self.assertEqual(notify_existing_user_mock.call_count, 0) + self.assertEqual(start_confirmation_mock.call_count, 1) + + +class TestAddressActivationConfirmation(unittest.TestCase): + """ + Tests the confirmation of an email address activation (validating token, + expiration, Mailman API calls etc.). + """ + pass diff --git a/src/postorius/views/user.py b/src/postorius/views/user.py index 7f97421..6ce803d 100644 --- a/src/postorius/views/user.py +++ b/src/postorius/views/user.py @@ -233,20 +233,45 @@ class AddressActivationView(TemplateView): + """ + Starts the process of adding additional email addresses to a mailman user + record. Forms are processes and email notifications are sent accordingly. + """ - def _handle_address(self, address): + @staticmethod + def _notify_existing_user(address): pass + @staticmethod + def _start_confirmation(address): + pass + + @staticmethod + def _handle_address(user, address): + try: + # A user exists for that email address, so this user should be + # that some tried to add this email address to their MM user record. + User.objects.get(email=address) + AddressActivationView._notify_existing_user(address) + except User.DoesNotExist: + # There's currently no user record for this email address. + # Start the confirmation process. + AddressActivationView._start_confirmation(address) + + @method_decorator(login_required) def get(self, request): - form = AddressActivationForm() + form = AddressActivationForm(initial={'user_email': request.user.email}) return render_to_response('postorius/user_address_activation.html', {'form': form}, context_instance=RequestContext(request)) + @method_decorator(login_required) def post(self, request): + print(request.POST) form = AddressActivationForm(request.POST) if form.is_valid(): - self._handle_address(request.POST['email']) + print('IS_VALID') + self._handle_address(request.user, form.cleaned_data['email']) return render_to_response('postorius/user_address_activation_sent.html', context_instance=RequestContext(request)) return render_to_response('postorius/user_address_activation.html',