# -*- coding: utf-8 -*- # Copyright The IETF Trust 2011, All Rights Reserved import os, shutil, datetime from StringIO import StringIO from pyquery import PyQuery from django.conf import settings from django.urls import reverse as urlreverse import debug # pyflakes:ignore from ietf.doc.factories import DocumentFactory, CharterFactory from ietf.doc.models import ( Document, State, BallotDocEvent, BallotType, NewRevisionDocEvent, TelechatDocEvent, WriteupDocEvent ) from ietf.doc.utils_charter import ( next_revision, default_review_text, default_action_text, charter_name_for_group ) from ietf.doc.utils import close_open_ballots from ietf.group.factories import RoleFactory from ietf.group.models import Group, GroupMilestone from ietf.iesg.models import TelechatDate from ietf.person.models import Person from ietf.utils.test_utils import TestCase, unicontent from ietf.utils.mail import outbox, empty_outbox from ietf.utils.test_data import make_test_data from ietf.utils.test_utils import login_testing_unauthorized class EditCharterTests(TestCase): def setUp(self): self.charter_dir = self.tempdir('charter') self.saved_charter_path = settings.CHARTER_PATH settings.CHARTER_PATH = self.charter_dir def tearDown(self): settings.CHARTER_PATH = self.saved_charter_path shutil.rmtree(self.charter_dir) def write_charter_file(self, charter): with open(os.path.join(self.charter_dir, "%s-%s.txt" % (charter.canonical_name(), charter.rev)), "w") as f: f.write("This is a charter.") def test_startstop_process(self): make_test_data() group = Group.objects.get(acronym="mars") charter = group.charter for option in ("recharter", "abandon"): self.client.logout() url = urlreverse('ietf.doc.views_charter.change_state', kwargs=dict(name=charter.name, option=option)) login_testing_unauthorized(self, "secretary", url) # normal get r = self.client.get(url) self.assertEqual(r.status_code, 200) # post self.write_charter_file(charter) r = self.client.post(url, dict(message="test message")) self.assertEqual(r.status_code, 302) if option == "abandon": self.assertTrue("abandoned" in charter.latest_event(type="changed_document").desc.lower()) else: self.assertTrue("state changed" in charter.latest_event(type="changed_state").desc.lower()) def test_change_state(self): make_test_data() group = Group.objects.get(acronym="ames") charter = group.charter url = urlreverse('ietf.doc.views_charter.change_state', kwargs=dict(name=charter.name)) login_testing_unauthorized(self, "secretary", url) first_state = charter.get_state() # normal get r = self.client.get(url) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) self.assertEqual(len(q('form select[name=charter_state]')), 1) # faulty post r = self.client.post(url, dict(charter_state="-12345")) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) self.assertTrue(len(q('form .has-error')) > 0) self.assertEqual(charter.get_state(), first_state) # change state for slug in ("intrev", "extrev", "iesgrev"): s = State.objects.get(used=True, type="charter", slug=slug) events_before = charter.docevent_set.count() empty_outbox() r = self.client.post(url, dict(charter_state=str(s.pk), message="test message")) self.assertEqual(r.status_code, 302) charter = Document.objects.get(name="charter-ietf-%s" % group.acronym) self.assertEqual(charter.get_state_slug(), slug) events_now = charter.docevent_set.count() self.assertTrue(events_now > events_before) def find_event(t): return [e for e in charter.docevent_set.all()[:events_now - events_before] if e.type == t] self.assertTrue("state changed" in find_event("changed_state")[0].desc.lower()) if slug in ("intrev", "extrev"): self.assertTrue(find_event("created_ballot")) self.assertEqual(len(outbox), 3 if slug=="intrev" else 2 ) if slug=="intrev": self.assertIn("Internal WG Review", outbox[-3]['Subject']) self.assertIn("iab@", outbox[-3]['To']) self.assertIn("iesg@", outbox[-3]['To']) self.assertIn("A new IETF WG", outbox[-3].get_payload()) body = outbox[-3].get_payload() for word in ["Chairs", "Ames Man ", "Secretaries", "Secretary ", "Assigned Area Director", "Areað Irector ", "Mailing list", "ames-wg@ietf.org", "Charter", "Milestones"]: self.assertIn(word, body) self.assertIn("state changed", outbox[-2]['Subject'].lower()) self.assertIn("iesg-secretary@", outbox[-2]['To']) body = outbox[-2].get_payload() for word in ["WG", "Charter", ]: self.assertIn(word, body) self.assertIn("State Update Notice", outbox[-1]['Subject']) self.assertIn("ames-chairs@", outbox[-1]['To']) body = outbox[-1].get_payload() for word in ["State changed", "Datatracker URL", ]: self.assertIn(word, body) by = Person.objects.get(user__username="secretary") for slug in ('extrev','iesgrev'): close_open_ballots(charter,by) r = self.client.post(url, dict(charter_state=str(State.objects.get(used=True,type='charter',slug=slug).pk) )) self.assertTrue(r.status_code,302) charter = Document.objects.get(name="charter-ietf-%s" % group.acronym) self.assertTrue(charter.ballot_open('approve')) # Exercise internal review of a recharter group = Group.objects.get(acronym="mars") charter = group.charter url = urlreverse('ietf.doc.views_charter.change_state', kwargs=dict(name=charter.name)) empty_outbox() r = self.client.post(url, dict(charter_state=str(State.objects.get(used=True,type="charter",slug="intrev").pk), message="test")) self.assertEqual(r.status_code, 302) self.assertTrue("A new charter" in outbox[-3].get_payload()) def test_abandon_bof(self): charter = CharterFactory(group__state_id='bof',group__type_id='wg') url = urlreverse('ietf.doc.views_charter.change_state',kwargs={'name':charter.name,'option':'abandon'}) login_testing_unauthorized(self, "secretary", url) response=self.client.get(url) self.assertEqual(response.status_code,200) response = self.client.post(url,{'comment':'Testing Abandoning a Bof Charter'}) self.assertEqual(response.status_code,302) charter = Document.objects.get(pk=charter.pk) self.assertEqual(charter.group.state_id,'abandon') self.assertTrue('Testing Abandoning' in charter.docevent_set.filter(type='added_comment').first().desc) def test_change_title(self): charter = CharterFactory(group__type_id='wg') url = urlreverse('ietf.doc.views_charter.change_title',kwargs={'name':charter.name}) login_testing_unauthorized(self, "secretary", url) response=self.client.get(url) self.assertEqual(response.status_code,200) response=self.client.post(url,{'charter_title':'New Test Title'}) self.assertEqual(response.status_code,302) charter=Document.objects.get(pk=charter.pk) self.assertEqual(charter.title,'New Test Title') def test_already_open_charter_ballot(self): # make sure the right thing happens to the charter ballots as the Secretariat # does the unusual state sequence of: intrev --> extrev --> intrev make_test_data() group = Group.objects.get(acronym="ames") charter = group.charter url = urlreverse('ietf.doc.views_charter.change_state', kwargs=dict(name=charter.name)) login_testing_unauthorized(self, "secretary", url) # get the charter state change page r = self.client.get(url) self.assertEqual(r.status_code, 200) # put the charter in "intrev" state s = State.objects.get(used=True, type="charter", slug="intrev") r = self.client.post(url, dict(charter_state=str(s.pk), message="test message")) self.assertEqual(r.status_code, 302) self.assertEqual(charter.get_state_slug(), "intrev") self.assertTrue(charter.ballot_open("r-extrev")) events_before = charter.docevent_set.count() # get the charter state change page again r = self.client.get(url) self.assertEqual(r.status_code, 200) # put the charter in "extrev" state without closing the previous ballot s = State.objects.get(used=True, type="charter", slug="extrev") r = self.client.post(url, dict(charter_state=str(s.pk), message="test message")) self.assertEqual(r.status_code, 302) charter = Document.objects.get(name="charter-ietf-%s" % group.acronym) self.assertEqual(charter.get_state_slug(), "extrev") self.assertTrue(charter.ballot_open("approve")) # make sure there is a closed_ballot event and a create_ballot event events_now = charter.docevent_set.count() self.assertTrue(events_now > events_before) def find_event(t): return [e for e in charter.docevent_set.all()[:events_now - events_before] if e.type == t] self.assertTrue(find_event("closed_ballot")) self.assertTrue(find_event("created_ballot")) events_before = events_now # get the charter state change page for a third time r = self.client.get(url) self.assertEqual(r.status_code, 200) # put the charter back in "intrev" state without closing the previous ballot s = State.objects.get(used=True, type="charter", slug="intrev") r = self.client.post(url, dict(charter_state=str(s.pk), message="test message")) self.assertEqual(r.status_code, 302) charter = Document.objects.get(name="charter-ietf-%s" % group.acronym) self.assertEqual(charter.get_state_slug(), "intrev") self.assertTrue(charter.ballot_open("r-extrev")) # make sure there is a closed_ballot event and a create_ballot event events_now = charter.docevent_set.count() self.assertTrue(events_now > events_before) self.assertTrue(find_event("closed_ballot")) self.assertTrue(find_event("created_ballot")) def test_edit_telechat_date(self): make_test_data() group = Group.objects.get(acronym="mars") charter = group.charter url = urlreverse('ietf.doc.views_doc.telechat_date;charter', kwargs=dict(name=charter.name)) login_testing_unauthorized(self, "secretary", url) # get r = self.client.get(url) self.assertEqual(r.status_code, 200) # add to telechat self.assertTrue(not charter.latest_event(TelechatDocEvent, "scheduled_for_telechat")) telechat_date = TelechatDate.objects.active()[0].date r = self.client.post(url, dict(name=group.name, acronym=group.acronym, telechat_date=telechat_date.isoformat())) self.assertEqual(r.status_code, 302) charter = Document.objects.get(name=charter.name) self.assertTrue(charter.latest_event(TelechatDocEvent, "scheduled_for_telechat")) self.assertEqual(charter.latest_event(TelechatDocEvent, "scheduled_for_telechat").telechat_date, telechat_date) # change telechat telechat_date = TelechatDate.objects.active()[1].date r = self.client.post(url, dict(name=group.name, acronym=group.acronym, telechat_date=telechat_date.isoformat())) self.assertEqual(r.status_code, 302) charter = Document.objects.get(name=charter.name) self.assertEqual(charter.latest_event(TelechatDocEvent, "scheduled_for_telechat").telechat_date, telechat_date) # remove from agenda telechat_date = "" r = self.client.post(url, dict(name=group.name, acronym=group.acronym, telechat_date=telechat_date)) self.assertEqual(r.status_code, 302) charter = Document.objects.get(name=charter.name) self.assertTrue(not charter.latest_event(TelechatDocEvent, "scheduled_for_telechat").telechat_date) def test_no_returning_item_for_different_ballot(self): make_test_data() group = Group.objects.get(acronym="ames") charter = group.charter url = urlreverse('ietf.doc.views_doc.telechat_date;charter', kwargs=dict(name=charter.name)) login_testing_unauthorized(self, "secretary", url) login = Person.objects.get(user__username="secretary") # Make it so that the charter has been through internal review, and passed its external review # ballot on a previous telechat last_week = datetime.date.today()-datetime.timedelta(days=7) BallotDocEvent.objects.create(type='created_ballot',by=login,doc=charter, rev=charter.rev, ballot_type=BallotType.objects.get(doc_type=charter.type,slug='r-extrev'), time=last_week) TelechatDocEvent.objects.create(type='scheduled_for_telechat', doc=charter, rev=charter.rev, by=login, telechat_date=last_week, returning_item=False) BallotDocEvent.objects.create(type='created_ballot', by=login, doc=charter, rev=charter.rev, ballot_type=BallotType.objects.get(doc_type=charter.type, slug='approve')) # Put the charter onto a future telechat and verify returning item is not set telechat_date = TelechatDate.objects.active()[1].date r = self.client.post(url, dict(name=group.name, acronym=group.acronym, telechat_date=telechat_date.isoformat())) self.assertEqual(r.status_code, 302) charter = Document.objects.get(name=charter.name) telechat_event = charter.latest_event(TelechatDocEvent, "scheduled_for_telechat") self.assertEqual(telechat_event.telechat_date, telechat_date) self.assertFalse(telechat_event.returning_item) def test_edit_notify(self): make_test_data() charter = Group.objects.get(acronym="mars").charter url = urlreverse('ietf.doc.views_doc.edit_notify;charter', kwargs=dict(name=charter.name)) login_testing_unauthorized(self, "secretary", url) # get r = self.client.get(url) self.assertEqual(r.status_code, 200) # post self.assertTrue(not charter.notify) newlist = "someone@example.com, someoneelse@example.com" r = self.client.post(url, dict(notify=newlist,save_addresses="1")) self.assertEqual(r.status_code, 302) charter = Document.objects.get(name=charter.name) self.assertEqual(charter.notify, newlist) # Ask the form to regenerate the list r = self.client.post(url,dict(regenerate_addresses="1")) self.assertEqual(r.status_code,200) charter= Document.objects.get(name=charter.name) # Regenerate does not save! self.assertEqual(charter.notify,newlist) q = PyQuery(r.content) formlist = q('form input[name=notify]')[0].value self.assertEqual(formlist, None) def test_edit_ad(self): make_test_data() charter = Group.objects.get(acronym="mars").charter url = urlreverse('ietf.doc.views_charter.edit_ad', kwargs=dict(name=charter.name)) login_testing_unauthorized(self, "secretary", url) # normal get r = self.client.get(url) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) self.assertEqual(len(q('select[name=ad]')),1) # post self.assertTrue(not charter.ad) ad2 = Person.objects.get(name='Ad No2') r = self.client.post(url,dict(ad=str(ad2.pk))) self.assertEqual(r.status_code, 302) charter = Document.objects.get(name=charter.name) self.assertEqual(charter.ad, ad2) def test_submit_charter(self): make_test_data() group = Group.objects.get(acronym="mars") charter = group.charter url = urlreverse('ietf.doc.views_charter.submit', kwargs=dict(name=charter.name)) login_testing_unauthorized(self, "secretary", url) # normal get r = self.client.get(url) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) self.assertEqual(len(q('form input[name=txt]')), 1) # faulty post test_file = StringIO("\x10\x11\x12") # post binary file test_file.name = "unnamed" r = self.client.post(url, dict(txt=test_file)) self.assertEqual(r.status_code, 200) self.assertTrue("does not appear to be a text file" in unicontent(r)) # post prev_rev = charter.rev latin_1_snippet = '\xe5' * 10 utf_8_snippet = '\xc3\xa5' * 10 test_file = StringIO("Windows line\r\nMac line\rUnix line\n" + latin_1_snippet) test_file.name = "unnamed" r = self.client.post(url, dict(txt=test_file)) self.assertEqual(r.status_code, 302) charter = Document.objects.get(name="charter-ietf-%s" % group.acronym) self.assertEqual(charter.rev, next_revision(prev_rev)) self.assertTrue("new_revision" in charter.latest_event().type) with open(os.path.join(self.charter_dir, charter.canonical_name() + "-" + charter.rev + ".txt")) as f: self.assertEqual(f.read(), "Windows line\nMac line\nUnix line\n" + utf_8_snippet) def test_submit_initial_charter(self): make_test_data() group = Group.objects.get(acronym="mars") # get rid of existing charter charter = group.charter group.charter = None group.save() charter.delete() charter = None url = urlreverse('ietf.doc.views_charter.submit', kwargs=dict(name=charter_name_for_group(group))) login_testing_unauthorized(self, "secretary", url) # normal get r = self.client.get(url) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) self.assertEqual(len(q('form input[name=txt]')), 1) # create charter test_file = StringIO("Simple test") test_file.name = "unnamed" r = self.client.post(url, dict(txt=test_file)) self.assertEqual(r.status_code, 302) charter = Document.objects.get(name="charter-ietf-%s" % group.acronym) self.assertEqual(charter.rev, "00-00") self.assertTrue("new_revision" in charter.latest_event().type) group = Group.objects.get(pk=group.pk) self.assertEqual(group.charter, charter) def test_edit_review_announcement_text(self): draft = make_test_data() charter = draft.group.charter url = urlreverse('ietf.doc.views_charter.review_announcement_text', kwargs=dict(name=charter.name)) self.client.logout() login_testing_unauthorized(self, "secretary", url) # normal get r = self.client.get(url) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) self.assertEqual(len(q('textarea[name=announcement_text]')), 1) self.assertEqual(len(q('textarea[name=new_work_text]')), 1) by = Person.objects.get(user__username="secretary") (e1, e2) = default_review_text(draft.group, charter, by) announcement_text = e1.text new_work_text = e2.text empty_outbox() r = self.client.post(url, dict( announcement_text=announcement_text, new_work_text=new_work_text, send_both="1")) self.assertEqual(len(outbox), 2) self.assertTrue(all(['WG Review' in m['Subject'] for m in outbox])) self.assertTrue('ietf-announce@' in outbox[0]['To']) self.assertTrue('mars-wg@' in outbox[0]['Cc']) self.assertTrue('new-work@' in outbox[1]['To']) empty_outbox() r = self.client.post(url, dict( announcement_text=announcement_text, new_work_text=new_work_text, send_annc_only="1")) self.assertEqual(len(outbox), 1) self.assertTrue('ietf-announce@' in outbox[0]['To']) empty_outbox() r = self.client.post(url, dict( announcement_text=announcement_text, new_work_text=new_work_text, send_nw_only="1")) self.assertEqual(len(outbox), 1) self.assertTrue('new-work@' in outbox[0]['To']) # save r = self.client.post(url, dict( announcement_text="This is a simple test.", new_work_text="New work gets something different.", save_text="1")) self.assertEqual(r.status_code, 302) self.assertTrue("This is a simple test" in charter.latest_event(WriteupDocEvent, type="changed_review_announcement").text) self.assertTrue("New work gets something different." in charter.latest_event(WriteupDocEvent, type="changed_new_work_text").text) # test regenerate r = self.client.post(url, dict( announcement_text="This is a simple test.", new_work_text="Too simple perhaps?", regenerate_text="1")) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) charter = Document.objects.get(name=charter.name) self.assertTrue(draft.group.name in charter.latest_event(WriteupDocEvent, type="changed_review_announcement").text) self.assertTrue(draft.group.name in charter.latest_event(WriteupDocEvent, type="changed_new_work_text").text) def test_edit_action_announcement_text(self): draft = make_test_data() charter = draft.group.charter url = urlreverse('ietf.doc.views_charter.action_announcement_text', kwargs=dict(name=charter.name)) self.client.logout() login_testing_unauthorized(self, "secretary", url) # normal get r = self.client.get(url) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) self.assertEqual(len(q('textarea[name=announcement_text]')), 1) # save r = self.client.post(url, dict( announcement_text="This is a simple test.", save_text="1")) self.assertEqual(r.status_code, 302) charter = Document.objects.get(name=charter.name) self.assertTrue("This is a simple test" in charter.latest_event(WriteupDocEvent, type="changed_action_announcement").text) # test regenerate r = self.client.post(url, dict( announcement_text="This is a simple test.", regenerate_text="1")) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) charter = Document.objects.get(name=charter.name) self.assertTrue(draft.group.name in charter.latest_event(WriteupDocEvent, type="changed_action_announcement").text) def test_edit_ballot_writeupnotes(self): draft = make_test_data() charter = draft.group.charter by = Person.objects.get(user__username="secretary") BallotDocEvent.objects.create( type="created_ballot", ballot_type=BallotType.objects.get(doc_type="charter", slug="approve"), by=by, doc=charter, rev=charter.rev, desc="Created ballot", ) url = urlreverse('ietf.doc.views_charter.ballot_writeupnotes', kwargs=dict(name=charter.name)) login_testing_unauthorized(self, "secretary", url) e = default_action_text(draft.group, charter, by) e.save() # normal get r = self.client.get(url) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) self.assertEqual(len(q('textarea[name=ballot_writeup]')), 1) # save r = self.client.post(url, dict( ballot_writeup="This is a simple test.", save_ballot_writeup="1")) self.assertEqual(r.status_code, 200) self.assertTrue("This is a simple test" in charter.latest_event(WriteupDocEvent, type="changed_ballot_writeup_text").text) # send empty_outbox() r = self.client.post(url, dict( ballot_writeup="This is a simple test.", send_ballot="1")) self.assertEqual(len(outbox), 1) self.assertTrue('Evaluation' in outbox[0]['Subject']) def test_approve(self): make_test_data() group = Group.objects.get(acronym="ames") charter = group.charter url = urlreverse('ietf.doc.views_charter.approve', kwargs=dict(name=charter.name)) login_testing_unauthorized(self, "secretary", url) self.write_charter_file(charter) p = Person.objects.get(name="Areað Irector") BallotDocEvent.objects.create( type="created_ballot", ballot_type=BallotType.objects.get(doc_type="charter", slug="approve"), by=p, doc=charter, rev=charter.rev, desc="Created ballot", ) charter.set_state(State.objects.get(used=True, type="charter", slug="iesgrev")) due_date = datetime.date.today() + datetime.timedelta(days=180) m1 = GroupMilestone.objects.create(group=group, state_id="active", desc="Has been copied", due=due_date, resolved="") GroupMilestone.objects.create(group=group, state_id="active", desc="To be deleted", due=due_date, resolved="") GroupMilestone.objects.create(group=group, state_id="charter", desc="Has been copied", due=due_date, resolved="") m4 = GroupMilestone.objects.create(group=group, state_id="charter", desc="New charter milestone", due=due_date, resolved="") # normal get r = self.client.get(url) self.assertEqual(r.status_code, 200) q = PyQuery(r.content) self.assertTrue(q('[type=submit]:contains("Send announcement")')) self.assertEqual(len(q('pre')), 1) # approve empty_outbox() r = self.client.post(url, dict()) self.assertEqual(r.status_code, 302) charter = Document.objects.get(name=charter.name) self.assertEqual(charter.get_state_slug(), "approved") self.assertTrue(not charter.ballot_open("approve")) self.assertEqual(charter.rev, "01") self.assertTrue(os.path.exists(os.path.join(self.charter_dir, "charter-ietf-%s-%s.txt" % (group.acronym, charter.rev)))) self.assertEqual(len(outbox), 2) # self.assertTrue("approved" in outbox[0]['Subject'].lower()) self.assertTrue("iesg-secretary" in outbox[0]['To']) body = outbox[0].get_payload() for word in ["WG", "/wg/ames/about/", "Charter", "/doc/charter-ietf-ames/", ]: self.assertIn(word, body) # self.assertTrue("WG Action" in outbox[1]['Subject']) self.assertTrue("ietf-announce" in outbox[1]['To']) self.assertTrue("ames-wg@ietf.org" in outbox[1]['Cc']) body = outbox[1].get_payload() for word in ["Chairs", "Ames Man ", "Secretaries", "Secretary ", "Assigned Area Director", "Areað Irector ", "Area Directors", "Mailing list", "ames-wg@ietf.org", "Charter", "/doc/charter-ietf-ames/", "Milestones"]: self.assertIn(word, body) self.assertEqual(group.groupmilestone_set.filter(state="charter").count(), 0) self.assertEqual(group.groupmilestone_set.filter(state="active").count(), 2) self.assertEqual(group.groupmilestone_set.filter(state="active", desc=m1.desc).count(), 1) self.assertEqual(group.groupmilestone_set.filter(state="active", desc=m4.desc).count(), 1) def test_charter_with_milestones(self): draft = make_test_data() charter = draft.group.charter NewRevisionDocEvent.objects.create(doc=charter, type="new_revision", rev=charter.rev, by=Person.objects.get(name="(System)")) m = GroupMilestone.objects.create(group=draft.group, state_id="active", desc="Test milestone", due=datetime.date.today(), resolved="") url = urlreverse('ietf.doc.views_charter.charter_with_milestones_txt', kwargs=dict(name=charter.name, rev=charter.rev)) r = self.client.get(url) self.assertEqual(r.status_code, 200) self.assertTrue(m.desc in unicontent(r)) def test_chartering_from_bof(self): ad_role = RoleFactory(group__type_id='area',name_id='ad') charter = DocumentFactory(type_id='charter',group__type_id='wg',group__state_id='bof',group__parent=ad_role.group) e1,_ = default_review_text(charter.group, charter, Person.objects.get(name="(System)")) self.assertTrue('A new IETF WG has been proposed' in e1.text)