<commit_before>import json
import time

import mock
from gratipay.exceptions import CannotRemovePrimaryEmail, EmailAlreadyTaken, EmailNotVerified
from gratipay.exceptions import TooManyEmailAddresses, ResendingTooFast
from gratipay.models.participant import Participant
from gratipay.testing.emails import EmailHarness
from gratipay.utils import emails


class TestEmail(EmailHarness):

    def setUp(self):
        EmailHarness.setUp(self)
        self.alice = self.make_participant('alice', claimed_time='now')
        self._old_threshold = self.client.website.env.resend_verification_threshold
        self.client.website.env.resend_verification_threshold = '0 seconds'

    def tearDown(self):
        self.client.website.env.resend_verification_threshold = self._old_threshold

    def hit_email_spt(self, action, address, user='alice', should_fail=False):
        P = self.client.PxST if should_fail else self.client.POST
        data = {'action': action, 'address': address}
        headers = {'HTTP_ACCEPT_LANGUAGE': 'en'}
        return P('/~alice/emails/modify.json', data, auth_as=user, **headers)

    def verify_email(self, email, nonce, username='alice', should_fail=False):
        url = '/~%s/emails/verify.html?email=%s&nonce=%s' % (username, email, nonce)
        G = self.client.GxT if should_fail else self.client.GET
        return G(url, auth_as=username)

    def verify_and_change_email(self, old_email, new_email, username='alice'):
        self.hit_email_spt('add-email', old_email)
        nonce = Participant.from_username(username).get_email(old_email).nonce
        self.verify_email(old_email, nonce)
        self.hit_email_spt('add-email', new_email)

    def test_participant_can_add_email(self):
        response = self.hit_email_spt('add-email', '<EMAIL>')
        actual = json.loads(response.body)
        assert actual

    def test_adding_email_sends_verification_email(self):
        self.hit_email_spt('add-email', '<EMAIL>')
        assert self.mailer.call_count == 1
        last_email = self.get_last_email()
        assert last_email['to'][0]['email'] == '<EMAIL>'
        expected = "We've received a request to connect <EMAIL> to the alice account on Gratipay"
        assert expected in last_email['text']

    def test_verification_email_doesnt_contain_unsubscribe(self):
        self.hit_email_spt('add-email', '<EMAIL>')
        last_email = self.get_last_email()
        assert "To stop receiving" not in last_email['text']

    def test_adding_second_email_sends_verification_notice(self):
        self.verify_and_change_email('<EMAIL>', '<EMAIL>')
        assert self.mailer.call_count == 3
        last_email = self.get_last_email()
        assert last_email['to'][0]['email'] == '<EMAIL>'
        expected = "We are connecting <EMAIL> to the alice account on Gratipay"
        assert expected in last_email['text']

    def test_post_anon_returns_403(self):
        response = self.hit_email_spt('add-email', '<EMAIL>', user=None, should_fail=True)
        assert response.code == 403

    def test_post_with_no_at_symbol_is_400(self):
        response = self.hit_email_spt('add-email', 'gratipay.com', should_fail=True)
        assert response.code == 400

    def test_post_with_no_period_symbol_is_400(self):
        response = self.hit_email_spt('add-email', 'test@gratipay', should_fail=True)
        assert response.code == 400

    def test_verify_email_without_adding_email(self):
        response = self.verify_email('', 'sample-nonce')
        assert 'Missing Info' in response.body

    def test_verify_email_wrong_nonce(self):
        self.hit_email_spt('add-email', '<EMAIL>')
        nonce = 'fake-nonce'
        r = self.alice.verify_email('<EMAIL>', nonce)
        assert r == emails.VERIFICATION_FAILED
        self.verify_email('<EMAIL>', nonce)
        expected = None
        actual = Participant.from_username('alice').email_address
        assert expected == actual

    def test_verify_email_a_second_time_returns_redundant(self):
        address = '<EMAIL>'
        self.hit_email_spt('add-email', address)
        nonce = self.alice.get_email(address).nonce
        r = self.alice.verify_email(address, nonce)
        r = self.alice.verify_email(address, nonce)
        assert r == emails.VERIFICATION_REDUNDANT

    def test_verify_email_expired_nonce(self):
        address = '<EMAIL>'
        self.hit_email_spt('add-email', address)
        self.db.run("""
            UPDATE emails
               SET verification_start = (now() - INTERVAL '25 hours')
             WHERE participant_id = %s;
        """, (self.alice.id,))
        nonce = self.alice.get_email(address).nonce
        r = self.alice.verify_email(address, nonce)
        assert r == emails.VERIFICATION_EXPIRED
        actual = Participant.from_username('alice').email_address
        assert actual == None

    def test_verify_email(self):
        self.hit_email_spt('add-email', '<EMAIL>')
        nonce = self.alice.get_email('<EMAIL>').nonce
        self.verify_email('<EMAIL>', nonce)
        expected = '<EMAIL>'
        actual = Participant.from_username('alice').email_address
        assert expected == actual

    def test_verified_email_is_not_changed_after_update(self):
        self.verify_and_change_email('<EMAIL>', '<EMAIL>')
        expected = '<EMAIL>'
        actual = Participant.from_username('alice').email_address
        assert expected == actual

    def test_get_emails(self):
        self.verify_and_change_email('<EMAIL>', '<EMAIL>')
        emails = self.alice.get_emails()
        assert len(emails) == 2

    def test_verify_email_after_update(self):
        self.verify_and_change_email('<EMAIL>', '<EMAIL>')
        nonce = self.alice.get_email('<EMAIL>').nonce
        self.verify_email('<EMAIL>', nonce)
        expected = '<EMAIL>'
        actual = Participant.from_username('alice').email_address
        assert expected == actual

    def test_nonce_is_reused_when_resending_email(self):
        self.hit_email_spt('add-email', '<EMAIL>')
        nonce1 = self.alice.get_email('<EMAIL>').nonce
        self.hit_email_spt('resend', '<EMAIL>')
        nonce2 = self.alice.get_email('<EMAIL>').nonce
        assert nonce1 == nonce2<commit_msg>Added tests for new style email verification.<commit_after>import json
import time

import mock
from gratipay.exceptions import CannotRemovePrimaryEmail, EmailAlreadyTaken, EmailNotVerified
from gratipay.exceptions import TooManyEmailAddresses, ResendingTooFast
from gratipay.models.participant import Participant
from gratipay.testing.emails import EmailHarness
from gratipay.utils import emails, b64encode_s


class TestEmail(EmailHarness):

    def setUp(self):
        EmailHarness.setUp(self)
        self.alice = self.make_participant('alice', claimed_time='now')
        self._old_threshold = self.client.website.env.resend_verification_threshold
        self.client.website.env.resend_verification_threshold = '0 seconds'

    def tearDown(self):
        self.client.website.env.resend_verification_threshold = self._old_threshold

    def hit_email_spt(self, action, address, user='alice', should_fail=False):
        P = self.client.PxST if should_fail else self.client.POST
        data = {'action': action, 'address': address}
        headers = {'HTTP_ACCEPT_LANGUAGE': 'en'}
        return P('/~alice/emails/modify.json', data, auth_as=user, **headers)

    def verify_email(self, email, nonce, username='alice', should_fail=False):
        # Email address is base64 encoded in url.
        url = '/~%s/emails/verify.html?email64=%s&nonce=%s' % (username, b64encode_s(email), nonce)
        G = self.client.GxT if should_fail else self.client.GET
        return G(url, auth_as=username)

    def verify_and_change_email(self, old_email, new_email, username='alice'):
        self.hit_email_spt('add-email', old_email)
        nonce = Participant.from_username(username).get_email(old_email).nonce
        self.verify_email(old_email, nonce)
        self.hit_email_spt('add-email', new_email)

    def test_participant_can_add_email(self):
        response = self.hit_email_spt('add-email', '<EMAIL>')
        actual = json.loads(response.body)
        assert actual

    def test_adding_email_sends_verification_email(self):
        self.hit_email_spt('add-email', '<EMAIL>')
        assert self.mailer.call_count == 1
        last_email = self.get_last_email()
        assert last_email['to'][0]['email'] == '<EMAIL>'
        expected = "We've received a request to connect <EMAIL> to the alice account on Gratipay"
        assert expected in last_email['text']

    def test_email_address_is_base64_encoded_in_sent_verification_link(self):
        address = '<EMAIL>'
        encoded = b64encode_s(address)
        self.hit_email_spt('add-email', address)
        last_email = self.get_last_email()
        assert "~alice/emails/verify.html?email64="+encoded in last_email['text']

    def test_verification_email_doesnt_contain_unsubscribe(self):
        self.hit_email_spt('add-email', '<EMAIL>')
        last_email = self.get_last_email()
        assert "To stop receiving" not in last_email['text']

    def test_adding_second_email_sends_verification_notice(self):
        self.verify_and_change_email('<EMAIL>', '<EMAIL>')
        assert self.mailer.call_count == 3
        last_email = self.get_last_email()
        assert last_email['to'][0]['email'] == '<EMAIL>'
        expected = "We are connecting <EMAIL> to the alice account on Gratipay"
        assert expected in last_email['text']

    def test_post_anon_returns_403(self):
        response = self.hit_email_spt('add-email', '<EMAIL>', user=None, should_fail=True)
        assert response.code == 403

    def test_post_with_no_at_symbol_is_400(self):
        response = self.hit_email_spt('add-email', 'gratipay.com', should_fail=True)
        assert response.code == 400

    def test_post_with_no_period_symbol_is_400(self):
        response = self.hit_email_spt('add-email', 'test@gratipay', should_fail=True)
        assert response.code == 400

    def test_verify_email_without_adding_email(self):
        response = self.verify_email('', 'sample-nonce')
        assert 'Missing Info' in response.body

    def test_verify_email_wrong_nonce(self):
        self.hit_email_spt('add-email', '<EMAIL>')
        nonce = 'fake-nonce'
        r = self.alice.verify_email('<EMAIL>', nonce)
        assert r == emails.VERIFICATION_FAILED
        self.verify_email('<EMAIL>', nonce)
        expected = None
        actual = Participant.from_username('alice').email_address
        assert expected == actual

    def test_verify_email_a_second_time_returns_redundant(self):
        address = '<EMAIL>'
        self.hit_email_spt('add-email', address)
        nonce = self.alice.get_email(address).nonce
        r = self.alice.verify_email(address, nonce)
        r = self.alice.verify_email(address, nonce)
        assert r == emails.VERIFICATION_REDUNDANT

    def test_verify_email_expired_nonce(self):
        address = '<EMAIL>'
        self.hit_email_spt('add-email', address)
        self.db.run("""
            UPDATE emails
               SET verification_start = (now() - INTERVAL '25 hours')
             WHERE participant_id = %s;
        """, (self.alice.id,))
        nonce = self.alice.get_email(address).nonce
        r = self.alice.verify_email(address, nonce)
        assert r == emails.VERIFICATION_EXPIRED
        actual = Participant.from_username('alice').email_address
        assert actual == None

    def test_verify_email(self):
        self.hit_email_spt('add-email', '<EMAIL>')
        nonce = self.alice.get_email('<EMAIL>').nonce
        self.verify_email('<EMAIL>', nonce)
        expected = '<EMAIL>'
        actual = Participant.from_username('alice').email_address
        assert expected == actual

    def test_email_verification_is_backwards_compatible(self):
        """Test email verification still works with unencoded email in verifcation link.
        """
        self.hit_email_spt('add-email', '<EMAIL>')
        nonce = self.alice.get_email('<EMAIL>').nonce
        url = '/~alice/emails/verify.html?email=<EMAIL>&nonce='+nonce
        self.client.GET(url, auth_as='alice')
        expected = '<EMAIL>'
        actual = Participant.from_username('alice').email_address
        assert expected == actual

    def test_verified_email_is_not_changed_after_update(self):
        self.verify_and_change_email('<EMAIL>', '<EMAIL>')
        expected = '<EMAIL>'
        actual = Participant.from_username('alice').email_address
        assert expected == actual

    def test_get_emails(self):
        self.verify_and_change_email('<EMAIL>', '<EMAIL>')
        emails = self.alice.get_emails()
        assert len(emails) == 2

    def test_verify_email_after_update(self):
        self.verify_and_change_email('<EMAIL>', '<EMAIL>')
        nonce = self.alice.get_email('<EMAIL>').nonce
        self.verify_email('<EMAIL>', nonce)
        expected = '<EMAIL>'
        actual = Participant.from_username('alice').email_address
        assert expected == actual

    def test_nonce_is_reused_when_resending_email(self):
        self.hit_email_spt('add-email', '<EMAIL>')
        nonce1 = self.alice.get_email('<EMAIL>').nonce
        self.hit_email_spt('resend', '<EMAIL>')
        nonce2 = self.alice.get_email('<EMAIL>').nonce
        assert nonce1 == nonce2
{'fraction_non_alphanumeric': 0.1182747556152526, 'fraction_numerical': 0.005521975971942392, 'mean_word_length': 3.3811703170970904, 'pattern_counts': {'":': 0, '<': 74, '<?xml version=': 0, '>': 74, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '884657', 'n_tokens_mistral': 4318, 'n_tokens_neox': 4221, 'n_words': 763}