Example #1
0
    def test_html_template(self):
        '''Assert that we may add an additional text template to the email
        engine.
        '''

        factory = EmailFactory('*****@*****.**',
                               'test_subject.txt',
                               'test.txt',
                               'plugin.email')
        factory.add_text_template('test.html', 'html')

        msg = factory.build('*****@*****.**',
                            test_parameter='value')

        # Assert that the message is multipart
        self.assertTrue(msg.is_multipart())
        self.assertEqual(2, len(msg.get_payload()))

        payload_text = msg.get_payload(0)
        self.assertEqual('text/plain; charset="utf-8"',
                         payload_text.get('Content-Type'))
        self.assertEqual(b'value',
                         payload_text.get_payload(decode=True))

        payload_html = msg.get_payload(1)
        self.assertEqual('text/html; charset="utf-8"',
                         payload_html.get('Content-Type'))
        self.assertEqual(b'value',
                         payload_html.get_payload(decode=True))
Example #2
0
    def test_simple_build(self):
        """Assert that a simple build provides an email.
        """
        factory = EmailFactory('*****@*****.**',
                               'test_subject',
                               'test.txt',
                               'plugin.email')

        msg = factory.build('*****@*****.**',
                            test_parameter='value')

        # Assert that the message is multipart
        self.assertTrue(msg.is_multipart())
        self.assertEqual(1, len(msg.get_payload()))

        # Test message headers
        self.assertEqual('*****@*****.**', msg.get('From'))
        self.assertEqual('*****@*****.**', msg.get('To'))
        self.assertEqual('test_subject', msg.get('Subject'))
        self.assertEqual('auto-generated', msg.get('Auto-Submitted'))
        self.assertEqual('multipart/alternative', msg.get('Content-Type'))
        self.assertIsNotNone(msg.get('Date'))  # This will vary

        payload_text = msg.get_payload(0)
        self.assertEqual('text/plain; charset="utf-8"',
                         payload_text.get('Content-Type'))
        self.assertEqual(b'value',
                         payload_text.get_payload(decode=True))

        # Assert that there's only one payload.
        self.assertEqual(1, len(msg.get_payload()))
Example #3
0
    def test_html_template(self):
        '''Assert that we may add an additional text template to the email
        engine.
        '''

        factory = EmailFactory('*****@*****.**',
                               'test_subject',
                               'test.txt',
                               'plugin.email')
        factory.add_text_template('test.html', 'html')

        msg = factory.build('*****@*****.**',
                            test_parameter='value')

        # Assert that the message is multipart
        self.assertTrue(msg.is_multipart())
        self.assertEqual(2, len(msg.get_payload()))

        payload_text = msg.get_payload(0)
        self.assertEqual('text/plain; charset="utf-8"',
                         payload_text.get('Content-Type'))
        self.assertEqual(b'value',
                         payload_text.get_payload(decode=True))

        payload_html = msg.get_payload(1)
        self.assertEqual('text/html; charset="utf-8"',
                         payload_html.get('Content-Type'))
        self.assertEqual(b'value',
                         payload_html.get_payload(decode=True))
Example #4
0
    def test_simple_build(self):
        """Assert that a simple build provides an email.
        """
        factory = EmailFactory('*****@*****.**',
                               'test_subject.txt',
                               'test.txt',
                               'plugin.email')

        msg = factory.build('*****@*****.**',
                            test_parameter='value')

        # Assert that the message is multipart
        self.assertTrue(msg.is_multipart())
        self.assertEqual(1, len(msg.get_payload()))

        # Test message headers
        self.assertEqual('*****@*****.**', msg.get('From'))
        self.assertEqual('*****@*****.**', msg.get('To'))
        self.assertEqual('value', msg.get('Subject'))
        self.assertEqual('auto-generated', msg.get('Auto-Submitted'))
        self.assertEqual('multipart/alternative', msg.get('Content-Type'))
        self.assertIsNotNone(msg.get('Date'))  # This will vary

        payload_text = msg.get_payload(0)
        self.assertEqual('text/plain; charset="utf-8"',
                         payload_text.get('Content-Type'))
        self.assertEqual(b'value',
                         payload_text.get_payload(decode=True))

        # Assert that there's only one payload.
        self.assertEqual(1, len(msg.get_payload()))
Example #5
0
    def test_uuid_ids(self):
        '''Assert that our message id's are not thread-based.'''
        outbox = Outbox()
        self.assertIsNotNone(outbox)

        factory = EmailFactory('*****@*****.**',
                               'test_subject',
                               'test.txt',
                               'plugin.email')
        for i in range(0, 100):
            recipient = '*****@*****.**' % (i,)
            message = factory.build(recipient, test_parameter=i)

            # This will throw an error if the message ID already exists.
            key = outbox.add(message)
            parts = re.match(r'^([0-9]+)M([0-9]+)_(.*)$', key)
            self.assertIsNotNone(parts)

            # The first part should be a timestamp.
            timestamp = float(parts.group(1)) + (float(parts.group(2)) / 1e6)
            send_date = datetime.datetime.fromtimestamp(timestamp)
            self.assertIsNotNone(send_date)

            # The last part should be a UUID. Trying to construct it will
            # cause a value error if it is not.
            uuid.UUID(parts.group(3), version=4)

        outbox.flush()
Example #6
0
    def test_subject_template(self):
        """Assert that the subject is templateable."""

        factory = EmailFactory('*****@*****.**',
                               'test_subject.txt',
                               'test.txt',
                               'plugin.email')
        msg = factory.build('*****@*****.**',
                            test_parameter='value')
        self.assertEqual('value', msg.get('Subject'))

        # Assert that the subject is trimmed. and appended with an ellipsis.
        factory = EmailFactory('*****@*****.**',
                               'test_long_subject.txt',
                               'test.txt',
                               'plugin.email')
        msg = factory.build('*****@*****.**',
                            test_parameter='value')
        self.assertEqual(78, len(msg.get('Subject')))
        self.assertEqual('...', msg.get('Subject')[-3:])

        # Assert that the subject has newlines trimmed
        factory = EmailFactory('*****@*****.**',
                               'test_subject_newline.txt',
                               'test.txt',
                               'plugin.email')
        msg = factory.build('*****@*****.**',
                            test_parameter='value')
        self.assertEqual('with newline', msg.get('Subject'))
Example #7
0
    def test_basic_outbox_functions(self):
        '''Assert that we can list elements of our outbox.'''
        outbox = Outbox()
        self.assertIsNotNone(outbox)

        factory = EmailFactory('*****@*****.**',
                               'test_subject',
                               'test.txt',
                               'plugin.email')
        for i in range(0, 100):
            recipient = '*****@*****.**' % (i,)
            message = factory.build(recipient, test_parameter=i)

            # This will throw an error if the message ID already exists.
            outbox.add(message)
        outbox.flush()

        values = []
        for key, email in six.iteritems(outbox):
            # Assert that the type of the email is correct.
            self.assertIsInstance(email, Message)

            # Assert that the message has a date header. While we can extract
            # this from the ID, it's also helpful for us to timebox the
            # emails to send by worker.
            self.assertIsNotNone(email.get('Date'))

            # Make sure we only have one payload (since that's what the
            # factory was configured with)
            self.assertEqual(1, len(email.get_payload()))

            # Pull the ID from the payload and check it against all the
            # emails we've found so far.
            text_part = email.get_payload(0)
            key = int(text_part.get_payload(decode=True))
            self.assertNotIn(key, values)

            # Store the key for later comparison.
            values.append(key)

        self.assertEqual(100, len(values))
Example #8
0
    def test_no_template(self):
        """Assert that attempting to load an invalid template raises an
        exception.
        """
        try:
            EmailFactory('*****@*****.**',
                         'test_subject',
                         'invalid.txt',
                         'plugin.email')
            self.assertFalse(True)
        except TemplateNotFound:
            self.assertFalse(False)

        try:
            factory = EmailFactory('*****@*****.**',
                                   'test_subject',
                                   'test.txt',
                                   'plugin.email')
            factory.add_text_template('invalid.html', 'html')
            self.assertFalse(True)
        except TemplateNotFound:
            self.assertFalse(False)
Example #9
0
    def test_no_template(self):
        """Assert that attempting to load an invalid template raises an
        exception.
        """
        try:
            EmailFactory('*****@*****.**',
                         'invalid_subject.txt',
                         'invalid.txt',
                         'plugin.email')
            self.assertFalse(True)
        except TemplateNotFound:
            self.assertFalse(False)

        try:
            factory = EmailFactory('*****@*****.**',
                                   'test_subject.txt',
                                   'test.txt',
                                   'plugin.email')
            factory.add_text_template('invalid.html', 'html')
            self.assertFalse(True)
        except TemplateNotFound:
            self.assertFalse(False)
Example #10
0
    def test_subject_template(self):
        """Assert that the subject is templateable."""

        factory = EmailFactory('*****@*****.**',
                               '{{test_parameter}}',
                               'test.txt',
                               'plugin.email')
        msg = factory.build('*****@*****.**',
                            test_parameter='value')
        self.assertEqual('value', msg.get('Subject'))

        # Assert that the subject is trimmed. and appended with an ellipsis.
        test_subject = ('a' * 100)
        factory = EmailFactory('*****@*****.**',
                               test_subject,
                               'test.txt',
                               'plugin.email')
        msg = factory.build('*****@*****.**',
                            test_parameter='value')
        self.assertEqual(78, len(msg.get('Subject')))
        self.assertEqual('...', msg.get('Subject')[-3:])

        # Assert that the subject has unix newlines trimmed
        factory = EmailFactory('*****@*****.**',
                               'with\nnewline',
                               'test.txt',
                               'plugin.email')
        msg = factory.build('*****@*****.**',
                            test_parameter='value')
        self.assertEqual('with newline', msg.get('Subject'))

        # Assert that the subject has windows returns trimmed
        factory = EmailFactory('*****@*****.**',
                               'with\r\nnewline',
                               'test.txt',
                               'plugin.email')
        msg = factory.build('*****@*****.**',
                            test_parameter='value')
        self.assertEqual('with newline', msg.get('Subject'))
Example #11
0
    def test_custom_headers(self):
        """Assert that we can set custom headers."""

        factory = EmailFactory('*****@*****.**',
                               'test_subject.txt',
                               'test.txt',
                               'plugin.email')
        custom_headers = {
            'X-Custom-Header': 'test-header-value'
        }
        for name, value in six.iteritems(custom_headers):
            factory.add_header(name, value)

        msg = factory.build('*****@*****.**',
                            test_parameter='value')
        self.assertEqual('test-header-value',
                         msg.get('X-Custom-Header'))

        # test that headers may be overridden, and that we don't end up with
        # duplicate subjects.
        factory.add_header('Subject', 'new_subject')
        msg = factory.build('*****@*****.**',
                            test_parameter='value')
        self.assertEqual('new_subject', msg.get('Subject'))
Example #12
0
    def test_custom_headers(self):
        """Assert that we can set custom headers."""

        factory = EmailFactory('*****@*****.**',
                               'test_subject',
                               'test.txt',
                               'plugin.email')
        custom_headers = {
            'X-Custom-Header': 'test-header-value'
        }
        for name, value in six.iteritems(custom_headers):
            factory.add_header(name, value)

        msg = factory.build('*****@*****.**',
                            test_parameter='value')
        self.assertEqual('test-header-value',
                         msg.get('X-Custom-Header'))

        # test that headers may be overridden, and that we don't end up with
        # duplicate subjects.
        factory.add_header('Subject', 'new_subject')
        msg = factory.build('*****@*****.**',
                            test_parameter='value')
        self.assertEqual('new_subject', msg.get('Subject'))
Example #13
0
    def handle_email(self,
                     session,
                     author,
                     subscribers,
                     method,
                     url,
                     path,
                     query_string,
                     status,
                     resource,
                     resource_id,
                     sub_resource=None,
                     sub_resource_id=None,
                     resource_before=None,
                     resource_after=None):
        """Send an email for a specific event.

        We assume that filtering logic has already occurred when this method
        is invoked.

        :param session: An event-specific SQLAlchemy session.
        :param author: The author's user record.
        :param subscribers: A list of subscribers that should receive an email.
        :param method: The HTTP Method.
        :param url: The Referer header from the request.
        :param path: The full HTTP Path requested.
        :param query_string: The query string from the request.
        :param status: The returned HTTP Status of the response.
        :param resource: The resource type.
        :param resource_id: The ID of the resource.
        :param sub_resource: The subresource type.
        :param sub_resource_id: The ID of the subresource.
        :param resource_before: The resource state before this event occurred.
        :param resource_after: The resource state after this event occurred.
        """

        email_config = CONF.plugin_email

        # Retrieve the template names.
        (subject_template, text_template, html_template) = \
            self.get_templates(method=method,
                               resource_name=resource,
                               sub_resource_name=sub_resource)

        # Build our factory. If an HTML template exists, add it. If it can't
        # find the template, skip.
        try:
            factory = EmailFactory(sender=email_config.sender,
                                   subject=subject_template,
                                   text_template=text_template)
        except TemplateNotFound:
            LOG.error("Templates not found [%s, %s]" %
                      (subject_template, text_template))
            return

        # Try to add an HTML template
        try:
            factory.add_text_template(html_template, 'html')
        except TemplateNotFound:
            LOG.debug('Template %s not found' % (html_template, ))

        # If there's a reply-to in our config, add that.
        if email_config.reply_to:
            factory.add_header('Reply-To', email_config.reply_to)

        # If there is a fallback URL configured, use it if needed
        if email_config.default_url and url is None:
            url = email_config.default_url

        # Resolve the resource instance
        resource_instance = self.resolve_resource_by_name(
            session, resource, resource_id)
        sub_resource_instance = self.resolve_resource_by_name(
            session, sub_resource, sub_resource_id)

        # Set In-Reply-To message id for 'task', 'story', and
        # 'worklist' resources
        story_id = None
        worklist_id = None
        if resource == 'task' and method == 'DELETE':
            # FIXME(pedroalvarez): Workaround the fact that the task won't be
            # in the database anymore if it has been deleted.
            # We should archive instead of delete to solve this.
            story_id = resource_before['story_id']
            created_at = self.resolve_resource_by_name(session, 'story',
                                                       story_id).created_at
        elif resource == 'task':
            story_id = resource_instance.story.id
            created_at = resource_instance.story.created_at
        elif resource == 'story':
            story_id = resource_instance.id
            created_at = resource_instance.created_at
        elif resource == 'worklist':
            worklist_id = resource_instance.id
            created_at = resource_instance.created_at

        if story_id and created_at:
            thread_id = "<storyboard.story.%s.%s@%s>" % (
                created_at.strftime("%Y%m%d%H%M"), story_id, getfqdn())
        elif worklist_id and created_at:
            thread_id = "<storyboard.worklist.%s.%s@%s>" % (
                created_at.strftime("%Y%m%d%H%M"), story_id, getfqdn())
        else:
            thread_id = make_msgid()

        factory.add_header("In-Reply-To", thread_id)
        factory.add_header("X-StoryBoard-Subscription-Type", resource)

        # Figure out the diff between old and new.
        before, after = self.get_changed_properties(resource_before,
                                                    resource_after)

        # For each subscriber, create the email and send it.
        with smtp.get_smtp_client() as smtp_client:
            for subscriber in subscribers:

                # Make sure this subscriber's preferences indicate they want
                # email and they're not receiving digests.
                if not self.get_preference('plugin_email_enable', subscriber) \
                        or self.get_preference('plugin_email_digest',
                                               subscriber):
                    continue

                send_notification = self.get_preference(
                    'receive_notifications_worklists', subscriber)
                if send_notification != 'true' and story_id is None:
                    continue

                # Don't send a notification if the user isn't allowed to see
                # the thing this event is about.
                if 'event_type' in resource:
                    event = events_api.event_get(resource['id'],
                                                 current_user=subscriber.id,
                                                 session=session)
                    if not events_api.is_visible(
                            event, subscriber.id, session=session):
                        continue

                try:
                    # Build an email.
                    message = factory.build(recipient=subscriber.email,
                                            author=author,
                                            resource=resource_instance,
                                            sub_resource=sub_resource_instance,
                                            url=url,
                                            query_string=query_string,
                                            before=before,
                                            after=after)
                    # Send the email.
                    from_addr = message.get('From')
                    to_addrs = message.get('To')

                    try:
                        smtp_client.sendmail(from_addr=from_addr,
                                             to_addrs=to_addrs,
                                             msg=message.as_string())
                    except smtplib.SMTPException as e:
                        LOG.error('Cannot send email, discarding: %s' % (e, ))
                except Exception as e:
                    # Skip, keep going.
                    LOG.error("Cannot schedule email: %s" % (e.message, ))
Example #14
0
    def handle_email(self,
                     session,
                     author,
                     subscribers,
                     method,
                     url,
                     path,
                     query_string,
                     status,
                     resource,
                     resource_id,
                     sub_resource=None,
                     sub_resource_id=None,
                     resource_before=None,
                     resource_after=None):
        """Send an email for a specific event.

        We assume that filtering logic has already occurred when this method
        is invoked.

        :param session: An event-specific SQLAlchemy session.
        :param author: The author's user record.
        :param subscribers: A list of subscribers that should receive an email.
        :param method: The HTTP Method.
        :param url: The Referer header from the request.
        :param path: The full HTTP Path requested.
        :param query_string: The query string from the request.
        :param status: The returned HTTP Status of the response.
        :param resource: The resource type.
        :param resource_id: The ID of the resource.
        :param sub_resource: The subresource type.
        :param sub_resource_id: The ID of the subresource.
        :param resource_before: The resource state before this event occurred.
        :param resource_after: The resource state after this event occurred.
        """

        email_config = CONF.plugin_email

        # Retrieve the template names.
        (subject_template, text_template, html_template) = \
            self.get_templates(method=method,
                               resource_name=resource,
                               sub_resource_name=sub_resource)

        # Build our factory. If an HTML template exists, add it. If it can't
        # find the template, skip.
        try:
            factory = EmailFactory(sender=email_config.sender,
                                   subject=subject_template,
                                   text_template=text_template)
        except TemplateNotFound:
            LOG.error("Templates not found [%s, %s]" %
                      (subject_template, text_template))
            return

        # Try to add an HTML template
        try:
            factory.add_text_template(html_template, 'html')
        except TemplateNotFound:
            LOG.debug('Template %s not found' % (html_template, ))

        # If there's a reply-to in our config, add that.
        if email_config.reply_to:
            factory.add_header('Reply-To', email_config.reply_to)

        # Resolve the resource instance
        resource_instance = self.resolve_resource_by_name(
            session, resource, resource_id)
        sub_resource_instance = self.resolve_resource_by_name(
            session, sub_resource, sub_resource_id)

        # Figure out the diff between old and new.
        before, after = self.get_changed_properties(resource_before,
                                                    resource_after)

        # For each subscriber, create the email and send it.
        with smtp.get_smtp_client() as smtp_client:
            for subscriber in subscribers:

                # Make sure this subscriber's preferences indicate they want
                # email and they're not receiving digests.
                if not self.get_preference('plugin_email_enable', subscriber) \
                        or self.get_preference('plugin_email_digest',
                                               subscriber):
                    continue

                try:
                    # Build an email.
                    message = factory.build(recipient=subscriber.email,
                                            author=author,
                                            resource=resource_instance,
                                            sub_resource=sub_resource_instance,
                                            url=url,
                                            query_string=query_string,
                                            before=before,
                                            after=after)
                    # Send the email.
                    from_addr = message.get('From')
                    to_addrs = message.get('To')

                    try:
                        smtp_client.sendmail(from_addr=from_addr,
                                             to_addrs=to_addrs,
                                             msg=message.as_string())
                    except smtplib.SMTPException as e:
                        LOG.error('Cannot send email, discarding: %s' % (e, ))
                except Exception as e:
                    # Skip, keep going.
                    LOG.error("Cannot schedule email: %s" % (e.message, ))
Example #15
0
    def handle_email(self, session, author, subscribers, method, url, path,
                     query_string, status, resource, resource_id,
                     sub_resource=None, sub_resource_id=None,
                     resource_before=None, resource_after=None):
        """Send an email for a specific event.

        We assume that filtering logic has already occurred when this method
        is invoked.

        :param session: An event-specific SQLAlchemy session.
        :param author: The author's user record.
        :param subscribers: A list of subscribers that should receive an email.
        :param method: The HTTP Method.
        :param url: The Referer header from the request.
        :param path: The full HTTP Path requested.
        :param query_string: The query string from the request.
        :param status: The returned HTTP Status of the response.
        :param resource: The resource type.
        :param resource_id: The ID of the resource.
        :param sub_resource: The subresource type.
        :param sub_resource_id: The ID of the subresource.
        :param resource_before: The resource state before this event occurred.
        :param resource_after: The resource state after this event occurred.
        """

        email_config = CONF.plugin_email

        # Retrieve the template names.
        (subject_template, text_template, html_template) = \
            self.get_templates(method=method,
                               resource_name=resource,
                               sub_resource_name=sub_resource)

        # Build our factory. If an HTML template exists, add it. If it can't
        # find the template, skip.
        try:
            factory = EmailFactory(sender=email_config.sender,
                                   subject=subject_template,
                                   text_template=text_template)
        except TemplateNotFound:
            LOG.error("Templates not found [%s, %s]" % (subject_template,
                                                        text_template))
            return

        # Try to add an HTML template
        try:
            factory.add_text_template(html_template, 'html')
        except TemplateNotFound:
            LOG.debug('Template %s not found' % (html_template,))

        # If there's a reply-to in our config, add that.
        if email_config.reply_to:
            factory.add_header('Reply-To', email_config.reply_to)

        # Resolve the resource instance
        resource_instance = self.resolve_resource_by_name(session, resource,
                                                          resource_id)
        sub_resource_instance = self.resolve_resource_by_name(session,
                                                              sub_resource,
                                                              sub_resource_id)

        # Figure out the diff between old and new.
        before, after = self.get_changed_properties(resource_before,
                                                    resource_after)

        # For each subscriber, create the email and send it.
        with smtp.get_smtp_client() as smtp_client:
            for subscriber in subscribers:

                # Make sure this subscriber's preferences indicate they want
                # email and they're not receiving digests.
                if not self.get_preference('plugin_email_enable', subscriber) \
                        or self.get_preference('plugin_email_digest',
                                               subscriber):
                    continue

                try:
                    # Build an email.
                    message = factory.build(recipient=subscriber.email,
                                            author=author,
                                            resource=resource_instance,
                                            sub_resource=sub_resource_instance,
                                            url=url,
                                            query_string=query_string,
                                            before=before,
                                            after=after)
                    # Send the email.
                    from_addr = message.get('From')
                    to_addrs = message.get('To')

                    try:
                        smtp_client.sendmail(from_addr=from_addr,
                                             to_addrs=to_addrs,
                                             msg=message.as_string())
                    except smtplib.SMTPException as e:
                        LOG.error('Cannot send email, discarding: %s' % (e,))
                except Exception as e:
                    # Skip, keep going.
                    LOG.error("Cannot schedule email: %s" % (e.message,))