11# -*- coding: utf-8 -*-
22
3- import datetime
3+ import datetime , os , shutil , json
4+ import tarfile , tempfile , mailbox
5+ import email .mime .multipart , email .mime .text , email .utils
6+ from StringIO import StringIO
47
58from django .core .urlresolvers import reverse as urlreverse
9+ from django .conf import settings
610
711from pyquery import PyQuery
812
913import debug # pyflakes:ignore
1014
1115from ietf .review .models import ReviewRequest , Reviewer
16+ import ietf .review .mailarch
1217from ietf .person .models import Person
1318from ietf .group .models import Group , Role
1419from ietf .name .models import ReviewResultName , ReviewRequestStateName
1722from ietf .utils .test_utils import login_testing_unauthorized , unicontent , reload_db_objects
1823from ietf .utils .mail import outbox , empty_outbox
1924
20-
2125def make_review_data (doc ):
2226 team = Group .objects .create (state_id = "active" , acronym = "reviewteam" , name = "Review Team" , type_id = "team" )
2327 team .reviewresultname_set .add (ReviewResultName .objects .filter (slug__in = ["issues" , "ready-issues" , "ready" , "not-ready" ]))
@@ -39,9 +43,28 @@ def make_review_data(doc):
3943 p = Person .objects .get (user__username = "marschairman" )
4044 role = Role .objects .create (name_id = "reviewer" , person = p , email = p .email_set .first (), group = team )
4145
46+ p = Person .objects .get (user__username = "secretary" )
47+ role = Role .objects .create (name_id = "secretary" , person = p , email = p .email_set .first (), group = team )
48+
4249 return review_req
4350
4451class ReviewTests (TestCase ):
52+ def setUp (self ):
53+ self .review_dir = os .path .abspath ("tmp-review-dir" )
54+ if not os .path .exists (self .review_dir ):
55+ os .mkdir (self .review_dir )
56+
57+ self .old_document_path_pattern = settings .DOCUMENT_PATH_PATTERN
58+ settings .DOCUMENT_PATH_PATTERN = self .review_dir + "/{doc.type_id}/"
59+
60+ self .review_subdir = os .path .join (self .review_dir , "review" )
61+ if not os .path .exists (self .review_subdir ):
62+ os .mkdir (self .review_subdir )
63+
64+ def tearDown (self ):
65+ shutil .rmtree (self .review_dir )
66+ settings .DOCUMENT_PATH_PATTERN = self .old_document_path_pattern
67+
4568 def test_request_review (self ):
4669 doc = make_test_data ()
4770 review_req = make_review_data (doc )
@@ -229,3 +252,266 @@ def test_reject_reviewer_assignment(self):
229252 self .assertEqual (len (outbox ), 1 )
230253 self .assertTrue ("Test message" in unicode (outbox [0 ]))
231254
255+ def make_test_mbox_tarball (self , review_req ):
256+ mbox_path = os .path .join (self .review_dir , "testmbox.tar.gz" )
257+ with tarfile .open (mbox_path , "w:gz" ) as tar :
258+ with tempfile .NamedTemporaryFile (dir = self .review_dir , suffix = ".mbox" ) as tmp :
259+ mbox = mailbox .mbox (tmp .name )
260+
261+ # plain text
262+ msg = email .mime .text .MIMEText ("Hello,\n \n I have reviewed the document and did not find any problems.\n \n John Doe" )
263+ msg ["From" ] = "johndoe@example.com"
264+ msg ["To" ] = review_req .team .list_email
265+ msg ["Subject" ] = "Review of {}-01" .format (review_req .doc .name )
266+ msg ["Message-ID" ] = email .utils .make_msgid ()
267+ msg ["Archived-At" ] = "<https://www.example.com/testmessage>"
268+ msg ["Date" ] = email .utils .formatdate ()
269+
270+ mbox .add (msg )
271+
272+ # plain text + HTML
273+ msg = email .mime .multipart .MIMEMultipart ('alternative' )
274+ msg ["From" ] = "johndoe2@example.com"
275+ msg ["To" ] = review_req .team .list_email
276+ msg ["Subject" ] = "Review of {}" .format (review_req .doc .name )
277+ msg ["Message-ID" ] = email .utils .make_msgid ()
278+ msg ["Archived-At" ] = "<https://www.example.com/testmessage2>"
279+
280+ msg .attach (email .mime .text .MIMEText ("Hi!,\r \n Looks OK!\r \n -John" , "plain" ))
281+ msg .attach (email .mime .text .MIMEText ("<html><body><p>Hi!,</p><p>Looks OK!</p><p>-John</p></body></html>" , "html" ))
282+ mbox .add (msg )
283+
284+ tmp .flush ()
285+
286+ tar .add (os .path .relpath (tmp .name ))
287+
288+ return mbox_path
289+
290+ def test_search_mail_archive (self ):
291+ doc = make_test_data ()
292+ review_req = make_review_data (doc )
293+ review_req .state = ReviewRequestStateName .objects .get (slug = "accepted" )
294+ review_req .save ()
295+ review_req .team .list_email = "{}@ietf.org" .format (review_req .team .acronym )
296+ review_req .team .save ()
297+
298+ # test URL construction
299+ query_urls = ietf .review .mailarch .construct_query_urls (review_req )
300+ self .assertTrue (review_req .doc .name in query_urls ["query_data_url" ])
301+
302+ # test parsing
303+ mbox_path = self .make_test_mbox_tarball (review_req )
304+
305+ try :
306+ # mock URL generator and point it to local file - for this
307+ # to work, the module (and not the function) must be
308+ # imported in the view
309+ real_fn = ietf .review .mailarch .construct_query_urls
310+ ietf .review .mailarch .construct_query_urls = lambda review_req , query = None : { "query_data_url" : "file://" + os .path .abspath (mbox_path ) }
311+
312+ url = urlreverse ('ietf.doc.views_review.search_mail_archive' , kwargs = { "name" : doc .name , "request_id" : review_req .pk })
313+ login_testing_unauthorized (self , "secretary" , url )
314+
315+ r = self .client .get (url )
316+ self .assertEqual (r .status_code , 200 )
317+ messages = json .loads (r .content )["messages" ]
318+ self .assertEqual (len (messages ), 2 )
319+
320+ self .assertEqual (messages [0 ]["url" ], "https://www.example.com/testmessage" )
321+ self .assertTrue ("John Doe" in messages [0 ]["content" ])
322+ self .assertEqual (messages [0 ]["subject" ], "Review of {}-01" .format (review_req .doc .name ))
323+
324+ self .assertEqual (messages [1 ]["url" ], "https://www.example.com/testmessage2" )
325+ self .assertTrue ("Looks OK" in messages [1 ]["content" ])
326+ self .assertTrue ("<html>" not in messages [1 ]["content" ])
327+ self .assertEqual (messages [1 ]["subject" ], "Review of {}" .format (review_req .doc .name ))
328+ finally :
329+ ietf .review .mailarch .construct_query_urls = real_fn
330+
331+ def setup_complete_review_test (self ):
332+ doc = make_test_data ()
333+ review_req = make_review_data (doc )
334+ review_req .state = ReviewRequestStateName .objects .get (slug = "accepted" )
335+ review_req .save ()
336+ review_req .team .list_email = "{}@ietf.org" .format (review_req .team .acronym )
337+ for r in ReviewResultName .objects .filter (slug__in = ("issues" , "ready" )):
338+ review_req .team .reviewresultname_set .add (r )
339+ review_req .team .save ()
340+
341+ url = urlreverse ('ietf.doc.views_review.complete_review' , kwargs = { "name" : doc .name , "request_id" : review_req .pk })
342+
343+ return review_req , url
344+
345+ def test_complete_review_upload_content (self ):
346+ review_req , url = self .setup_complete_review_test ()
347+
348+ login_testing_unauthorized (self , review_req .reviewer .person .user .username , url )
349+
350+ # get
351+ r = self .client .get (url )
352+ self .assertEqual (r .status_code , 200 )
353+
354+ # faulty post
355+ r = self .client .post (url , data = {
356+ "result" : "ready" ,
357+ "state" : "completed" ,
358+ "reviewed_rev" : "abc" ,
359+ "review_submission" : "upload" ,
360+ "review_content" : "" ,
361+ "review_url" : "" ,
362+ "review_file" : "" ,
363+ })
364+ self .assertEqual (r .status_code , 200 )
365+ q = PyQuery (r .content )
366+ self .assertTrue (q ("[name=reviewed_rev]" ).closest (".form-group" ).filter (".has-error" ))
367+ self .assertTrue (q ("[name=review_file]" ).closest (".form-group" ).filter (".has-error" ))
368+
369+ # complete by uploading file
370+ empty_outbox ()
371+
372+ test_file = StringIO ("This is a review\n with two lines" )
373+ test_file .name = "unnamed"
374+
375+ r = self .client .post (url , data = {
376+ "result" : ReviewResultName .objects .get (teams = review_req .team , slug = "ready" ).pk ,
377+ "state" : ReviewRequestStateName .objects .get (slug = "completed" ).pk ,
378+ "reviewed_rev" : review_req .doc .rev ,
379+ "review_submission" : "upload" ,
380+ "review_content" : "" ,
381+ "review_url" : "" ,
382+ "review_file" : test_file ,
383+ })
384+ self .assertEqual (r .status_code , 302 )
385+
386+ review_req = reload_db_objects (review_req )
387+ self .assertEqual (review_req .state_id , "completed" )
388+ self .assertEqual (review_req .result_id , "ready" )
389+ self .assertEqual (review_req .reviewed_rev , review_req .doc .rev )
390+ self .assertTrue (review_req .team .acronym .lower () in review_req .review .name )
391+ self .assertTrue (review_req .doc .rev in review_req .review .name )
392+
393+ with open (os .path .join (self .review_subdir , review_req .review .name + "-" + review_req .review .rev + ".txt" )) as f :
394+ self .assertEqual (f .read (), "This is a review\n with two lines" )
395+
396+ self .assertEqual (len (outbox ), 1 )
397+ self .assertTrue (review_req .team .list_email in outbox [0 ]["To" ])
398+ self .assertTrue ("This is a review" in unicode (outbox [0 ]))
399+
400+ self .assertTrue (settings .MAILING_LIST_ARCHIVE_URL in review_req .review .external_url )
401+
402+ def test_complete_review_enter_content (self ):
403+ review_req , url = self .setup_complete_review_test ()
404+
405+ login_testing_unauthorized (self , review_req .reviewer .person .user .username , url )
406+
407+ # complete by uploading file
408+ empty_outbox ()
409+
410+ r = self .client .post (url , data = {
411+ "result" : ReviewResultName .objects .get (teams = review_req .team , slug = "ready" ).pk ,
412+ "state" : ReviewRequestStateName .objects .get (slug = "completed" ).pk ,
413+ "reviewed_rev" : review_req .doc .rev ,
414+ "review_submission" : "enter" ,
415+ "review_content" : "This is a review\n with two lines" ,
416+ "review_url" : "" ,
417+ "review_file" : "" ,
418+ })
419+ self .assertEqual (r .status_code , 302 )
420+
421+ review_req = reload_db_objects (review_req )
422+ self .assertEqual (review_req .state_id , "completed" )
423+
424+ with open (os .path .join (self .review_subdir , review_req .review .name + "-" + review_req .review .rev + ".txt" )) as f :
425+ self .assertEqual (f .read (), "This is a review\n with two lines" )
426+
427+ self .assertEqual (len (outbox ), 1 )
428+ self .assertTrue (review_req .team .list_email in outbox [0 ]["To" ])
429+ self .assertTrue ("This is a review" in unicode (outbox [0 ]))
430+
431+ self .assertTrue (settings .MAILING_LIST_ARCHIVE_URL in review_req .review .external_url )
432+
433+ def test_complete_review_link_to_mailing_list (self ):
434+ review_req , url = self .setup_complete_review_test ()
435+
436+ login_testing_unauthorized (self , review_req .reviewer .person .user .username , url )
437+
438+ # complete by uploading file
439+ empty_outbox ()
440+
441+ r = self .client .post (url , data = {
442+ "result" : ReviewResultName .objects .get (teams = review_req .team , slug = "ready" ).pk ,
443+ "state" : ReviewRequestStateName .objects .get (slug = "completed" ).pk ,
444+ "reviewed_rev" : review_req .doc .rev ,
445+ "review_submission" : "link" ,
446+ "review_content" : "This is a review\n with two lines" ,
447+ "review_url" : "http://example.com/testreview/" ,
448+ "review_file" : "" ,
449+ })
450+ self .assertEqual (r .status_code , 302 )
451+
452+ review_req = reload_db_objects (review_req )
453+ self .assertEqual (review_req .state_id , "completed" )
454+
455+ with open (os .path .join (self .review_subdir , review_req .review .name + "-" + review_req .review .rev + ".txt" )) as f :
456+ self .assertEqual (f .read (), "This is a review\n with two lines" )
457+
458+ self .assertEqual (len (outbox ), 0 )
459+ self .assertTrue ("http://example.com" in review_req .review .external_url )
460+
461+ def test_partially_complete_review (self ):
462+ review_req , url = self .setup_complete_review_test ()
463+
464+ login_testing_unauthorized (self , review_req .reviewer .person .user .username , url )
465+
466+ # partially complete
467+ empty_outbox ()
468+
469+ r = self .client .post (url , data = {
470+ "result" : ReviewResultName .objects .get (teams = review_req .team , slug = "ready" ).pk ,
471+ "state" : ReviewRequestStateName .objects .get (slug = "part-completed" ).pk ,
472+ "reviewed_rev" : review_req .doc .rev ,
473+ "review_submission" : "enter" ,
474+ "review_content" : "This is a review\n with two lines" ,
475+ })
476+ self .assertEqual (r .status_code , 302 )
477+
478+ review_req = reload_db_objects (review_req )
479+ self .assertEqual (review_req .state_id , "part-completed" )
480+ self .assertTrue (review_req .doc .rev in review_req .review .name )
481+
482+ self .assertEqual (len (outbox ), 2 )
483+ self .assertTrue ("secretary" in outbox [0 ]["To" ])
484+ self .assertTrue ("partially" in outbox [0 ]["Subject" ].lower ())
485+ self .assertTrue ("new review request" in unicode (outbox [0 ]))
486+
487+ self .assertTrue (review_req .team .list_email in outbox [1 ]["To" ])
488+ self .assertTrue ("partial review" in outbox [1 ]["Subject" ].lower ())
489+ self .assertTrue ("This is a review" in unicode (outbox [1 ]))
490+
491+ first_review = review_req .review
492+ first_reviewer = review_req .reviewer
493+
494+
495+ # complete
496+ review_req = ReviewRequest .objects .get (state = "requested" , doc = review_req .doc , team = review_req .team )
497+ self .assertEqual (review_req .reviewer , None )
498+ review_req .reviewer = first_reviewer # same reviewer, so we can test uniquification
499+ review_req .save ()
500+
501+ url = urlreverse ('ietf.doc.views_review.complete_review' , kwargs = { "name" : review_req .doc .name , "request_id" : review_req .pk })
502+
503+ r = self .client .post (url , data = {
504+ "result" : ReviewResultName .objects .get (teams = review_req .team , slug = "ready" ).pk ,
505+ "state" : ReviewRequestStateName .objects .get (slug = "completed" ).pk ,
506+ "reviewed_rev" : review_req .doc .rev ,
507+ "review_submission" : "enter" ,
508+ "review_content" : "This is another review\n with\n three lines" ,
509+ })
510+ self .assertEqual (r .status_code , 302 )
511+
512+ review_req = reload_db_objects (review_req )
513+ self .assertEqual (review_req .state_id , "completed" )
514+ self .assertTrue (review_req .doc .rev in review_req .review .name )
515+ second_review = review_req .review
516+ self .assertTrue (first_review .name != second_review .name )
517+ self .assertTrue (second_review .name .endswith ("-2" )) # uniquified
0 commit comments