Repository: allura Updated Branches: refs/heads/db/8132 c620dab54 -> b93736c55 (forced update)
[#8132] thread messages by References: if missing In-Reply-To: Project: http://git-wip-us.apache.org/repos/asf/allura/repo Commit: http://git-wip-us.apache.org/repos/asf/allura/commit/b93736c5 Tree: http://git-wip-us.apache.org/repos/asf/allura/tree/b93736c5 Diff: http://git-wip-us.apache.org/repos/asf/allura/diff/b93736c5 Branch: refs/heads/db/8132 Commit: b93736c558584bc0bb6276d8f31b288ec6e1b87f Parents: 67b553c Author: Dave Brondsema <d...@brondsema.net> Authored: Wed Oct 5 13:59:16 2016 -0500 Committer: Dave Brondsema <d...@brondsema.net> Committed: Wed Oct 5 16:55:11 2016 -0500 ---------------------------------------------------------------------- Allura/allura/app.py | 2 +- ForgeDiscussion/forgediscussion/model/forum.py | 18 ++++----- .../tests/functional/test_forum.py | 42 +++++++++++++++++--- 3 files changed, 45 insertions(+), 17 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/allura/blob/b93736c5/Allura/allura/app.py ---------------------------------------------------------------------- diff --git a/Allura/allura/app.py b/Allura/allura/app.py index f155514..22190e6 100644 --- a/Allura/allura/app.py +++ b/Allura/allura/app.py @@ -703,7 +703,7 @@ class Application(object): post_id=message_id, artifact_id=message_id) return - # Handle duplicates + # Handle duplicates (from multipart mail messages) post = self.PostClass.query.get(_id=message_id) if post: log.info( http://git-wip-us.apache.org/repos/asf/allura/blob/b93736c5/ForgeDiscussion/forgediscussion/model/forum.py ---------------------------------------------------------------------- diff --git a/ForgeDiscussion/forgediscussion/model/forum.py b/ForgeDiscussion/forgediscussion/model/forum.py index 675efa2..bab3edf 100644 --- a/ForgeDiscussion/forgediscussion/model/forum.py +++ b/ForgeDiscussion/forgediscussion/model/forum.py @@ -111,19 +111,17 @@ class Forum(M.Discussion): def get_discussion_thread(self, data=None): # If the data is a reply, use the parent's thread subject = '[no subject]' - parent_id = None if data is not None: - in_reply_to = data.get('in_reply_to') - if in_reply_to: - parent_id = in_reply_to[0].split('/')[-1] - else: - parent_id = None message_id = data.get('message_id') or '' subject = data['headers'].get('Subject', subject) - if parent_id is not None: - parent = self.post_class().query.get(_id=parent_id) - if parent: - return parent.thread, parent_id + in_reply_to = data.get('in_reply_to') or [] + references = data.get('references') or [] + # find first valid In-Reply-To: header or References: header (starting from end) + for msg_id in in_reply_to + list(reversed(references)): + parent_id = msg_id.split('/')[-1] + parent = self.post_class().query.get(_id=parent_id) + if parent: + return parent.thread, parent_id if message_id: post = self.post_class().query.get(_id=message_id) if post: http://git-wip-us.apache.org/repos/asf/allura/blob/b93736c5/ForgeDiscussion/forgediscussion/tests/functional/test_forum.py ---------------------------------------------------------------------- diff --git a/ForgeDiscussion/forgediscussion/tests/functional/test_forum.py b/ForgeDiscussion/forgediscussion/tests/functional/test_forum.py index 99df83b..a8c3283 100644 --- a/ForgeDiscussion/forgediscussion/tests/functional/test_forum.py +++ b/ForgeDiscussion/forgediscussion/tests/functional/test_forum.py @@ -25,6 +25,8 @@ from email.mime.image import MIMEImage from email.mime.multipart import MIMEMultipart import pkg_resources +import pymongo + from ming.odm import ThreadLocalORMSession from pylons import tmpl_context as c @@ -129,7 +131,11 @@ class TestForumEmail(TestController): M.artifact_orm_session.flush() -class TestForumAsync(TestController): +class TestForumMessageHandling(TestController): + ''' + Tests all the "handle_message" related logic, which is what inbound emails run through + ''' + def setUp(self): TestController.setUp(self) self.app.get('/discussion/') @@ -166,17 +172,38 @@ class TestForumAsync(TestController): posts = FM.ForumPost.query.find() assert_equal(posts.count(), 1) assert_equal(FM.ForumThread.query.get().num_replies, 1) - assert_equal(FM.ForumThread.query.get() - .first_post_id, 'test_re...@domain.net') + assert_equal(FM.ForumThread.query.get().first_post_id, 'test_re...@domain.net') post = posts.first() self._post('testforum', 'Test Reply', 'Nothing here, either', - message_id=post.thread.url() + post._id, + message_id='test_reply-m...@domain.net', in_reply_to=['test_re...@domain.net']) assert_equal(FM.ForumThread.query.find().count(), 1) assert_equal(FM.ForumPost.query.find().count(), 2) - assert_equal(FM.ForumThread.query.get() - .first_post_id, 'test_re...@domain.net') + assert_equal(FM.ForumThread.query.get().first_post_id, 'test_re...@domain.net') + + def test_reply_using_references_headers(self): + self._post('testforum', 'Test Thread', 'Nothing here', + message_id='first-message-id') + prev_post = FM.ForumPost.query.find().first() + thread = FM.ForumThread.query.find().first() + + refs = M.Notification._references(thread, prev_post) + ['first-message-id'] + self._post('testforum', 'Test Thread', 'Nothing here, yet', + message_id='second-message-id', + in_reply_to=['some-other...@not.helpful.com'], + references=refs) + assert_equal(FM.ForumThread.query.find().count(), 1) + assert_equal(FM.ForumPost.query.find().count(), 2) + + prev_post = FM.ForumPost.query.find().sort('timestamp', pymongo.DESCENDING).first() + refs = M.Notification._references(thread, prev_post) + ['second-message-id'] + self._post('testforum', 'Test Reply', 'Nothing here, either', + message_id='third-message-id', + # missing in_reply_to altogether + references=refs) + assert_equal(FM.ForumThread.query.find().count(), 1) + assert_equal(FM.ForumPost.query.find().count(), 3) def test_attach(self): self._post('testforum', 'Attachment Thread', 'This is a text file', @@ -255,6 +282,9 @@ class TestForumAsync(TestController): params=dict(subject='', delete='on')) def _post(self, topic, subject, body, **kw): + ''' + Submit a message very similar to how incoming email works + ''' message_id = kw.pop('message_id', '%s...@test.com' % random.random()) with h.push_config(c, user=self.user): c.app.handle_message(