# -*- coding: utf-8 -*-

import mock
from nose.tools import *  # noqa (PEP8 asserts)

import hmac
import hashlib
from StringIO import StringIO

from django.core.exceptions import ValidationError
from django.db import IntegrityError
import furl

from framework.auth import get_or_create_user
from framework.auth.core import Auth

from osf.models import OSFUser, AbstractNode
from addons.wiki.models import WikiVersion
from osf.exceptions import BlacklistedEmailError
from website import settings
from website.conferences import views
from website.conferences import utils, message
from website.util import api_url_for, web_url_for

from tests.base import OsfTestCase, fake
from osf_tests.factories import ConferenceFactory, ProjectFactory, UserFactory


def assert_absolute(url):
    parsed_domain = furl.furl(settings.DOMAIN)
    parsed_url = furl.furl(url)
    assert_equal(parsed_domain.host, parsed_url.host)


def assert_equal_urls(first, second):
    parsed_first = furl.furl(first)
    parsed_first.port = None
    parsed_second = furl.furl(second)
    parsed_second.port = None
    assert_equal(parsed_first, parsed_second)


def create_fake_conference_nodes(n, conference):
    nodes = []
    for i in range(n):
        node = ProjectFactory(is_public=True)
        conference.submissions.add(node)
        node.save()
        nodes.append(node)
    return nodes

def create_fake_conference_nodes_bad_data(conference, n, bad_n, endpoint):
    nodes = []
    for i in range(n):
        node = ProjectFactory(is_public=True)
        conference.submissions.add(node)
        # inject bad data
        if i < bad_n:
            # Delete only contributor
            node.contributor_set.filter(user=node.contributors.first()).delete()
        node.save()
        nodes.append(node)
    return nodes

class TestConferenceUtils(OsfTestCase):

    def test_get_or_create_user_exists(self):
        user = UserFactory()
        fetched, created = get_or_create_user(user.fullname, user.username, is_spam=True)
        assert_false(created)
        assert_equal(user._id, fetched._id)
        assert_false('is_spam' in fetched.system_tags)

    def test_get_or_create_user_not_exists(self):
        fullname = 'Roger Taylor'
        username = 'roger@queen.com'
        fetched, created = get_or_create_user(fullname, username, is_spam=False)
        fetched.save()  # in order to access m2m fields, e.g. tags
        assert_true(created)
        assert_equal(fetched.fullname, fullname)
        assert_equal(fetched.username, username)
        assert_false('is_spam' in fetched.system_tags)

    def test_get_or_create_user_is_spam(self):
        fullname = 'John Deacon'
        username = 'deacon@queen.com'
        fetched, created = get_or_create_user(fullname, username, is_spam=True)
        fetched.save()  # in order to access m2m fields, e.g. tags
        assert_true(created)
        assert_equal(fetched.fullname, fullname)
        assert_equal(fetched.username, username)
        assert_true('is_spam' in fetched.system_tags)

    def test_get_or_create_user_with_blacklisted_domain(self):
        fullname = 'Kanye West'
        username = 'kanye@mailinator.com'
        with assert_raises(BlacklistedEmailError) as e:
            get_or_create_user(fullname, username, is_spam=True)
        assert_equal(e.exception.message, 'Invalid Email')


class ContextTestCase(OsfTestCase):

    MAILGUN_API_KEY = 'mailkimp'

    @classmethod
    def setUpClass(cls):
        super(ContextTestCase, cls).setUpClass()
        settings.MAILGUN_API_KEY, cls._MAILGUN_API_KEY = cls.MAILGUN_API_KEY, settings.MAILGUN_API_KEY

    @classmethod
    def tearDownClass(cls):
        super(ContextTestCase, cls).tearDownClass()
        settings.MAILGUN_API_KEY = cls._MAILGUN_API_KEY

    def make_context(self, method='POST', **kwargs):
        data = {
            'X-Mailgun-Sscore': 0,
            'timestamp': '123',
            'token': 'secret',
            'signature': hmac.new(
                key=settings.MAILGUN_API_KEY,
                msg='{}{}'.format('123', 'secret'),
                digestmod=hashlib.sha256,
            ).hexdigest(),
        }
        data.update(kwargs.pop('data', {}))
        data = {
            key: value
            for key, value in data.items()
            if value is not None
        }
        return self.app.app.test_request_context(method=method, data=data, **kwargs)


class TestProvisionNode(ContextTestCase):

    def setUp(self):
        super(TestProvisionNode, self).setUp()
        self.node = ProjectFactory()
        self.user = self.node.creator
        self.conference = ConferenceFactory()
        self.body = 'dragon on my back'
        self.content = 'dragon attack'
        self.attachment = StringIO(self.content)
        self.recipient = '{0}{1}-poster@osf.io'.format(
            'test-' if settings.DEV_MODE else '',
            self.conference.endpoint,
        )

    def make_context(self, **kwargs):
        data = {
            'attachment-count': '1',
            'attachment-1': (self.attachment, 'attachment-1'),
            'X-Mailgun-Sscore': 0,
            'recipient': self.recipient,
            'stripped-text': self.body,
        }
        data.update(kwargs.pop('data', {}))
        return super(TestProvisionNode, self).make_context(data=data, **kwargs)

    def test_provision(self):
        with self.make_context():
            msg = message.ConferenceMessage()
            utils.provision_node(self.conference, msg, self.node, self.user)
        assert_true(self.node.is_public)
        assert_in(self.conference.admins.first(), self.node.contributors)
        assert_in('emailed', self.node.system_tags)
        assert_in(self.conference.endpoint, self.node.system_tags)
        assert self.node in self.conference.submissions.all()
        assert_not_in('spam', self.node.system_tags)

    def test_provision_private(self):
        self.conference.public_projects = False
        self.conference.save()
        with self.make_context():
            msg = message.ConferenceMessage()
            utils.provision_node(self.conference, msg, self.node, self.user)
        assert_false(self.node.is_public)
        assert_in(self.conference.admins.first(), self.node.contributors)
        assert_in('emailed', self.node.system_tags)
        assert_not_in('spam', self.node.system_tags)

    def test_provision_spam(self):
        with self.make_context(data={'X-Mailgun-Sscore': message.SSCORE_MAX_VALUE + 1}):
            msg = message.ConferenceMessage()
            utils.provision_node(self.conference, msg, self.node, self.user)
        assert_false(self.node.is_public)
        assert_in(self.conference.admins.first(), self.node.contributors)
        assert_in('emailed', self.node.system_tags)
        assert_in('spam', self.node.system_tags)

    @mock.patch('website.conferences.utils.waterbutler_api_url_for')
    @mock.patch('website.conferences.utils.requests.put')
    def test_upload(self, mock_put, mock_get_url):
        mock_get_url.return_value = 'http://queen.com/'
        file_name = 'hammer-to-fall'
        self.attachment.filename = file_name
        self.attachment.content_type = 'application/json'
        utils.upload_attachment(self.user, self.node, self.attachment)
        mock_get_url.assert_called_with(
            self.node._id,
            'osfstorage',
            _internal=True,
            base_url=self.node.osfstorage_region.waterbutler_url,
            cookie=self.user.get_or_create_cookie(),
            name=file_name
        )
        mock_put.assert_called_with(
            mock_get_url.return_value,
            data=self.content,
        )

    @mock.patch('website.conferences.utils.waterbutler_api_url_for')
    @mock.patch('website.conferences.utils.requests.put')
    def test_upload_no_file_name(self, mock_put, mock_get_url):
        mock_get_url.return_value = 'http://queen.com/'
        self.attachment.filename = ''
        self.attachment.content_type = 'application/json'
        utils.upload_attachment(self.user, self.node, self.attachment)
        mock_get_url.assert_called_with(
            self.node._id,
            'osfstorage',
            _internal=True,
            base_url=self.node.osfstorage_region.waterbutler_url,
            cookie=self.user.get_or_create_cookie(),
            name=settings.MISSING_FILE_NAME,
        )
        mock_put.assert_called_with(
            mock_get_url.return_value,
            data=self.content,
        )

    @mock.patch('website.conferences.utils.upload_attachments')
    def test_add_poster_by_email(self, mock_upload_attachments):
        conference = ConferenceFactory()

        with self.make_context(data={'from': 'bdawk@sb52champs.com', 'subject': 'It\'s PARTY TIME!'}):
            msg = message.ConferenceMessage()
            views.add_poster_by_email(conference, msg)

        user = OSFUser.objects.get(username='bdawk@sb52champs.com')
        assert user.email == 'bdawk@sb52champs.com'
        assert user.fullname == user._id  # user's shouldn't be able to use email as fullname, so we use the guid.


class TestMessage(ContextTestCase):

    PUSH_CONTEXT = False

    def test_verify_signature_valid(self):
        with self.make_context():
            msg = message.ConferenceMessage()
            msg.verify_signature()

    def test_verify_signature_invalid(self):
        with self.make_context(data={'signature': 'fake'}):
            self.app.app.preprocess_request()
            msg = message.ConferenceMessage()
            with assert_raises(message.ConferenceError):
                msg.verify_signature()

    def test_is_spam_false_missing_headers(self):
        ctx = self.make_context(
            method='POST',
            data={'X-Mailgun-Sscore': message.SSCORE_MAX_VALUE - 1},
        )
        with ctx:
            msg = message.ConferenceMessage()
            assert not msg.is_spam

    def test_is_spam_false_all_headers(self):
        ctx = self.make_context(
            method='POST',
            data={
                'X-Mailgun-Sscore': message.SSCORE_MAX_VALUE - 1,
                'X-Mailgun-Dkim-Check-Result': message.DKIM_PASS_VALUES[0],
                'X-Mailgun-Spf': message.SPF_PASS_VALUES[0],
            },
        )
        with ctx:
            msg = message.ConferenceMessage()
            assert not msg.is_spam

    def test_is_spam_true_sscore(self):
        ctx = self.make_context(
            method='POST',
            data={'X-Mailgun-Sscore': message.SSCORE_MAX_VALUE + 1},
        )
        with ctx:
            msg = message.ConferenceMessage()
            assert msg.is_spam

    def test_is_spam_true_dkim(self):
        ctx = self.make_context(
            method='POST',
            data={'X-Mailgun-Dkim-Check-Result': message.DKIM_PASS_VALUES[0][::-1]},
        )
        with ctx:
            msg = message.ConferenceMessage()
            assert msg.is_spam

    def test_is_spam_true_spf(self):
        ctx = self.make_context(
            method='POST',
            data={'X-Mailgun-Spf': message.SPF_PASS_VALUES[0][::-1]},
        )
        with ctx:
            msg = message.ConferenceMessage()
            assert msg.is_spam

    def test_subject(self):
        ctx = self.make_context(
            method='POST',
            data={'subject': 'RE: Hip Hopera'},
        )
        with ctx:
            msg = message.ConferenceMessage()
            assert_equal(msg.subject, 'Hip Hopera')

    def test_recipient(self):
        address = 'test-conference@osf.io'
        ctx = self.make_context(
            method='POST',
            data={'recipient': address},
        )
        with ctx:
            msg = message.ConferenceMessage()
            assert_equal(msg.recipient, address)

    def test_text(self):
        text = 'welcome to my nuclear family'
        ctx = self.make_context(
            method='POST',
            data={'stripped-text': text},
        )
        with ctx:
            msg = message.ConferenceMessage()
            assert_equal(msg.text, text)

    def test_sender_name(self):
        names = [
            (' Fred', 'Fred'),
            (u'Me‰¨ü', u'Me‰¨ü'),
            (u'fred@queen.com', u'fred@queen.com'),
            (u'Fred <fred@queen.com>', u'Fred'),
            (u'"Fred" <fred@queen.com>', u'Fred'),
        ]
        for name in names:
            with self.make_context(data={'from': name[0]}):
                msg = message.ConferenceMessage()
                assert_equal(msg.sender_name, name[1])

    def test_sender_email(self):
        emails = [
            (u'fred@queen.com', u'fred@queen.com'),
            (u'FRED@queen.com', u'fred@queen.com')
        ]
        for email in emails:
            with self.make_context(data={'from': email[0]}):
                msg = message.ConferenceMessage()
                assert_equal(msg.sender_email, email[1])

    def test_route_invalid_pattern(self):
        with self.make_context(data={'recipient': 'spam@osf.io'}):
            self.app.app.preprocess_request()
            msg = message.ConferenceMessage()
            with assert_raises(message.ConferenceError):
                msg.route

    def test_route_invalid_test(self):
        recipient = '{0}conf-talk@osf.io'.format('' if settings.DEV_MODE else 'stage-')
        with self.make_context(data={'recipient': recipient}):
            self.app.app.preprocess_request()
            msg = message.ConferenceMessage()
            with assert_raises(message.ConferenceError):
                msg.route

    def test_route_valid_alternate(self):
        conf = ConferenceFactory(endpoint='chocolate', active=True)
        conf.name = 'Chocolate Conference'
        conf.field_names['submission2'] = 'data'
        conf.save()
        recipient = '{0}chocolate-data@osf.io'.format('test-' if settings.DEV_MODE else '')
        with self.make_context(data={'recipient': recipient}):
            self.app.app.preprocess_request()
            msg = message.ConferenceMessage()
            assert_equal(msg.conference_name, 'chocolate')
            assert_equal(msg.conference_category, 'data')
        conf.__class__.delete(conf)

    def test_route_valid_b(self):
        recipient = '{0}conf-poster@osf.io'.format('test-' if settings.DEV_MODE else '')
        with self.make_context(data={'recipient': recipient}):
            self.app.app.preprocess_request()
            msg = message.ConferenceMessage()
            assert_equal(msg.conference_name, 'conf')
            assert_equal(msg.conference_category, 'poster')

    def test_alternate_route_invalid(self):
        recipient = '{0}chocolate-data@osf.io'.format('test-' if settings.DEV_MODE else '')
        with self.make_context(data={'recipient': recipient}):
            self.app.app.preprocess_request()
            msg = message.ConferenceMessage()
            with assert_raises(message.ConferenceError):
                msg.route

    def test_attachments_count_zero(self):
        with self.make_context(data={'attachment-count': '0'}):
            msg = message.ConferenceMessage()
            assert_equal(msg.attachments, [])

    def test_attachments_count_one(self):
        content = 'slightly mad'
        sio = StringIO(content)
        ctx = self.make_context(
            method='POST',
            data={
                'attachment-count': 1,
                'attachment-1': (sio, 'attachment-1'),
            },
        )
        with ctx:
            msg = message.ConferenceMessage()
            assert_equal(len(msg.attachments), 1)
            assert_equal(msg.attachments[0].read(), content)


class TestConferenceEmailViews(OsfTestCase):

    def test_redirect_to_meetings_url(self):
        url = '/presentations/'
        res = self.app.get(url)
        assert_equal(res.status_code, 302)
        res = res.follow()
        assert_equal(res.request.path, '/meetings/')

    def test_conference_submissions(self):
        AbstractNode.objects.all().delete()
        conference1 = ConferenceFactory()
        conference2 = ConferenceFactory()
        # Create conference nodes
        create_fake_conference_nodes(
            3,
            conference1,
        )
        create_fake_conference_nodes(
            2,
            conference2,
        )

        url = api_url_for('conference_submissions')
        res = self.app.get(url)
        assert_equal(res.json['success'], True)

    def test_conference_plain_returns_200(self):
        conference = ConferenceFactory()
        url = web_url_for('conference_results__plain', meeting=conference.endpoint)
        res = self.app.get(url)
        assert_equal(res.status_code, 200)

    def test_conference_data(self):
        conference = ConferenceFactory()

        # Create conference nodes
        n_conference_nodes = 3
        create_fake_conference_nodes(
            n_conference_nodes,
            conference,
        )
        # Create a non-conference node
        ProjectFactory()

        url = api_url_for('conference_data', meeting=conference.endpoint)
        res = self.app.get(url)
        assert_equal(res.status_code, 200)
        assert_equal(len(res.json), n_conference_nodes)

    # Regression for OSF-8864 to confirm bad project data does not make whole conference break
    def test_conference_bad_data(self):
        conference = ConferenceFactory()

        # Create conference nodes
        n_conference_nodes = 3
        n_conference_nodes_bad = 1
        create_fake_conference_nodes_bad_data(
            conference,
            n_conference_nodes,
            n_conference_nodes_bad,
            conference,
        )
        # Create a non-conference node
        ProjectFactory()

        url = api_url_for('conference_data', meeting=conference.endpoint)
        res = self.app.get(url)
        assert_equal(res.status_code, 200)
        assert_equal(len(res.json), n_conference_nodes - n_conference_nodes_bad)

    def test_conference_data_url_upper(self):
        conference = ConferenceFactory()

        # Create conference nodes
        n_conference_nodes = 3
        create_fake_conference_nodes(
            n_conference_nodes,
            conference,
        )
        # Create a non-conference node
        ProjectFactory()

        url = api_url_for('conference_data', meeting=conference.endpoint.upper())
        res = self.app.get(url)
        assert_equal(res.status_code, 200)
        assert_equal(len(res.json), n_conference_nodes)

    def test_conference_data_tag_upper(self):
        conference = ConferenceFactory()

        # Create conference nodes
        n_conference_nodes = 3
        create_fake_conference_nodes(
            n_conference_nodes,
            conference,
        )
        # Create a non-conference node
        ProjectFactory()

        url = api_url_for('conference_data', meeting=conference.endpoint)
        res = self.app.get(url)
        assert_equal(res.status_code, 200)
        assert_equal(len(res.json), n_conference_nodes)

    def test_conference_results(self):
        conference = ConferenceFactory()

        url = web_url_for('conference_results', meeting=conference.endpoint)
        res = self.app.get(url)
        assert_equal(res.status_code, 200)

    def test_confererence_results_endpoint_is_case_insensitive(self):
        ConferenceFactory(endpoint='StudySwap')
        url = web_url_for('conference_results', meeting='studyswap')
        res = self.app.get(url)
        assert_equal(res.status_code, 200)


class TestConferenceModel(OsfTestCase):

    def test_endpoint_is_required(self):
        with assert_raises(IntegrityError):
            ConferenceFactory(endpoint=None, name=fake.company()).save()

    def test_name_is_required(self):
        with assert_raises(IntegrityError):
            ConferenceFactory(endpoint='spsp2014', name=None).save()

    def test_default_field_names(self):
        conf = ConferenceFactory(endpoint='cookie', name='Cookies Conference')
        conf.save()
        assert_equal(conf.field_names['submission1'], 'poster')
        assert_equal(conf.field_names['mail_subject'], 'Presentation title')

    def test_conference_valid_submissions(self):
        conf = ConferenceFactory(endpoint='Hamburgers', name='Hamburger conference')
        conf.save()

        # 3 good nodes added
        create_fake_conference_nodes(3, conf)

        # Deleted node added
        deleted_node = ProjectFactory(is_public=True)
        deleted_node.is_deleted = True
        deleted_node.save()
        conf.submissions.add(deleted_node)

        # Private node added
        private_node = ProjectFactory(is_public=False)
        conf.submissions.add(private_node)

        assert_equal(conf.submissions.count(), 5)
        assert_equal(conf.valid_submissions.count(), 3)


class TestConferenceIntegration(ContextTestCase):

    @mock.patch('website.conferences.views.send_mail')
    @mock.patch('website.conferences.utils.upload_attachments')
    def test_integration(self, mock_upload, mock_send_mail):
        fullname = 'John Deacon'
        username = 'deacon@queen.com'
        title = 'good songs'
        conference = ConferenceFactory()
        body = 'dragon on my back'
        content = 'dragon attack'
        recipient = '{0}{1}-poster@osf.io'.format(
            'test-' if settings.DEV_MODE else '',
            conference.endpoint,
        )
        self.app.post(
            api_url_for('meeting_hook'),
            {
                'X-Mailgun-Sscore': 0,
                'timestamp': '123',
                'token': 'secret',
                'signature': hmac.new(
                    key=settings.MAILGUN_API_KEY,
                    msg='{}{}'.format('123', 'secret'),
                    digestmod=hashlib.sha256,
                ).hexdigest(),
                'attachment-count': '1',
                'X-Mailgun-Sscore': 0,
                'from': '{0} <{1}>'.format(fullname, username),
                'recipient': recipient,
                'subject': title,
                'stripped-text': body,
            },
            upload_files=[
                ('attachment-1', 'attachment-1', content),
            ],
        )
        assert_true(mock_upload.called)
        users = OSFUser.objects.filter(username=username)
        assert_equal(users.count(), 1)
        nodes = AbstractNode.objects.filter(title=title)
        assert_equal(nodes.count(), 1)
        node = nodes[0]
        assert_equal(WikiVersion.objects.get_for_node(node, 'home').content, body)
        assert_true(mock_send_mail.called)
        call_args, call_kwargs = mock_send_mail.call_args
        assert_absolute(call_kwargs['conf_view_url'])
        assert_absolute(call_kwargs['set_password_url'])
        assert_absolute(call_kwargs['profile_url'])
        assert_absolute(call_kwargs['file_url'])
        assert_absolute(call_kwargs['node_url'])

    @mock.patch('website.conferences.views.send_mail')
    def test_integration_inactive(self, mock_send_mail):
        conference = ConferenceFactory(active=False)
        fullname = 'John Deacon'
        username = 'deacon@queen.com'
        title = 'good songs'
        body = 'dragon on my back'
        recipient = '{0}{1}-poster@osf.io'.format(
            'test-' if settings.DEV_MODE else '',
            conference.endpoint,
        )
        res = self.app.post(
            api_url_for('meeting_hook'),
            {
                'X-Mailgun-Sscore': 0,
                'timestamp': '123',
                'token': 'secret',
                'signature': hmac.new(
                    key=settings.MAILGUN_API_KEY,
                    msg='{}{}'.format('123', 'secret'),
                    digestmod=hashlib.sha256,
                ).hexdigest(),
                'attachment-count': '1',
                'X-Mailgun-Sscore': 0,
                'from': '{0} <{1}>'.format(fullname, username),
                'recipient': recipient,
                'subject': title,
                'stripped-text': body,
            },
            expect_errors=True,
        )
        assert_equal(res.status_code, 406)
        call_args, call_kwargs = mock_send_mail.call_args
        assert_equal(call_args, (username, views.CONFERENCE_INACTIVE))
        assert_equal(call_kwargs['fullname'], fullname)
        assert_equal_urls(
            call_kwargs['presentations_url'],
            web_url_for('conference_view', _absolute=True),
        )

    @mock.patch('website.conferences.views.send_mail')
    @mock.patch('website.conferences.utils.upload_attachments')
    def test_integration_wo_full_name(self, mock_upload, mock_send_mail):
        username = 'no_full_name@mail.com'
        title = 'no full name only email'
        conference = ConferenceFactory()
        body = 'dragon on my back'
        content = 'dragon attack'
        recipient = '{0}{1}-poster@osf.io'.format(
            'test-' if settings.DEV_MODE else '',
            conference.endpoint,
        )
        self.app.post(
            api_url_for('meeting_hook'),
            {
                'X-Mailgun-Sscore': 0,
                'timestamp': '123',
                'token': 'secret',
                'signature': hmac.new(
                    key=settings.MAILGUN_API_KEY,
                    msg='{}{}'.format('123', 'secret'),
                    digestmod=hashlib.sha256,
                ).hexdigest(),
                'attachment-count': '1',
                'X-Mailgun-Sscore': 0,
                'from': username,
                'recipient': recipient,
                'subject': title,
                'stripped-text': body,
            },
            upload_files=[
                ('attachment-1', 'attachment-1', content),
            ],
        )
        assert_true(mock_upload.called)
        users = OSFUser.objects.filter(username=username)
        assert_equal(users.count(), 1)
        nodes = AbstractNode.objects.filter(title=title)
        assert_equal(nodes.count(), 1)
        node = nodes[0]
        assert_equal(WikiVersion.objects.get_for_node(node, 'home').content, body)
        assert_true(mock_send_mail.called)
        call_args, call_kwargs = mock_send_mail.call_args
        assert_absolute(call_kwargs['conf_view_url'])
        assert_absolute(call_kwargs['set_password_url'])
        assert_absolute(call_kwargs['profile_url'])
        assert_absolute(call_kwargs['file_url'])
        assert_absolute(call_kwargs['node_url'])

    @mock.patch('website.conferences.views.send_mail')
    @mock.patch('website.conferences.utils.upload_attachments')
    def test_create_conference_node_with_same_name_as_existing_node(self, mock_upload, mock_send_mail):
        conference = ConferenceFactory()
        user = UserFactory()
        title = 'Long Live Greg'
        ProjectFactory(creator=user, title=title)

        body = 'Greg is a good plant'
        content = 'Long may they reign.'
        recipient = '{0}{1}-poster@osf.io'.format(
            'test-' if settings.DEV_MODE else '',
            conference.endpoint,
        )
        self.app.post(
            api_url_for('meeting_hook'),
            {
                'X-Mailgun-Sscore': 0,
                'timestamp': '123',
                'token': 'secret',
                'signature': hmac.new(
                    key=settings.MAILGUN_API_KEY,
                    msg='{}{}'.format('123', 'secret'),
                    digestmod=hashlib.sha256,
                ).hexdigest(),
                'attachment-count': '1',
                'X-Mailgun-Sscore': 0,
                'from': '{0} <{1}>'.format(user.fullname, user.username),
                'recipient': recipient,
                'subject': title,
                'stripped-text': body,
            },
            upload_files=[
                ('attachment-1', 'attachment-1', content),
            ],
        )

        assert AbstractNode.objects.filter(title=title, creator=user).count() == 2
        assert mock_upload.called
        assert mock_send_mail.called

[((30, 20, 30, 46), 'furl.furl', 'furl.furl', ({(30, 30, 30, 45): 'settings.DOMAIN'}, {}), '(settings.DOMAIN)', False, 'import furl\n'), ((31, 17, 31, 31), 'furl.furl', 'furl.furl', ({(31, 27, 31, 30): 'url'}, {}), '(url)', False, 'import furl\n'), ((36, 19, 36, 35), 'furl.furl', 'furl.furl', ({(36, 29, 36, 34): 'first'}, {}), '(first)', False, 'import furl\n'), ((38, 20, 38, 37), 'furl.furl', 'furl.furl', ({(38, 30, 38, 36): 'second'}, {}), '(second)', False, 'import furl\n'), ((193, 5, 193, 68), 'mock.patch', 'mock.patch', ({(193, 16, 193, 67): '"""website.conferences.utils.waterbutler_api_url_for"""'}, {}), "('website.conferences.utils.waterbutler_api_url_for')", False, 'import mock\n'), ((194, 5, 194, 57), 'mock.patch', 'mock.patch', ({(194, 16, 194, 56): '"""website.conferences.utils.requests.put"""'}, {}), "('website.conferences.utils.requests.put')", False, 'import mock\n'), ((214, 5, 214, 68), 'mock.patch', 'mock.patch', ({(214, 16, 214, 67): '"""website.conferences.utils.waterbutler_api_url_for"""'}, {}), "('website.conferences.utils.waterbutler_api_url_for')", False, 'import mock\n'), ((215, 5, 215, 57), 'mock.patch', 'mock.patch', ({(215, 16, 215, 56): '"""website.conferences.utils.requests.put"""'}, {}), "('website.conferences.utils.requests.put')", False, 'import mock\n'), ((234, 5, 234, 63), 'mock.patch', 'mock.patch', ({(234, 16, 234, 62): '"""website.conferences.utils.upload_attachments"""'}, {}), "('website.conferences.utils.upload_attachments')", False, 'import mock\n'), ((587, 5, 587, 54), 'mock.patch', 'mock.patch', ({(587, 16, 587, 53): '"""website.conferences.views.send_mail"""'}, {}), "('website.conferences.views.send_mail')", False, 'import mock\n'), ((588, 5, 588, 63), 'mock.patch', 'mock.patch', ({(588, 16, 588, 62): '"""website.conferences.utils.upload_attachments"""'}, {}), "('website.conferences.utils.upload_attachments')", False, 'import mock\n'), ((637, 5, 637, 54), 'mock.patch', 'mock.patch', ({(637, 16, 637, 53): '"""website.conferences.views.send_mail"""'}, {}), "('website.conferences.views.send_mail')", False, 'import mock\n'), ((677, 5, 677, 54), 'mock.patch', 'mock.patch', ({(677, 16, 677, 53): '"""website.conferences.views.send_mail"""'}, {}), "('website.conferences.views.send_mail')", False, 'import mock\n'), ((678, 5, 678, 63), 'mock.patch', 'mock.patch', ({(678, 16, 678, 62): '"""website.conferences.utils.upload_attachments"""'}, {}), "('website.conferences.utils.upload_attachments')", False, 'import mock\n'), ((726, 5, 726, 54), 'mock.patch', 'mock.patch', ({(726, 16, 726, 53): '"""website.conferences.views.send_mail"""'}, {}), "('website.conferences.views.send_mail')", False, 'import mock\n'), ((727, 5, 727, 63), 'mock.patch', 'mock.patch', ({(727, 16, 727, 62): '"""website.conferences.utils.upload_attachments"""'}, {}), "('website.conferences.utils.upload_attachments')", False, 'import mock\n'), ((46, 15, 46, 45), 'osf_tests.factories.ProjectFactory', 'ProjectFactory', (), '', False, 'from osf_tests.factories import ConferenceFactory, ProjectFactory, UserFactory\n'), ((55, 15, 55, 45), 'osf_tests.factories.ProjectFactory', 'ProjectFactory', (), '', False, 'from osf_tests.factories import ConferenceFactory, ProjectFactory, UserFactory\n'), ((68, 15, 68, 28), 'osf_tests.factories.UserFactory', 'UserFactory', ({}, {}), '()', False, 'from osf_tests.factories import ConferenceFactory, ProjectFactory, UserFactory\n'), ((69, 27, 69, 89), 'framework.auth.get_or_create_user', 'get_or_create_user', (), '', False, 'from framework.auth import get_or_create_user\n'), ((77, 27, 77, 80), 'framework.auth.get_or_create_user', 'get_or_create_user', (), '', False, 'from framework.auth import get_or_create_user\n'), ((87, 27, 87, 79), 'framework.auth.get_or_create_user', 'get_or_create_user', (), '', False, 'from framework.auth import get_or_create_user\n'), ((140, 20, 140, 36), 'osf_tests.factories.ProjectFactory', 'ProjectFactory', ({}, {}), '()', False, 'from osf_tests.factories import ConferenceFactory, ProjectFactory, UserFactory\n'), ((142, 26, 142, 45), 'osf_tests.factories.ConferenceFactory', 'ConferenceFactory', ({}, {}), '()', False, 'from osf_tests.factories import ConferenceFactory, ProjectFactory, UserFactory\n'), ((145, 26, 145, 48), 'StringIO.StringIO', 'StringIO', ({(145, 35, 145, 47): 'self.content'}, {}), '(self.content)', False, 'from StringIO import StringIO\n'), ((200, 8, 200, 70), 'website.conferences.utils.upload_attachment', 'utils.upload_attachment', ({(200, 32, 200, 41): 'self.user', (200, 43, 200, 52): 'self.node', (200, 54, 200, 69): 'self.attachment'}, {}), '(self.user, self.node, self.attachment)', False, 'from website.conferences import utils, message\n'), ((220, 8, 220, 70), 'website.conferences.utils.upload_attachment', 'utils.upload_attachment', ({(220, 32, 220, 41): 'self.user', (220, 43, 220, 52): 'self.node', (220, 54, 220, 69): 'self.attachment'}, {}), '(self.user, self.node, self.attachment)', False, 'from website.conferences import utils, message\n'), ((236, 21, 236, 40), 'osf_tests.factories.ConferenceFactory', 'ConferenceFactory', ({}, {}), '()', False, 'from osf_tests.factories import ConferenceFactory, ProjectFactory, UserFactory\n'), ((242, 15, 242, 67), 'osf.models.OSFUser.objects.get', 'OSFUser.objects.get', (), '', False, 'from osf.models import OSFUser, AbstractNode\n'), ((380, 15, 380, 67), 'osf_tests.factories.ConferenceFactory', 'ConferenceFactory', (), '', False, 'from osf_tests.factories import ConferenceFactory, ProjectFactory, UserFactory\n'), ((415, 14, 415, 31), 'StringIO.StringIO', 'StringIO', ({(415, 23, 415, 30): 'content'}, {}), '(content)', False, 'from StringIO import StringIO\n'), ((440, 22, 440, 41), 'osf_tests.factories.ConferenceFactory', 'ConferenceFactory', ({}, {}), '()', False, 'from osf_tests.factories import ConferenceFactory, ProjectFactory, UserFactory\n'), ((441, 22, 441, 41), 'osf_tests.factories.ConferenceFactory', 'ConferenceFactory', ({}, {}), '()', False, 'from osf_tests.factories import ConferenceFactory, ProjectFactory, UserFactory\n'), ((452, 14, 452, 51), 'website.util.api_url_for', 'api_url_for', ({(452, 26, 452, 50): '"""conference_submissions"""'}, {}), "('conference_submissions')", False, 'from website.util import api_url_for, web_url_for\n'), ((457, 21, 457, 40), 'osf_tests.factories.ConferenceFactory', 'ConferenceFactory', ({}, {}), '()', False, 'from osf_tests.factories import ConferenceFactory, ProjectFactory, UserFactory\n'), ((458, 14, 458, 83), 'website.util.web_url_for', 'web_url_for', (), '', False, 'from website.util import api_url_for, web_url_for\n'), ((463, 21, 463, 40), 'osf_tests.factories.ConferenceFactory', 'ConferenceFactory', ({}, {}), '()', False, 'from osf_tests.factories import ConferenceFactory, ProjectFactory, UserFactory\n'), ((472, 8, 472, 24), 'osf_tests.factories.ProjectFactory', 'ProjectFactory', ({}, {}), '()', False, 'from osf_tests.factories import ConferenceFactory, ProjectFactory, UserFactory\n'), ((474, 14, 474, 73), 'website.util.api_url_for', 'api_url_for', (), '', False, 'from website.util import api_url_for, web_url_for\n'), ((481, 21, 481, 40), 'osf_tests.factories.ConferenceFactory', 'ConferenceFactory', ({}, {}), '()', False, 'from osf_tests.factories import ConferenceFactory, ProjectFactory, UserFactory\n'), ((493, 8, 493, 24), 'osf_tests.factories.ProjectFactory', 'ProjectFactory', ({}, {}), '()', False, 'from osf_tests.factories import ConferenceFactory, ProjectFactory, UserFactory\n'), ((495, 14, 495, 73), 'website.util.api_url_for', 'api_url_for', (), '', False, 'from website.util import api_url_for, web_url_for\n'), ((501, 21, 501, 40), 'osf_tests.factories.ConferenceFactory', 'ConferenceFactory', ({}, {}), '()', False, 'from osf_tests.factories import ConferenceFactory, ProjectFactory, UserFactory\n'), ((510, 8, 510, 24), 'osf_tests.factories.ProjectFactory', 'ProjectFactory', ({}, {}), '()', False, 'from osf_tests.factories import ConferenceFactory, ProjectFactory, UserFactory\n'), ((518, 21, 518, 40), 'osf_tests.factories.ConferenceFactory', 'ConferenceFactory', ({}, {}), '()', False, 'from osf_tests.factories import ConferenceFactory, ProjectFactory, UserFactory\n'), ((527, 8, 527, 24), 'osf_tests.factories.ProjectFactory', 'ProjectFactory', ({}, {}), '()', False, 'from osf_tests.factories import ConferenceFactory, ProjectFactory, UserFactory\n'), ((529, 14, 529, 73), 'website.util.api_url_for', 'api_url_for', (), '', False, 'from website.util import api_url_for, web_url_for\n'), ((535, 21, 535, 40), 'osf_tests.factories.ConferenceFactory', 'ConferenceFactory', ({}, {}), '()', False, 'from osf_tests.factories import ConferenceFactory, ProjectFactory, UserFactory\n'), ((537, 14, 537, 76), 'website.util.web_url_for', 'web_url_for', (), '', False, 'from website.util import api_url_for, web_url_for\n'), ((542, 8, 542, 47), 'osf_tests.factories.ConferenceFactory', 'ConferenceFactory', (), '', False, 'from osf_tests.factories import ConferenceFactory, ProjectFactory, UserFactory\n'), ((543, 14, 543, 68), 'website.util.web_url_for', 'web_url_for', (), '', False, 'from website.util import api_url_for, web_url_for\n'), ((559, 15, 559, 78), 'osf_tests.factories.ConferenceFactory', 'ConferenceFactory', (), '', False, 'from osf_tests.factories import ConferenceFactory, ProjectFactory, UserFactory\n'), ((565, 15, 565, 84), 'osf_tests.factories.ConferenceFactory', 'ConferenceFactory', (), '', False, 'from osf_tests.factories import ConferenceFactory, ProjectFactory, UserFactory\n'), ((572, 23, 572, 53), 'osf_tests.factories.ProjectFactory', 'ProjectFactory', (), '', False, 'from osf_tests.factories import ConferenceFactory, ProjectFactory, UserFactory\n'), ((578, 23, 578, 54), 'osf_tests.factories.ProjectFactory', 'ProjectFactory', (), '', False, 'from osf_tests.factories import ConferenceFactory, ProjectFactory, UserFactory\n'), ((593, 21, 593, 40), 'osf_tests.factories.ConferenceFactory', 'ConferenceFactory', ({}, {}), '()', False, 'from osf_tests.factories import ConferenceFactory, ProjectFactory, UserFactory\n'), ((623, 16, 623, 57), 'osf.models.OSFUser.objects.filter', 'OSFUser.objects.filter', (), '', False, 'from osf.models import OSFUser, AbstractNode\n'), ((625, 16, 625, 56), 'osf.models.AbstractNode.objects.filter', 'AbstractNode.objects.filter', (), '', False, 'from osf.models import OSFUser, AbstractNode\n'), ((639, 21, 639, 52), 'osf_tests.factories.ConferenceFactory', 'ConferenceFactory', (), '', False, 'from osf_tests.factories import ConferenceFactory, ProjectFactory, UserFactory\n'), ((682, 21, 682, 40), 'osf_tests.factories.ConferenceFactory', 'ConferenceFactory', ({}, {}), '()', False, 'from osf_tests.factories import ConferenceFactory, ProjectFactory, UserFactory\n'), ((712, 16, 712, 57), 'osf.models.OSFUser.objects.filter', 'OSFUser.objects.filter', (), '', False, 'from osf.models import OSFUser, AbstractNode\n'), ((714, 16, 714, 56), 'osf.models.AbstractNode.objects.filter', 'AbstractNode.objects.filter', (), '', False, 'from osf.models import OSFUser, AbstractNode\n'), ((729, 21, 729, 40), 'osf_tests.factories.ConferenceFactory', 'ConferenceFactory', ({}, {}), '()', False, 'from osf_tests.factories import ConferenceFactory, ProjectFactory, UserFactory\n'), ((730, 15, 730, 28), 'osf_tests.factories.UserFactory', 'UserFactory', ({}, {}), '()', False, 'from osf_tests.factories import ConferenceFactory, ProjectFactory, UserFactory\n'), ((732, 8, 732, 49), 'osf_tests.factories.ProjectFactory', 'ProjectFactory', (), '', False, 'from osf_tests.factories import ConferenceFactory, ProjectFactory, UserFactory\n'), ((98, 12, 98, 64), 'framework.auth.get_or_create_user', 'get_or_create_user', (), '', False, 'from framework.auth import get_or_create_user\n'), ((164, 18, 164, 45), 'website.conferences.message.ConferenceMessage', 'message.ConferenceMessage', ({}, {}), '()', False, 'from website.conferences import utils, message\n'), ((165, 12, 165, 76), 'website.conferences.utils.provision_node', 'utils.provision_node', ({(165, 33, 165, 48): 'self.conference', (165, 50, 165, 53): 'msg', (165, 55, 165, 64): 'self.node', (165, 66, 165, 75): 'self.user'}, {}), '(self.conference, msg, self.node, self.user)', False, 'from website.conferences import utils, message\n'), ((177, 18, 177, 45), 'website.conferences.message.ConferenceMessage', 'message.ConferenceMessage', ({}, {}), '()', False, 'from website.conferences import utils, message\n'), ((178, 12, 178, 76), 'website.conferences.utils.provision_node', 'utils.provision_node', ({(178, 33, 178, 48): 'self.conference', (178, 50, 178, 53): 'msg', (178, 55, 178, 64): 'self.node', (178, 66, 178, 75): 'self.user'}, {}), '(self.conference, msg, self.node, self.user)', False, 'from website.conferences import utils, message\n'), ((186, 18, 186, 45), 'website.conferences.message.ConferenceMessage', 'message.ConferenceMessage', ({}, {}), '()', False, 'from website.conferences import utils, message\n'), ((187, 12, 187, 76), 'website.conferences.utils.provision_node', 'utils.provision_node', ({(187, 33, 187, 48): 'self.conference', (187, 50, 187, 53): 'msg', (187, 55, 187, 64): 'self.node', (187, 66, 187, 75): 'self.user'}, {}), '(self.conference, msg, self.node, self.user)', False, 'from website.conferences import utils, message\n'), ((239, 18, 239, 45), 'website.conferences.message.ConferenceMessage', 'message.ConferenceMessage', ({}, {}), '()', False, 'from website.conferences import utils, message\n'), ((240, 12, 240, 54), 'website.conferences.views.add_poster_by_email', 'views.add_poster_by_email', ({(240, 38, 240, 48): 'conference', (240, 50, 240, 53): 'msg'}, {}), '(conference, msg)', False, 'from website.conferences import views\n'), ((253, 18, 253, 45), 'website.conferences.message.ConferenceMessage', 'message.ConferenceMessage', ({}, {}), '()', False, 'from website.conferences import utils, message\n'), ((259, 18, 259, 45), 'website.conferences.message.ConferenceMessage', 'message.ConferenceMessage', ({}, {}), '()', False, 'from website.conferences import utils, message\n'), ((269, 18, 269, 45), 'website.conferences.message.ConferenceMessage', 'message.ConferenceMessage', ({}, {}), '()', False, 'from website.conferences import utils, message\n'), ((282, 18, 282, 45), 'website.conferences.message.ConferenceMessage', 'message.ConferenceMessage', ({}, {}), '()', False, 'from website.conferences import utils, message\n'), ((291, 18, 291, 45), 'website.conferences.message.ConferenceMessage', 'message.ConferenceMessage', ({}, {}), '()', False, 'from website.conferences import utils, message\n'), ((300, 18, 300, 45), 'website.conferences.message.ConferenceMessage', 'message.ConferenceMessage', ({}, {}), '()', False, 'from website.conferences import utils, message\n'), ((309, 18, 309, 45), 'website.conferences.message.ConferenceMessage', 'message.ConferenceMessage', ({}, {}), '()', False, 'from website.conferences import utils, message\n'), ((318, 18, 318, 45), 'website.conferences.message.ConferenceMessage', 'message.ConferenceMessage', ({}, {}), '()', False, 'from website.conferences import utils, message\n'), ((328, 18, 328, 45), 'website.conferences.message.ConferenceMessage', 'message.ConferenceMessage', ({}, {}), '()', False, 'from website.conferences import utils, message\n'), ((338, 18, 338, 45), 'website.conferences.message.ConferenceMessage', 'message.ConferenceMessage', ({}, {}), '()', False, 'from website.conferences import utils, message\n'), ((367, 18, 367, 45), 'website.conferences.message.ConferenceMessage', 'message.ConferenceMessage', ({}, {}), '()', False, 'from website.conferences import utils, message\n'), ((375, 18, 375, 45), 'website.conferences.message.ConferenceMessage', 'message.ConferenceMessage', ({}, {}), '()', False, 'from website.conferences import utils, message\n'), ((387, 18, 387, 45), 'website.conferences.message.ConferenceMessage', 'message.ConferenceMessage', ({}, {}), '()', False, 'from website.conferences import utils, message\n'), ((396, 18, 396, 45), 'website.conferences.message.ConferenceMessage', 'message.ConferenceMessage', ({}, {}), '()', False, 'from website.conferences import utils, message\n'), ((404, 18, 404, 45), 'website.conferences.message.ConferenceMessage', 'message.ConferenceMessage', ({}, {}), '()', False, 'from website.conferences import utils, message\n'), ((410, 18, 410, 45), 'website.conferences.message.ConferenceMessage', 'message.ConferenceMessage', ({}, {}), '()', False, 'from website.conferences import utils, message\n'), ((424, 18, 424, 45), 'website.conferences.message.ConferenceMessage', 'message.ConferenceMessage', ({}, {}), '()', False, 'from website.conferences import utils, message\n'), ((601, 12, 601, 39), 'website.util.api_url_for', 'api_url_for', ({(601, 24, 601, 38): '"""meeting_hook"""'}, {}), "('meeting_hook')", False, 'from website.util import api_url_for, web_url_for\n'), ((649, 12, 649, 39), 'website.util.api_url_for', 'api_url_for', ({(649, 24, 649, 38): '"""meeting_hook"""'}, {}), "('meeting_hook')", False, 'from website.util import api_url_for, web_url_for\n'), ((674, 12, 674, 58), 'website.util.web_url_for', 'web_url_for', (), '', False, 'from website.util import api_url_for, web_url_for\n'), ((690, 12, 690, 39), 'website.util.api_url_for', 'api_url_for', ({(690, 24, 690, 38): '"""meeting_hook"""'}, {}), "('meeting_hook')", False, 'from website.util import api_url_for, web_url_for\n'), ((741, 12, 741, 39), 'website.util.api_url_for', 'api_url_for', ({(741, 24, 741, 38): '"""meeting_hook"""'}, {}), "('meeting_hook')", False, 'from website.util import api_url_for, web_url_for\n'), ((351, 22, 351, 49), 'website.conferences.message.ConferenceMessage', 'message.ConferenceMessage', ({}, {}), '()', False, 'from website.conferences import utils, message\n'), ((361, 22, 361, 49), 'website.conferences.message.ConferenceMessage', 'message.ConferenceMessage', ({}, {}), '()', False, 'from website.conferences import utils, message\n'), ((439, 8, 439, 34), 'osf.models.AbstractNode.objects.all', 'AbstractNode.objects.all', ({}, {}), '()', False, 'from osf.models import OSFUser, AbstractNode\n'), ((628, 21, 628, 67), 'addons.wiki.models.WikiVersion.objects.get_for_node', 'WikiVersion.objects.get_for_node', ({(628, 54, 628, 58): 'node', (628, 60, 628, 66): '"""home"""'}, {}), "(node, 'home')", False, 'from addons.wiki.models import WikiVersion\n'), ((717, 21, 717, 67), 'addons.wiki.models.WikiVersion.objects.get_for_node', 'WikiVersion.objects.get_for_node', ({(717, 54, 717, 58): 'node', (717, 60, 717, 66): '"""home"""'}, {}), "(node, 'home')", False, 'from addons.wiki.models import WikiVersion\n'), ((556, 12, 556, 61), 'osf_tests.factories.ConferenceFactory', 'ConferenceFactory', (), '', False, 'from osf_tests.factories import ConferenceFactory, ProjectFactory, UserFactory\n'), ((763, 15, 763, 69), 'osf.models.AbstractNode.objects.filter', 'AbstractNode.objects.filter', (), '', False, 'from osf.models import OSFUser, AbstractNode\n'), ((552, 50, 552, 64), 'tests.base.fake.company', 'fake.company', ({}, {}), '()', False, 'from tests.base import OsfTestCase, fake\n')]