def test_classical_thread(self): # msg1 # |-msg2 # | `-msg4 # `-msg3 thread = Thread("example-list", "<msg1>") self.store.db.add(thread) msg1 = make_fake_email(1) msg2 = make_fake_email(2) msg3 = make_fake_email(3) msg4 = make_fake_email(4) # All in the same thread msg2.thread_id = msg3.thread_id = msg4.thread_id = u"<msg1>" # Set up the reply tree msg2.in_reply_to = msg3.in_reply_to = u"<msg1>" msg4.in_reply_to = u"<msg2>" # Init with false values msg1.thread_order = msg1.thread_depth = \ msg2.thread_order = msg2.thread_depth = \ msg3.thread_order = msg3.thread_depth = \ msg4.thread_order = msg4.thread_depth = 42 self.store.db.add(msg1) self.store.db.add(msg2) self.store.db.add(msg3) self.store.db.add(msg4) self.store.flush() compute_thread_order_and_depth(thread) self.assertEqual(msg1.thread_order, 0) self.assertEqual(msg1.thread_depth, 0) self.assertEqual(msg2.thread_order, 1) self.assertEqual(msg2.thread_depth, 1) self.assertEqual(msg3.thread_order, 3) self.assertEqual(msg3.thread_depth, 1) self.assertEqual(msg4.thread_order, 2) self.assertEqual(msg4.thread_depth, 2)
def test_reply_to_oneself(self): # A message replying to itself (yes, it's been spotted in the wild) thread = Thread("example-list", "<msg1>") self.store.db.add(thread) msg1 = make_fake_email(1) msg1.in_reply_to = u"<msg1>" msg1.thread_order = msg1.thread_depth = 42 self.store.db.add(msg1) self.store.flush() compute_thread_order_and_depth(thread) # Don't traceback with a "maximum recursion depth exceeded" error self.assertEqual(msg1.thread_order, 0) self.assertEqual(msg1.thread_depth, 0)
def test_reply_loops(self): """Loops in message replies""" # This implies that someone replies to a message not yet sent, but you # never know, Dr Who can be on your mailing-list. thread = Thread("example-list", "<msg1>") self.store.db.add(thread) msg1 = make_fake_email(1) msg1.in_reply_to = u"<msg2>" self.store.db.add(msg1) msg2 = make_fake_email(2) msg2.thread_id = u"<msg1>" msg2.in_reply_to = u"<msg1>" self.store.db.add(msg2) self.store.flush() compute_thread_order_and_depth(thread)
def apply(store): """Add the thread table""" dbtype = get_db_type(store) for statement in SQL[dbtype]: store.execute(statement) for email in store.find(Email, Email.in_reply_to == None).values( Email.list_name, Email.thread_id): list_name, thread_id = email thread = Thread(list_name, thread_id) store.add(thread) store.flush() for email in store.find(Email).values(Email.list_name, Email.thread_id): # in case of partial imports, some threads are missing their original # email (the one without an in-reply-to header) list_name, thread_id = email thread_count = store.find( Thread, And( Thread.list_name == list_name, Thread.thread_id == thread_id, )).count() if thread_count == 0: # this email has no associated thread, create it thread = Thread(list_name, thread_id) store.add(thread) store.flush() if dbtype == "postgres": store.execute( 'ALTER TABLE email ' 'ADD FOREIGN KEY (list_name, thread_id) ' 'REFERENCES thread(list_name, thread_id) ON DELETE CASCADE;') store.execute( 'ALTER TABLE attachment ' 'ADD FOREIGN KEY (list_name, message_id) ' 'REFERENCES email(list_name, message_id) ON DELETE CASCADE;') store.commit()
def test_simple_thread(self): # A basic thread: msg2 replies to msg1 thread = Thread("example-list", "<msg1>") self.store.db.add(thread) msg1 = make_fake_email(1) msg1.thread_order = msg1.thread_depth = 42 self.store.db.add(msg1) msg2 = make_fake_email(2) msg2.thread_id = u"<msg1>" msg2.in_reply_to = u"<msg1>" msg2.thread_order = msg2.thread_depth = 42 self.store.db.add(msg2) self.store.flush() compute_thread_order_and_depth(thread) self.assertEqual(msg1.thread_order, 0) self.assertEqual(msg1.thread_depth, 0) self.assertEqual(msg2.thread_order, 1) self.assertEqual(msg2.thread_depth, 1)
def test_thread_no_email(self): thread = Thread("example-list", "<msg1>") self.store.db.add(thread) self.store.flush()